From 1889ed4379fdd3c0a2015a31edac3bc071d9952f Mon Sep 17 00:00:00 2001 From: Jason Date: Fri, 13 Sep 2024 18:28:50 -0500 Subject: [PATCH 01/15] add zoom, removed invalid qrcode check, remove auto navigate, and add button --- src/screens/events/QRCodeScanningScreen.tsx | 174 ++++++++++++++------ 1 file changed, 123 insertions(+), 51 deletions(-) diff --git a/src/screens/events/QRCodeScanningScreen.tsx b/src/screens/events/QRCodeScanningScreen.tsx index ff153ecc..9287c570 100644 --- a/src/screens/events/QRCodeScanningScreen.tsx +++ b/src/screens/events/QRCodeScanningScreen.tsx @@ -1,15 +1,21 @@ -import { View, Text, Button, StyleSheet, TouchableHighlight, TouchableOpacity, Alert, Animated, Easing } from 'react-native'; +import { View, Text, TouchableOpacity, Alert, Animated, Easing } from 'react-native'; import React, { useEffect, useRef, useState } from 'react'; import { CameraView, Camera } from 'expo-camera'; import { SafeAreaView } from 'react-native-safe-area-context'; +import { GestureHandlerRootView, PinchGestureHandler, PinchGestureHandlerGestureEvent, State } from 'react-native-gesture-handler'; import { MainStackParams } from '../../types/navigation'; import { Octicons } from '@expo/vector-icons'; import { NativeStackScreenProps } from '@react-navigation/native-stack'; const QRCodeScanningScreen = ({ navigation }: NativeStackScreenProps) => { const [hasCameraPermissions, setHasCameraPermissions] = useState(null); - const [scanned, setScanned] = useState(false); const pulseAnim = useRef(new Animated.Value(1)).current; + const [zoom, setZoom] = useState(0); + const [qrData, setQrData] = useState<{ id: string; mode: string } | null>(null); + const cameraRef = useRef(null); + const lastScale = useRef(1); + const maxZoom = 0.05; + const timeoutRef = useRef(null); useEffect(() => { const getBarCodeScannerPermissions = async () => { @@ -42,29 +48,60 @@ const QRCodeScanningScreen = ({ navigation }: NativeStackScreenProps { - console.log('Data Received', `Bar code with type ${type} and data ${data} has been scanned!`); - setScanned(true); const dataRegex: RegExp = /^tamu-shpe:\/\/event\?id=[a-zA-z0-9]+&mode=(sign-in|sign-out)$/i; - if (!dataRegex.test(data)) { - Alert.alert("Invalid QR Code", "Either this QR Code is invalid or was misscanned. Please try again.", [ - { - text: 'ok', - onPress: () => { - setScanned(false); - } - } - ]); - } - else { + + if (dataRegex.test(data)) { const linkVariables = data.split('?')[1].split('&'); const id = linkVariables[0].split('=')[1]; const mode = linkVariables[1].split('=')[1]; - if (id && mode === 'sign-in' || mode === 'sign-out') { - navigation.navigate("EventVerificationScreen", { id, mode }); + if (id && (mode === 'sign-in' || mode === 'sign-out')) { + setQrData({ id, mode }); + + // Clear any existing timeout when valid data is found + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + + // Set a timeout to clear qrData after 3 seconds if no new valid QR codes are detected + timeoutRef.current = setTimeout(() => { + setQrData(null); + }, 5000); } } }; + useEffect(() => { + return () => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + }; + }, []); + + const handleConfirm = () => { + if (qrData) { + navigation.navigate("EventVerificationScreen", { id: qrData.id, mode: qrData.mode as "sign-in" | "sign-out" }); + setQrData(null); + } + }; + + const handlePinchGestureEvent = ({ nativeEvent }: PinchGestureHandlerGestureEvent) => { + if (nativeEvent.scale !== 0) { + const baseZoomFactor = 0.0004; + const zoomSensitivity = 0.1; + const dynamicFactor = zoom * zoomSensitivity + baseZoomFactor; + const scaleChange = (nativeEvent.scale - 1) * dynamicFactor * (nativeEvent.scale > 1 ? 1 : 2); + const newZoom = Math.min(Math.max(zoom + scaleChange, 0), maxZoom); + setZoom(newZoom); + } + }; + + const handlePinchStateChange = ({ nativeEvent }: PinchGestureHandlerGestureEvent) => { + if (nativeEvent.state === State.END || nativeEvent.state === State.CANCELLED) { + lastScale.current = 1; + } + }; + if (hasCameraPermissions === null) { return Requesting for camera permission; } @@ -73,43 +110,78 @@ const QRCodeScanningScreen = ({ navigation }: NativeStackScreenProps - {/* Header */} - - - Scanner + + + {/* Header */} + + + Scanner + + navigation.goBack()}> + + - navigation.goBack()} > - - - - - - {/* Pulsing Effect */} - - - - - - - + + + + {/* Pulsing Effect */} + + + + + + + + + - - - - - Using Scanner - Scan the QRCode provided by the event host. - - + + {(1 + zoom * 100).toFixed(1)}x + + + {/* Button for Sign In/Sign Out */} + {qrData && ( + + + + {qrData.mode === 'sign-in' ? 'Sign in to the event' : 'Sign out of the event'} + + + + )} + + + + + Using Scanner + Scan the QRCode provided by the event host. + + + ); }; -export default QRCodeScanningScreen; \ No newline at end of file +export default QRCodeScanningScreen; From 94b67d25b7b6acd5660e9fe72fe3544a6eaed581 Mon Sep 17 00:00:00 2001 From: Jason Date: Fri, 13 Sep 2024 19:14:26 -0500 Subject: [PATCH 02/15] remove filters --- src/screens/committees/CommitteeInfo.tsx | 6 - src/screens/events/Events.tsx | 213 ++--------------------- src/screens/home/Home.tsx | 6 - 3 files changed, 12 insertions(+), 213 deletions(-) diff --git a/src/screens/committees/CommitteeInfo.tsx b/src/screens/committees/CommitteeInfo.tsx index 6f3cec89..136b4d46 100644 --- a/src/screens/committees/CommitteeInfo.tsx +++ b/src/screens/committees/CommitteeInfo.tsx @@ -348,12 +348,6 @@ const CommitteeInfo: React.FC = ({ route, navigat Upcoming Events - navigation.getParent()?.navigate('EventsTab', { screen: 'EventsScreen', params: { filter: EventType.COMMITTEE_MEETING, committee: firebaseDocName } })} - > - View all - { const userContext = useContext(UserContext); const { userInfo, setUserInfo } = userContext!; - const fixDarkMode = userInfo?.private?.privateInfo?.settings?.darkMode; const useSystemDefault = userInfo?.private?.privateInfo?.settings?.useSystemDefault; const colorScheme = useColorScheme(); const darkMode = useSystemDefault ? colorScheme === 'dark' : fixDarkMode; - const filterScrollViewRef = useRef(null); - const committeeScrollViewRef = useRef(null); const [isLoading, setIsLoading] = useState(true); const [todayEvents, setTodayEvents] = useState([]); const [upcomingEvents, setUpcomingEvents] = useState([]); const [pastEvents, setPastEvents] = useState([]); - const [selectedFilter, setSelectedFilter] = useState(route.params?.filter || null); const [committees, setCommittees] = useState([]); const [selectedCommittee, setSelectedCommittee] = useState(route.params?.committee || null); const [infoVisible, setInfoVisible] = useState(false); @@ -49,7 +45,7 @@ const Events = ({ navigation }: EventsProps) => { setIsLoading(true); const upcomingEventsData = await getUpcomingEvents(); - const pastEventsData = await getPastEvents(3, null); + const pastEventsData = await getPastEvents(10, null); const currentTime = new Date(); const today = new Date(currentTime.getFullYear(), currentTime.getMonth(), currentTime.getDate()); @@ -92,27 +88,6 @@ const Events = ({ navigation }: EventsProps) => { fetchCommittees(); }, []) - useEffect(() => { - if (route.params?.filter !== undefined) { - setSelectedFilter(route.params.filter); - - const filterIndex = ["myEvents", "clubWide", ...Object.values(EventType)].indexOf(route.params.filter); - if (filterIndex !== -1 && filterScrollViewRef.current) { - const scrollPosition = filterIndex * 115; - filterScrollViewRef.current.scrollTo({ x: scrollPosition, animated: true }); - } - } - - if (route.params?.committee !== undefined) { - setSelectedCommittee(route.params.committee); - - const committeeIndex = committees.findIndex(committee => committee.firebaseDocName === route.params.committee); - if (committeeIndex !== -1 && committeeScrollViewRef.current) { - const scrollPosition = committeeIndex * 100; - committeeScrollViewRef.current.scrollTo({ x: scrollPosition, animated: true }); - } - } - }, [route.params, committees]); useFocusEffect( useCallback(() => { @@ -122,75 +97,6 @@ const Events = ({ navigation }: EventsProps) => { }, [hasPrivileges]) ); - const handleFilterSelect = (filter?: ExtendedEventType, committee?: string) => { - // Deselect committee when the same committee is selected - if (committee) { - if (selectedCommittee === committee) { - setSelectedCommittee(null); - if (!selectedFilter) { - setSelectedFilter(null); - } - } else { - setSelectedCommittee(committee); - if (selectedFilter !== EventType.COMMITTEE_MEETING) { - setSelectedFilter(EventType.COMMITTEE_MEETING); - } - } - return; - } - - // Deselect "Committee Meetings" when no committee is selected - if (filter === EventType.COMMITTEE_MEETING && selectedFilter === EventType.COMMITTEE_MEETING) { - setSelectedFilter(null); - setSelectedCommittee(null); - return; - } - - // Handle other filters - if (selectedFilter === filter) { - setSelectedFilter(null); - setSelectedCommittee(null); - } else { - setSelectedFilter(filter!); - setSelectedCommittee(null); - } - }; - - const filteredEvents = (events: SHPEEvent[]): SHPEEvent[] => { - // If no filter is selected, filter out hidden events and committee meetings - if (!selectedFilter) { - return events.filter(event => - (event.eventType !== EventType.COMMITTEE_MEETING || event.general) && - !event.hiddenEvent - ); - } - - if (selectedFilter === 'myEvents') { - return events.filter(event => - (userInfo?.publicInfo?.committees?.includes(event.committee || '') || - userInfo?.publicInfo?.interests?.includes(event.eventType || '')) && - !event.hiddenEvent - ); - } - - if (selectedFilter === 'clubWide') { - return events.filter(event => event.general && !event.hiddenEvent); - } - // Show hidden events for "Custom Event" filter - if (selectedFilter === 'Custom Event') { - return events.filter(event => event.eventType === selectedFilter); - } - - if (selectedFilter === EventType.COMMITTEE_MEETING) { - return events.filter(event => - event.eventType === EventType.COMMITTEE_MEETING && - (selectedCommittee ? event.committee === selectedCommittee : true) && - !event.hiddenEvent - ); - } - - return events.filter(event => event.eventType === selectedFilter && !event.hiddenEvent); - }; return ( @@ -204,100 +110,6 @@ const Events = ({ navigation }: EventsProps) => { - {/* Filters */} - - - handleFilterSelect("myEvents")} - > - My Events - - - handleFilterSelect("clubWide")} - > - Club Wide - - - {Object.values(EventType).map((type) => ( - handleFilterSelect(type)} - > - {type} - - ))} - - - - {/* Additional Committee Filter */} - {selectedFilter === EventType.COMMITTEE_MEETING && ( - - - {committees.map(committee => ( - handleFilterSelect(undefined, committee.firebaseDocName)} - > - {committee.name} - - ))} - - - )} {isLoading && @@ -308,17 +120,17 @@ const Events = ({ navigation }: EventsProps) => { {/* Event Listings */} {!isLoading && ( - {filteredEvents(todayEvents).length === 0 && filteredEvents(upcomingEvents).length === 0 && filteredEvents(pastEvents).length === 0 ? ( + {todayEvents.length === 0 && upcomingEvents.length === 0 && pastEvents.length === 0 ? ( No Events ) : ( {/* Today's Events */} - {filteredEvents(todayEvents).length !== 0 && ( + {todayEvents.length !== 0 && ( Today's Events - {filteredEvents(todayEvents)?.map((event: SHPEEvent, index) => { + {todayEvents?.map((event: SHPEEvent, index) => { return ( { )} {/* Upcoming Events */} - {filteredEvents(upcomingEvents).length !== 0 && ( + {upcomingEvents.length !== 0 && ( Upcoming Events - {filteredEvents(upcomingEvents)?.map((event: SHPEEvent, index) => { + {upcomingEvents?.map((event: SHPEEvent, index) => { return ( 0 && "mt-8"}`}> @@ -384,10 +196,10 @@ const Events = ({ navigation }: EventsProps) => { )} {/* Past Events */} - {filteredEvents(pastEvents).length !== 0 && ( + {pastEvents.length !== 0 && ( Past Events - {filteredEvents(pastEvents)?.map((event: SHPEEvent, index) => { + {pastEvents?.map((event: SHPEEvent, index) => { return ( 0 && "mt-8"}`}> @@ -396,11 +208,10 @@ const Events = ({ navigation }: EventsProps) => { })} )} - {!selectedFilter && ( - navigation.navigate("PastEvents")}> - View more - - )} + navigation.navigate("PastEvents")}> + View more + + )} diff --git a/src/screens/home/Home.tsx b/src/screens/home/Home.tsx index 7f9ba2b7..a9379cd6 100644 --- a/src/screens/home/Home.tsx +++ b/src/screens/home/Home.tsx @@ -381,12 +381,6 @@ const Home = ({ navigation, route }: NativeStackScreenProps) => - navigation.getParent()?.navigate('EventsTab', { screen: 'EventsScreen', params: { filter: 'myEvents' } })} - > - View all - From b87b94a324ad32760d50a25c7a981b24b52d62af Mon Sep 17 00:00:00 2001 From: Jason Date: Sat, 14 Sep 2024 12:57:06 -0500 Subject: [PATCH 03/15] tempt remove zoom --- src/screens/events/QRCodeScanningScreen.tsx | 108 ++++++++------------ 1 file changed, 40 insertions(+), 68 deletions(-) diff --git a/src/screens/events/QRCodeScanningScreen.tsx b/src/screens/events/QRCodeScanningScreen.tsx index 9287c570..f7d17404 100644 --- a/src/screens/events/QRCodeScanningScreen.tsx +++ b/src/screens/events/QRCodeScanningScreen.tsx @@ -10,11 +10,8 @@ import { NativeStackScreenProps } from '@react-navigation/native-stack'; const QRCodeScanningScreen = ({ navigation }: NativeStackScreenProps) => { const [hasCameraPermissions, setHasCameraPermissions] = useState(null); const pulseAnim = useRef(new Animated.Value(1)).current; - const [zoom, setZoom] = useState(0); const [qrData, setQrData] = useState<{ id: string; mode: string } | null>(null); const cameraRef = useRef(null); - const lastScale = useRef(1); - const maxZoom = 0.05; const timeoutRef = useRef(null); useEffect(() => { @@ -85,22 +82,6 @@ const QRCodeScanningScreen = ({ navigation }: NativeStackScreenProps { - if (nativeEvent.scale !== 0) { - const baseZoomFactor = 0.0004; - const zoomSensitivity = 0.1; - const dynamicFactor = zoom * zoomSensitivity + baseZoomFactor; - const scaleChange = (nativeEvent.scale - 1) * dynamicFactor * (nativeEvent.scale > 1 ? 1 : 2); - const newZoom = Math.min(Math.max(zoom + scaleChange, 0), maxZoom); - setZoom(newZoom); - } - }; - - const handlePinchStateChange = ({ nativeEvent }: PinchGestureHandlerGestureEvent) => { - if (nativeEvent.state === State.END || nativeEvent.state === State.CANCELLED) { - lastScale.current = 1; - } - }; if (hasCameraPermissions === null) { return Requesting for camera permission; @@ -122,58 +103,49 @@ const QRCodeScanningScreen = ({ navigation }: NativeStackScreenProps - - - {/* Pulsing Effect */} - - - - - - - - - - + {/* Pulsing Effect */} + + + + + + + + + + - - {(1 + zoom * 100).toFixed(1)}x + {/* Button for Sign In/Sign Out */} + {qrData && ( + + + + {qrData.mode === 'sign-in' ? 'Sign in to the event' : 'Sign out of the event'} + + - - {/* Button for Sign In/Sign Out */} - {qrData && ( - - - - {qrData.mode === 'sign-in' ? 'Sign in to the event' : 'Sign out of the event'} - - - - )} - - + )} + Using Scanner From bcc443a7be5f2cee6cbcd4e9a6f3a8ffbab28af2 Mon Sep 17 00:00:00 2001 From: Jason Date: Sat, 14 Sep 2024 14:23:42 -0500 Subject: [PATCH 04/15] remove manual button click and add imrpove ui feedback --- src/screens/events/QRCodeScanningScreen.tsx | 215 +++++++++++--------- 1 file changed, 117 insertions(+), 98 deletions(-) diff --git a/src/screens/events/QRCodeScanningScreen.tsx b/src/screens/events/QRCodeScanningScreen.tsx index f7d17404..89589545 100644 --- a/src/screens/events/QRCodeScanningScreen.tsx +++ b/src/screens/events/QRCodeScanningScreen.tsx @@ -1,18 +1,31 @@ -import { View, Text, TouchableOpacity, Alert, Animated, Easing } from 'react-native'; +import { View, Text, TouchableOpacity, Animated, Easing, Dimensions } from 'react-native'; import React, { useEffect, useRef, useState } from 'react'; import { CameraView, Camera } from 'expo-camera'; import { SafeAreaView } from 'react-native-safe-area-context'; -import { GestureHandlerRootView, PinchGestureHandler, PinchGestureHandlerGestureEvent, State } from 'react-native-gesture-handler'; -import { MainStackParams } from '../../types/navigation'; import { Octicons } from '@expo/vector-icons'; import { NativeStackScreenProps } from '@react-navigation/native-stack'; +import { MainStackParams } from '../../types/navigation'; + +const { width: screenWidth, height: screenHeight } = Dimensions.get('window'); + +type BarCodeScannedResult = { + type: string; + data: string; + bounds?: { + origin: { x: number; y: number }; + size: { width: number; height: number }; + }; +}; const QRCodeScanningScreen = ({ navigation }: NativeStackScreenProps) => { const [hasCameraPermissions, setHasCameraPermissions] = useState(null); + const [boxColor, setBoxColor] = useState('#FFFFFF'); + const [validScanned, setValidScanned] = useState(false); const pulseAnim = useRef(new Animated.Value(1)).current; - const [qrData, setQrData] = useState<{ id: string; mode: string } | null>(null); - const cameraRef = useRef(null); - const timeoutRef = useRef(null); + const boxTop = useRef(new Animated.Value((screenHeight / 2) - 240)).current; + const boxLeft = useRef(new Animated.Value((screenWidth / 2) - 120)).current; + const boxWidth = useRef(new Animated.Value(240)).current; + const boxHeight = useRef(new Animated.Value(240)).current; useEffect(() => { const getBarCodeScannerPermissions = async () => { @@ -30,13 +43,13 @@ const QRCodeScanningScreen = ({ navigation }: NativeStackScreenProps pulse()); }; @@ -44,45 +57,57 @@ const QRCodeScanningScreen = ({ navigation }: NativeStackScreenProps { - const dataRegex: RegExp = /^tamu-shpe:\/\/event\?id=[a-zA-z0-9]+&mode=(sign-in|sign-out)$/i; - - if (dataRegex.test(data)) { - const linkVariables = data.split('?')[1].split('&'); - const id = linkVariables[0].split('=')[1]; - const mode = linkVariables[1].split('=')[1]; - if (id && (mode === 'sign-in' || mode === 'sign-out')) { - setQrData({ id, mode }); - - // Clear any existing timeout when valid data is found - if (timeoutRef.current) { - clearTimeout(timeoutRef.current); - } - - // Set a timeout to clear qrData after 3 seconds if no new valid QR codes are detected - timeoutRef.current = setTimeout(() => { - setQrData(null); - }, 5000); - } + const handleBarCodeScanned = ({ bounds, type, data }: BarCodeScannedResult) => { + if (validScanned) { + return; } - }; - useEffect(() => { - return () => { - if (timeoutRef.current) { - clearTimeout(timeoutRef.current); + const dataRegex = /^tamu-shpe:\/\/event\?id=[a-zA-Z0-9]+&mode=(sign-in|sign-out)$/i; + if (dataRegex.test(data)) { + setValidScanned(true); + console.log('Data Received', `Bar code with type ${type} and data ${data} has been scanned!`); + if (bounds) { + setBoxColor('#FD652F'); + + Animated.parallel([ + Animated.timing(boxTop, { + toValue: bounds.origin.y, + duration: 90, + easing: Easing.inOut(Easing.ease), + useNativeDriver: false, + }), + Animated.timing(boxLeft, { + toValue: bounds.origin.x, + duration: 90, + easing: Easing.inOut(Easing.ease), + useNativeDriver: false, + }), + Animated.timing(boxWidth, { + toValue: bounds.size.width, + duration: 90, + easing: Easing.inOut(Easing.ease), + useNativeDriver: false, + }), + Animated.timing(boxHeight, { + toValue: bounds.size.height, + duration: 90, + easing: Easing.inOut(Easing.ease), + useNativeDriver: false, + }), + ]).start(); } - }; - }, []); - const handleConfirm = () => { - if (qrData) { - navigation.navigate("EventVerificationScreen", { id: qrData.id, mode: qrData.mode as "sign-in" | "sign-out" }); - setQrData(null); + setTimeout(() => { + const linkVariables = data.split('?')[1].split('&'); + const id = linkVariables[0].split('=')[1]; + const mode = linkVariables[1].split('=')[1]; + if (id && (mode === 'sign-in' || mode === 'sign-out')) { + navigation.navigate('EventVerificationScreen', { id, mode }); + } + }, 500); } }; - if (hasCameraPermissions === null) { return Requesting for camera permission; } @@ -91,68 +116,62 @@ const QRCodeScanningScreen = ({ navigation }: NativeStackScreenProps - - {/* Header */} - - - Scanner - - navigation.goBack()}> - - + + + + Scanner - - - navigation.goBack()}> + + + + + + {/* Pulsing Effect with Animated Transition */} + - {/* Pulsing Effect */} - - - - - - - - - + + + + + - - {/* Button for Sign In/Sign Out */} - {qrData && ( - - - - {qrData.mode === 'sign-in' ? 'Sign in to the event' : 'Sign out of the event'} - - - - )} - - - - Using Scanner - Scan the QRCode provided by the event host. - - - + + + + + Using Scanner + Scan the QRCode provided by the event host. + + ); }; From fe5f2dcfda22c7e849411ebbc4d62170ecdc6a28 Mon Sep 17 00:00:00 2001 From: Jason Date: Sat, 14 Sep 2024 14:37:59 -0500 Subject: [PATCH 05/15] fix typing --- src/screens/events/QRCodeScanningScreen.tsx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/screens/events/QRCodeScanningScreen.tsx b/src/screens/events/QRCodeScanningScreen.tsx index 89589545..4b2328ec 100644 --- a/src/screens/events/QRCodeScanningScreen.tsx +++ b/src/screens/events/QRCodeScanningScreen.tsx @@ -1,6 +1,6 @@ import { View, Text, TouchableOpacity, Animated, Easing, Dimensions } from 'react-native'; import React, { useEffect, useRef, useState } from 'react'; -import { CameraView, Camera } from 'expo-camera'; +import { CameraView, Camera, BarcodeBounds } from 'expo-camera'; import { SafeAreaView } from 'react-native-safe-area-context'; import { Octicons } from '@expo/vector-icons'; import { NativeStackScreenProps } from '@react-navigation/native-stack'; @@ -11,10 +11,7 @@ const { width: screenWidth, height: screenHeight } = Dimensions.get('window'); type BarCodeScannedResult = { type: string; data: string; - bounds?: { - origin: { x: number; y: number }; - size: { width: number; height: number }; - }; + bounds?: BarcodeBounds }; const QRCodeScanningScreen = ({ navigation }: NativeStackScreenProps) => { From 278ff437c4861b2f3967d3bd38b9455fda6e1b8a Mon Sep 17 00:00:00 2001 From: Jason Date: Sat, 14 Sep 2024 14:56:13 -0500 Subject: [PATCH 06/15] add zoom again --- src/screens/events/QRCodeScanningScreen.tsx | 148 ++++++++++++-------- 1 file changed, 93 insertions(+), 55 deletions(-) diff --git a/src/screens/events/QRCodeScanningScreen.tsx b/src/screens/events/QRCodeScanningScreen.tsx index 4b2328ec..a6f3034a 100644 --- a/src/screens/events/QRCodeScanningScreen.tsx +++ b/src/screens/events/QRCodeScanningScreen.tsx @@ -1,12 +1,14 @@ -import { View, Text, TouchableOpacity, Animated, Easing, Dimensions } from 'react-native'; +import { View, Text, TouchableOpacity, Animated, Easing, Dimensions, PixelRatio } from 'react-native'; import React, { useEffect, useRef, useState } from 'react'; import { CameraView, Camera, BarcodeBounds } from 'expo-camera'; import { SafeAreaView } from 'react-native-safe-area-context'; import { Octicons } from '@expo/vector-icons'; import { NativeStackScreenProps } from '@react-navigation/native-stack'; import { MainStackParams } from '../../types/navigation'; +import { GestureHandlerRootView, PinchGestureHandler, PinchGestureHandlerGestureEvent, State } from 'react-native-gesture-handler'; const { width: screenWidth, height: screenHeight } = Dimensions.get('window'); +const screenDensity = PixelRatio.get(); type BarCodeScannedResult = { type: string; @@ -18,11 +20,15 @@ const QRCodeScanningScreen = ({ navigation }: NativeStackScreenProps(null); const [boxColor, setBoxColor] = useState('#FFFFFF'); const [validScanned, setValidScanned] = useState(false); + const [zoom, setZoom] = useState(0); + const pulseAnim = useRef(new Animated.Value(1)).current; const boxTop = useRef(new Animated.Value((screenHeight / 2) - 240)).current; const boxLeft = useRef(new Animated.Value((screenWidth / 2) - 120)).current; const boxWidth = useRef(new Animated.Value(240)).current; const boxHeight = useRef(new Animated.Value(240)).current; + const lastScale = useRef(1); + useEffect(() => { const getBarCodeScannerPermissions = async () => { @@ -54,6 +60,26 @@ const QRCodeScanningScreen = ({ navigation }: NativeStackScreenProps { + if (nativeEvent.scale !== 0) { + const baseZoomFactor = 0.0004 * screenDensity; + const zoomSensitivity = 0.1; + const dynamicFactor = zoom * zoomSensitivity + baseZoomFactor; + const scaleChange = (nativeEvent.scale - 1) * dynamicFactor * (nativeEvent.scale > 1 ? 1 : 2); + + const maxZoom = 0.05; + const newZoom = Math.min(Math.max(zoom + scaleChange, 0), maxZoom); + + setZoom(newZoom); + } + }; + + const handlePinchStateChange = ({ nativeEvent }: any) => { + if (nativeEvent.state === State.END || nativeEvent.state === State.CANCELLED) { + lastScale.current = 1; + } + }; + const handleBarCodeScanned = ({ bounds, type, data }: BarCodeScannedResult) => { if (validScanned) { return; @@ -113,62 +139,74 @@ const QRCodeScanningScreen = ({ navigation }: NativeStackScreenProps - - - Scanner + + + + + Scanner + + navigation.goBack()}> + + - navigation.goBack()}> - - - - - - {/* Pulsing Effect with Animated Transition */} - - - - - - - - - - - - Using Scanner - Scan the QRCode provided by the event host. - - + + + {/* Pulsing Effect with Animated Transition */} + + + + + + + + + + + {(1 + zoom * 100).toFixed(1)}x + + + + + + Using Scanner + Scan the QRCode provided by the event host. + + + ); }; From 8fd9c5310e8fc332723bbc05caca58a2ac8eecd4 Mon Sep 17 00:00:00 2001 From: Jason Date: Sat, 14 Sep 2024 15:07:15 -0500 Subject: [PATCH 07/15] simple ui fix for motm card --- src/components/MOTMCard.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/MOTMCard.tsx b/src/components/MOTMCard.tsx index c9735e15..24314dcc 100644 --- a/src/components/MOTMCard.tsx +++ b/src/components/MOTMCard.tsx @@ -68,6 +68,9 @@ const MOTMCard: React.FC = ({ navigation }) => { }, [currentUser]) ); + if (!MOTM) { + return null; + } return ( From b569b8278451acb6df9554522ccea4d6904040ea Mon Sep 17 00:00:00 2001 From: Jason Date: Sat, 14 Sep 2024 15:28:09 -0500 Subject: [PATCH 08/15] add filter ui and temp remove fetch committee data --- src/screens/events/Events.tsx | 70 ++++++++++++++++++++++++++++------- 1 file changed, 56 insertions(+), 14 deletions(-) diff --git a/src/screens/events/Events.tsx b/src/screens/events/Events.tsx index 1965a70b..029985bc 100644 --- a/src/screens/events/Events.tsx +++ b/src/screens/events/Events.tsx @@ -1,20 +1,18 @@ import { View, Text, TouchableOpacity, ActivityIndicator, ScrollView, Image, useColorScheme } from 'react-native' -import React, { useCallback, useContext, useEffect, useRef, useState } from 'react' +import React, { useCallback, useContext, useEffect, useState } from 'react' import { SafeAreaView } from 'react-native-safe-area-context'; import { NativeStackNavigationProp } from '@react-navigation/native-stack'; -import AsyncStorage from '@react-native-async-storage/async-storage'; import { RouteProp, useFocusEffect, useRoute } from '@react-navigation/core'; import { Octicons, FontAwesome6 } from '@expo/vector-icons'; import { LinearGradient } from 'expo-linear-gradient'; import { StatusBar } from 'expo-status-bar'; -import { auth } from '../../config/firebaseConfig'; import { getUpcomingEvents, getPastEvents, getCommittees, fetchAndStoreUser } from '../../api/firebaseUtils'; import { UserContext } from '../../context/UserContext'; import { Images } from '../../../assets'; import { formatTime } from '../../helpers/timeUtils'; import { truncateStringWithEllipsis } from '../../helpers/stringUtils'; import { EventsStackParams } from '../../types/navigation'; -import { EventType, ExtendedEventType, SHPEEvent } from '../../types/events'; +import { ExtendedEventType, SHPEEvent } from '../../types/events'; import EventCard from './EventCard'; import { Committee } from '../../types/committees'; import DismissibleModal from '../../components/DismissibleModal'; @@ -34,9 +32,8 @@ const Events = ({ navigation }: EventsProps) => { const [todayEvents, setTodayEvents] = useState([]); const [upcomingEvents, setUpcomingEvents] = useState([]); const [pastEvents, setPastEvents] = useState([]); - const [committees, setCommittees] = useState([]); - const [selectedCommittee, setSelectedCommittee] = useState(route.params?.committee || null); const [infoVisible, setInfoVisible] = useState(false); + const [filter, setFilter] = useState<"main" | "intramural" | "committee">("main"); const hasPrivileges = (userInfo?.publicInfo?.roles?.admin?.valueOf() || userInfo?.publicInfo?.roles?.officer?.valueOf() || userInfo?.publicInfo?.roles?.developer?.valueOf() || userInfo?.publicInfo?.roles?.lead?.valueOf() || userInfo?.publicInfo?.roles?.representative?.valueOf()); @@ -69,12 +66,6 @@ const Events = ({ navigation }: EventsProps) => { } }; - const fetchCommittees = async () => { - const committeeData = await getCommittees(); - setCommittees(committeeData); - }; - - useEffect(() => { const fetchUserData = async () => { const firebaseUser = await fetchAndStoreUser(); @@ -85,7 +76,6 @@ const Events = ({ navigation }: EventsProps) => { fetchEvents(); fetchUserData(); - fetchCommittees(); }, []) @@ -110,6 +100,58 @@ const Events = ({ navigation }: EventsProps) => { + {/* Filters */} + + { + setFilter("main") + }} + > + + Main + + + + { + setFilter("intramural") + }} + > + + Intramural + + + { + setFilter("committee") + }} + > + + Committee + + + {isLoading && @@ -275,7 +317,7 @@ const Events = ({ navigation }: EventsProps) => { Event Location Check - The location check only happens during scans; we do not track you continuously. You are free to leave the area, but if a sign-out scan is required, you must be at the location to sign out. + The location check only happens during scans; we do not track you continuously. If a sign-out scan is required, you must be at the location to sign out. From 16c49879993b5956fb9dc25bdd5531015adfd264 Mon Sep 17 00:00:00 2001 From: Jason Date: Sat, 14 Sep 2024 16:09:22 -0500 Subject: [PATCH 09/15] update event ui --- src/api/firebaseUtils.ts | 23 +++ src/screens/events/Events.tsx | 257 +++++++++++++++++++++++----------- 2 files changed, 196 insertions(+), 84 deletions(-) diff --git a/src/api/firebaseUtils.ts b/src/api/firebaseUtils.ts index 18648bcf..21942aff 100644 --- a/src/api/firebaseUtils.ts +++ b/src/api/firebaseUtils.ts @@ -528,6 +528,29 @@ export const getUpcomingEvents = async () => { return events; }; +export const getWeekPastEvents = async (): Promise => { + const currentTime = new Date(); + const twoWeeksAgo = new Date(currentTime); + twoWeeksAgo.setDate(currentTime.getDate() - 8); + + const eventsRef = collection(db, "events"); + const q = query( + eventsRef, + where("endTime", "<", currentTime), + where("endTime", ">", twoWeeksAgo), + orderBy("endTime", "desc") + ); + + const querySnapshot = await getDocs(q); + const events: SHPEEvent[] = []; + + querySnapshot.forEach(doc => { + events.push({ id: doc.id, ...doc.data() } as SHPEEvent); + }); + + return events; +}; + export const getPastEvents = async (numLimit: number, startAfterDoc: any, setEndOfData?: (endOfData: boolean) => void) => { const currentTime = new Date(); const eventsRef = collection(db, "events"); diff --git a/src/screens/events/Events.tsx b/src/screens/events/Events.tsx index 029985bc..cb27d99b 100644 --- a/src/screens/events/Events.tsx +++ b/src/screens/events/Events.tsx @@ -6,17 +6,24 @@ import { RouteProp, useFocusEffect, useRoute } from '@react-navigation/core'; import { Octicons, FontAwesome6 } from '@expo/vector-icons'; import { LinearGradient } from 'expo-linear-gradient'; import { StatusBar } from 'expo-status-bar'; -import { getUpcomingEvents, getPastEvents, getCommittees, fetchAndStoreUser } from '../../api/firebaseUtils'; +import { getUpcomingEvents, getPastEvents, getCommittees, fetchAndStoreUser, getWeekPastEvents } from '../../api/firebaseUtils'; import { UserContext } from '../../context/UserContext'; import { Images } from '../../../assets'; import { formatTime } from '../../helpers/timeUtils'; import { truncateStringWithEllipsis } from '../../helpers/stringUtils'; import { EventsStackParams } from '../../types/navigation'; -import { ExtendedEventType, SHPEEvent } from '../../types/events'; +import { EventType, ExtendedEventType, SHPEEvent } from '../../types/events'; import EventCard from './EventCard'; import { Committee } from '../../types/committees'; import DismissibleModal from '../../components/DismissibleModal'; +interface EventGroups { + today: SHPEEvent[]; + upcoming: SHPEEvent[]; + past: SHPEEvent[]; +} + + const Events = ({ navigation }: EventsProps) => { const route = useRoute(); const userContext = useContext(UserContext); @@ -27,25 +34,39 @@ const Events = ({ navigation }: EventsProps) => { const colorScheme = useColorScheme(); const darkMode = useSystemDefault ? colorScheme === 'dark' : fixDarkMode; - const [isLoading, setIsLoading] = useState(true); - const [todayEvents, setTodayEvents] = useState([]); - const [upcomingEvents, setUpcomingEvents] = useState([]); - const [pastEvents, setPastEvents] = useState([]); + const [mainEvents, setMainEvents] = useState({ + today: [], + upcoming: [], + past: [], + }); + const [intramuralEvents, setIntramuralEvents] = useState({ + today: [], + upcoming: [], + past: [], + }); + const [committeeEvents, setCommitteeEvents] = useState({ + today: [], + upcoming: [], + past: [], + }); const [infoVisible, setInfoVisible] = useState(false); const [filter, setFilter] = useState<"main" | "intramural" | "committee">("main"); const hasPrivileges = (userInfo?.publicInfo?.roles?.admin?.valueOf() || userInfo?.publicInfo?.roles?.officer?.valueOf() || userInfo?.publicInfo?.roles?.developer?.valueOf() || userInfo?.publicInfo?.roles?.lead?.valueOf() || userInfo?.publicInfo?.roles?.representative?.valueOf()); + const selectedEvents = filter === "main" ? mainEvents : filter === "intramural" ? intramuralEvents : committeeEvents; + const fetchEvents = async () => { try { setIsLoading(true); const upcomingEventsData = await getUpcomingEvents(); - const pastEventsData = await getPastEvents(10, null); const currentTime = new Date(); const today = new Date(currentTime.getFullYear(), currentTime.getMonth(), currentTime.getDate()); + + // Filter today's and upcoming events const todayEvents = upcomingEventsData.filter(event => { const startTime = event.startTime ? event.startTime.toDate() : new Date(0); return startTime >= today && startTime < new Date(today.getTime() + 24 * 60 * 60 * 1000); @@ -55,9 +76,65 @@ const Events = ({ navigation }: EventsProps) => { return startTime >= new Date(today.getTime() + 24 * 60 * 60 * 1000); }); - setTodayEvents(todayEvents); - setUpcomingEvents(upcomingEvents); - setPastEvents(pastEventsData.events); + const mainEventsFiltered = upcomingEventsData.filter( + (event: SHPEEvent) => + !event.hiddenEvent && + event.eventType !== EventType.COMMITTEE_MEETING && + event.eventType !== EventType.INTRAMURAL_EVENT + ); + + const intramuralEventsFiltered = upcomingEventsData.filter( + (event: SHPEEvent) => + !event.hiddenEvent && + event.eventType === EventType.INTRAMURAL_EVENT + ); + + const committeeEventsFiltered = upcomingEventsData.filter( + (event: SHPEEvent) => + !event.hiddenEvent && + event.eventType === EventType.COMMITTEE_MEETING + ); + + + const allPastEvents = await getWeekPastEvents(); + + const pastMainEvents = allPastEvents.filter( + (event: SHPEEvent) => + !event.hiddenEvent && + event.eventType !== EventType.COMMITTEE_MEETING && + event.eventType !== EventType.INTRAMURAL_EVENT + ); + + const pastIntramuralEvents = allPastEvents.filter( + (event: SHPEEvent) => + !event.hiddenEvent && + event.eventType === EventType.INTRAMURAL_EVENT + ); + + const pastCommitteeEvents = allPastEvents.filter( + (event: SHPEEvent) => + !event.hiddenEvent && + event.eventType === EventType.COMMITTEE_MEETING + ); + + + setMainEvents({ + today: todayEvents.filter(event => mainEventsFiltered.includes(event)), + upcoming: upcomingEvents.filter(event => mainEventsFiltered.includes(event)), + past: pastMainEvents, + }); + + setIntramuralEvents({ + today: todayEvents.filter(event => intramuralEventsFiltered.includes(event)), + upcoming: upcomingEvents.filter(event => intramuralEventsFiltered.includes(event)), + past: pastIntramuralEvents, + }); + + setCommitteeEvents({ + today: todayEvents.filter(event => committeeEventsFiltered.includes(event)), + upcoming: upcomingEvents.filter(event => committeeEventsFiltered.includes(event)), + past: pastCommitteeEvents, + }); setIsLoading(false); } catch (error) { @@ -66,6 +143,7 @@ const Events = ({ navigation }: EventsProps) => { } }; + useEffect(() => { const fetchUserData = async () => { const firebaseUser = await fetchAndStoreUser(); @@ -102,7 +180,7 @@ const Events = ({ navigation }: EventsProps) => { {/* Filters */} { {/* Event Listings */} {!isLoading && ( - - {todayEvents.length === 0 && upcomingEvents.length === 0 && pastEvents.length === 0 ? ( - + + {selectedEvents.today.length === 0 && + selectedEvents.upcoming.length === 0 && + selectedEvents.past.length === 0 ? ( + No Events ) : ( {/* Today's Events */} - {todayEvents.length !== 0 && ( - + {selectedEvents.today.length !== 0 && ( + Today's Events - {todayEvents?.map((event: SHPEEvent, index) => { - return ( - 0 && "mt-8"}`} - style={{ - shadowColor: "#000", - shadowOffset: { - width: 0, - height: 2, - }, - shadowOpacity: 0.25, - shadowRadius: 3.84, - - elevation: 5, - }} - onPress={() => { navigation.navigate("EventInfo", { event: event }) }} + {selectedEvents.today.map((event: SHPEEvent, index) => ( + 0 && "mt-8"}`} + style={{ + shadowColor: "#000", + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.25, + shadowRadius: 3.84, + elevation: 5, + }} + onPress={() => { + navigation.navigate("EventInfo", { event: event }); + }} + > + + - - + + {truncateStringWithEllipsis(event.name, 20)} + + {event.locationName ? ( + + {truncateStringWithEllipsis(event.locationName, 24)} + + ) : null} + + {formatTime(event.startTime?.toDate()!)} + + + + {hasPrivileges && ( + { + navigation.navigate("QRCode", { event: event }); + }} + className="absolute right-0 top-0 p-2 m-2 rounded-full" + style={{ backgroundColor: "rgba(0,0,0,0.7)" }} > - - {truncateStringWithEllipsis(event.name, 20)} - {event.locationName ? ( - {truncateStringWithEllipsis(event.locationName, 24)} - ) : null} - {formatTime(event.startTime?.toDate()!)} - - - {hasPrivileges && ( - { navigation.navigate("QRCode", { event: event }) }} - className='absolute right-0 top-0 p-2 m-2 rounded-full' - style={{ backgroundColor: 'rgba(0,0,0,0.7)' }} - > - - - )} - - ); - })} + + + )} + + ))} )} {/* Upcoming Events */} - {upcomingEvents.length !== 0 && ( - - Upcoming Events - {upcomingEvents?.map((event: SHPEEvent, index) => { - return ( - 0 && "mt-8"}`}> - - - ); - })} + {selectedEvents.upcoming.length !== 0 && ( + + + Upcoming Events + + {selectedEvents.upcoming.map((event: SHPEEvent, index) => ( + 0 && "mt-8"}`}> + + + ))} )} {/* Past Events */} - {pastEvents.length !== 0 && ( - - Past Events - {pastEvents?.map((event: SHPEEvent, index) => { - return ( - 0 && "mt-8"}`}> - - - ); - })} + {selectedEvents.past.length !== 0 && ( + + + Past Events + + {selectedEvents.past.map((event: SHPEEvent, index) => ( + 0 && "mt-8"}`}> + + + ))} )} navigation.navigate("PastEvents")}> - View more + View all past events - )} From 8d284677d91fa68f66cd22bf24d4a2c37a897295 Mon Sep 17 00:00:00 2001 From: Jason Date: Sat, 14 Sep 2024 16:29:57 -0500 Subject: [PATCH 10/15] clean up code --- src/screens/events/Events.tsx | 25 +++++-------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/src/screens/events/Events.tsx b/src/screens/events/Events.tsx index cb27d99b..1d251cf2 100644 --- a/src/screens/events/Events.tsx +++ b/src/screens/events/Events.tsx @@ -6,7 +6,7 @@ import { RouteProp, useFocusEffect, useRoute } from '@react-navigation/core'; import { Octicons, FontAwesome6 } from '@expo/vector-icons'; import { LinearGradient } from 'expo-linear-gradient'; import { StatusBar } from 'expo-status-bar'; -import { getUpcomingEvents, getPastEvents, getCommittees, fetchAndStoreUser, getWeekPastEvents } from '../../api/firebaseUtils'; +import { getUpcomingEvents, fetchAndStoreUser, getWeekPastEvents } from '../../api/firebaseUtils'; import { UserContext } from '../../context/UserContext'; import { Images } from '../../../assets'; import { formatTime } from '../../helpers/timeUtils'; @@ -14,7 +14,6 @@ import { truncateStringWithEllipsis } from '../../helpers/stringUtils'; import { EventsStackParams } from '../../types/navigation'; import { EventType, ExtendedEventType, SHPEEvent } from '../../types/events'; import EventCard from './EventCard'; -import { Committee } from '../../types/committees'; import DismissibleModal from '../../components/DismissibleModal'; interface EventGroups { @@ -35,21 +34,9 @@ const Events = ({ navigation }: EventsProps) => { const darkMode = useSystemDefault ? colorScheme === 'dark' : fixDarkMode; const [isLoading, setIsLoading] = useState(true); - const [mainEvents, setMainEvents] = useState({ - today: [], - upcoming: [], - past: [], - }); - const [intramuralEvents, setIntramuralEvents] = useState({ - today: [], - upcoming: [], - past: [], - }); - const [committeeEvents, setCommitteeEvents] = useState({ - today: [], - upcoming: [], - past: [], - }); + const [mainEvents, setMainEvents] = useState({ today: [], upcoming: [], past: [] }); + const [intramuralEvents, setIntramuralEvents] = useState({ today: [], upcoming: [], past: [] }); + const [committeeEvents, setCommitteeEvents] = useState({ today: [], upcoming: [], past: [] }); const [infoVisible, setInfoVisible] = useState(false); const [filter, setFilter] = useState<"main" | "intramural" | "committee">("main"); @@ -62,11 +49,11 @@ const Events = ({ navigation }: EventsProps) => { setIsLoading(true); const upcomingEventsData = await getUpcomingEvents(); + const allPastEvents = await getWeekPastEvents(); const currentTime = new Date(); const today = new Date(currentTime.getFullYear(), currentTime.getMonth(), currentTime.getDate()); - // Filter today's and upcoming events const todayEvents = upcomingEventsData.filter(event => { const startTime = event.startTime ? event.startTime.toDate() : new Date(0); return startTime >= today && startTime < new Date(today.getTime() + 24 * 60 * 60 * 1000); @@ -96,8 +83,6 @@ const Events = ({ navigation }: EventsProps) => { ); - const allPastEvents = await getWeekPastEvents(); - const pastMainEvents = allPastEvents.filter( (event: SHPEEvent) => !event.hiddenEvent && From 77dc8191ae025411e11ea0e84f85f78449b141f2 Mon Sep 17 00:00:00 2001 From: Jason Date: Sat, 14 Sep 2024 16:42:47 -0500 Subject: [PATCH 11/15] show hidden event to privilege user --- src/screens/events/Events.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/screens/events/Events.tsx b/src/screens/events/Events.tsx index 1d251cf2..9694286e 100644 --- a/src/screens/events/Events.tsx +++ b/src/screens/events/Events.tsx @@ -65,44 +65,44 @@ const Events = ({ navigation }: EventsProps) => { const mainEventsFiltered = upcomingEventsData.filter( (event: SHPEEvent) => - !event.hiddenEvent && + (hasPrivileges || !event.hiddenEvent) && event.eventType !== EventType.COMMITTEE_MEETING && event.eventType !== EventType.INTRAMURAL_EVENT ); const intramuralEventsFiltered = upcomingEventsData.filter( (event: SHPEEvent) => - !event.hiddenEvent && + (hasPrivileges || !event.hiddenEvent) && event.eventType === EventType.INTRAMURAL_EVENT ); const committeeEventsFiltered = upcomingEventsData.filter( (event: SHPEEvent) => - !event.hiddenEvent && + (hasPrivileges || !event.hiddenEvent) && event.eventType === EventType.COMMITTEE_MEETING ); - const pastMainEvents = allPastEvents.filter( (event: SHPEEvent) => - !event.hiddenEvent && + (hasPrivileges || !event.hiddenEvent) && event.eventType !== EventType.COMMITTEE_MEETING && event.eventType !== EventType.INTRAMURAL_EVENT ); const pastIntramuralEvents = allPastEvents.filter( (event: SHPEEvent) => - !event.hiddenEvent && + (hasPrivileges || !event.hiddenEvent) && event.eventType === EventType.INTRAMURAL_EVENT ); const pastCommitteeEvents = allPastEvents.filter( (event: SHPEEvent) => - !event.hiddenEvent && + (hasPrivileges || !event.hiddenEvent) && event.eventType === EventType.COMMITTEE_MEETING ); + setMainEvents({ today: todayEvents.filter(event => mainEventsFiltered.includes(event)), upcoming: upcomingEvents.filter(event => mainEventsFiltered.includes(event)), From de10d36243569a63ab98892a93993bc963de2d9f Mon Sep 17 00:00:00 2001 From: Jason Date: Sun, 15 Sep 2024 20:17:49 -0500 Subject: [PATCH 12/15] remove zoom --- src/screens/events/QRCodeScanningScreen.tsx | 142 ++++++++------------ 1 file changed, 53 insertions(+), 89 deletions(-) diff --git a/src/screens/events/QRCodeScanningScreen.tsx b/src/screens/events/QRCodeScanningScreen.tsx index a6f3034a..1427a659 100644 --- a/src/screens/events/QRCodeScanningScreen.tsx +++ b/src/screens/events/QRCodeScanningScreen.tsx @@ -5,10 +5,8 @@ import { SafeAreaView } from 'react-native-safe-area-context'; import { Octicons } from '@expo/vector-icons'; import { NativeStackScreenProps } from '@react-navigation/native-stack'; import { MainStackParams } from '../../types/navigation'; -import { GestureHandlerRootView, PinchGestureHandler, PinchGestureHandlerGestureEvent, State } from 'react-native-gesture-handler'; const { width: screenWidth, height: screenHeight } = Dimensions.get('window'); -const screenDensity = PixelRatio.get(); type BarCodeScannedResult = { type: string; @@ -20,7 +18,6 @@ const QRCodeScanningScreen = ({ navigation }: NativeStackScreenProps(null); const [boxColor, setBoxColor] = useState('#FFFFFF'); const [validScanned, setValidScanned] = useState(false); - const [zoom, setZoom] = useState(0); const pulseAnim = useRef(new Animated.Value(1)).current; const boxTop = useRef(new Animated.Value((screenHeight / 2) - 240)).current; @@ -60,26 +57,6 @@ const QRCodeScanningScreen = ({ navigation }: NativeStackScreenProps { - if (nativeEvent.scale !== 0) { - const baseZoomFactor = 0.0004 * screenDensity; - const zoomSensitivity = 0.1; - const dynamicFactor = zoom * zoomSensitivity + baseZoomFactor; - const scaleChange = (nativeEvent.scale - 1) * dynamicFactor * (nativeEvent.scale > 1 ? 1 : 2); - - const maxZoom = 0.05; - const newZoom = Math.min(Math.max(zoom + scaleChange, 0), maxZoom); - - setZoom(newZoom); - } - }; - - const handlePinchStateChange = ({ nativeEvent }: any) => { - if (nativeEvent.state === State.END || nativeEvent.state === State.CANCELLED) { - lastScale.current = 1; - } - }; - const handleBarCodeScanned = ({ bounds, type, data }: BarCodeScannedResult) => { if (validScanned) { return; @@ -139,74 +116,61 @@ const QRCodeScanningScreen = ({ navigation }: NativeStackScreenProps - - - - Scanner - - navigation.goBack()}> - - + + + + Scanner - navigation.goBack()}> + + + + + {/* Pulsing Effect with Animated Transition */} + - - - {/* Pulsing Effect with Animated Transition */} - - - - - - - - - - - {(1 + zoom * 100).toFixed(1)}x - - - - - - Using Scanner - Scan the QRCode provided by the event host. - - - + + + + + + + + + + + Using Scanner + Scan the QRCode provided by the event host. + + ); }; From 5501643976e4a0668ccb46a1551e464a5df7edc4 Mon Sep 17 00:00:00 2001 From: Jason Date: Mon, 16 Sep 2024 14:59:50 -0500 Subject: [PATCH 13/15] simple ui update to evnet info screen --- assets/calendar-days-solid_black.svg | 1 + assets/calendar-days-solid_white.svg | 1 + assets/clock-solid_black.svg | 1 + assets/clock-solid_white.svg | 1 + assets/location-dot-solid_black.svg | 1 + assets/location-dot-solid_white.svg | 1 + src/helpers/timeUtils.ts | 119 ++++++++++++++++++++------- src/screens/events/EventInfo.tsx | 85 ++++++++++++------- 8 files changed, 149 insertions(+), 61 deletions(-) create mode 100644 assets/calendar-days-solid_black.svg create mode 100644 assets/calendar-days-solid_white.svg create mode 100644 assets/clock-solid_black.svg create mode 100644 assets/clock-solid_white.svg create mode 100644 assets/location-dot-solid_black.svg create mode 100644 assets/location-dot-solid_white.svg diff --git a/assets/calendar-days-solid_black.svg b/assets/calendar-days-solid_black.svg new file mode 100644 index 00000000..8d38d2cc --- /dev/null +++ b/assets/calendar-days-solid_black.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/calendar-days-solid_white.svg b/assets/calendar-days-solid_white.svg new file mode 100644 index 00000000..f8d5d5f4 --- /dev/null +++ b/assets/calendar-days-solid_white.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/clock-solid_black.svg b/assets/clock-solid_black.svg new file mode 100644 index 00000000..66f32461 --- /dev/null +++ b/assets/clock-solid_black.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/clock-solid_white.svg b/assets/clock-solid_white.svg new file mode 100644 index 00000000..40b063d1 --- /dev/null +++ b/assets/clock-solid_white.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/location-dot-solid_black.svg b/assets/location-dot-solid_black.svg new file mode 100644 index 00000000..acd033c1 --- /dev/null +++ b/assets/location-dot-solid_black.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/location-dot-solid_white.svg b/assets/location-dot-solid_white.svg new file mode 100644 index 00000000..89977cad --- /dev/null +++ b/assets/location-dot-solid_white.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/helpers/timeUtils.ts b/src/helpers/timeUtils.ts index b97e163b..db54f1ca 100644 --- a/src/helpers/timeUtils.ts +++ b/src/helpers/timeUtils.ts @@ -65,26 +65,6 @@ export const formatTime = (date: Date): string => { return `${hour % 12 == 0 ? 12 : hour % 12}:${minute.toString().padStart(2, '0')}${hour > 11 ? "pm" : "am"}`; } -export const formatEventTime = (startDate: Date, endDate: Date): string => { - const startHour = startDate.getHours(); - const endHour = endDate.getHours(); - const startMinute = startDate.getMinutes(); - const endMinute = endDate.getMinutes(); - - const startAmPm = startHour >= 12 ? "pm" : "am"; - const endAmPm = endHour >= 12 ? "pm" : "am"; - - const formattedStartTime = `${startHour % 12 === 0 ? 12 : startHour % 12}:${startMinute.toString().padStart(2, '0')}`; - const formattedEndTime = `${endHour % 12 === 0 ? 12 : endHour % 12}:${endMinute.toString().padStart(2, '0')}${endAmPm}`; - - if (startAmPm === endAmPm) { - return `${formattedStartTime} - ${formattedEndTime}`; - } - - return `${formattedStartTime}${startAmPm} - ${formattedEndTime}`; -} - - /** * Constructs a readable string that represents the date and time of day of a given `Date` object and it's timezone offset. * @param date @@ -94,7 +74,26 @@ export const formatDateTime = (date: Date): string => { return `${formatDate(date)}, ${formatTime(date)} GMT${date.getTimezoneOffset() < 0 ? "+" : "-"}${(date.getTimezoneOffset() / 60).toString().padStart(2, '0')}` } -export const formatEventDate = (startTime: Date, endTime: Date) => { +export const formatEventDate = (startTime: Date, endTime: Date): string => { + // Formats the date as "Monday, September 16" + const formatFullDate = (date: Date): string => { + const options: Intl.DateTimeFormatOptions = { weekday: 'long', month: 'long', day: 'numeric' }; + return date.toLocaleDateString('en-US', options); + }; + + // Formats the date as "September 16" + const formatMonthDay = (date: Date): string => { + const options: Intl.DateTimeFormatOptions = { month: 'long', day: 'numeric' }; + return date.toLocaleDateString('en-US', options); + }; + + // Formats the date as "September 16, 2024" + const formatMonthDayYear = (date: Date): string => { + const options: Intl.DateTimeFormatOptions = { month: 'long', day: 'numeric', year: 'numeric' }; + return date.toLocaleDateString('en-US', options); + }; + + // Check if start and end times are on the same day, month, or year const isSameDay = startTime.getDate() === endTime.getDate() && startTime.getMonth() === endTime.getMonth() && startTime.getFullYear() === endTime.getFullYear(); @@ -103,22 +102,82 @@ export const formatEventDate = (startTime: Date, endTime: Date) => { startTime.getFullYear() === endTime.getFullYear(); const isSameYear = startTime.getFullYear() === endTime.getFullYear(); - const formatMonthDayOnly = (date: Date): string => { - const day = date.getDate(); - const month = monthNames[date.getMonth()]; - return `${month} ${day}`; + if (isSameDay) { + return formatFullDate(startTime); // "Monday, September 16" + } else if (isSameMonth) { + return `${formatMonthDay(startTime)} - ${endTime.getDate()}`; // "September 16 - 17" + } else if (isSameYear) { + return `${formatMonthDay(startTime)} - ${formatMonthDay(endTime)}`; // "September 16 - October 18" + } else { + return `${formatMonthDayYear(startTime)} - ${formatMonthDayYear(endTime)}`; // "December 17, 2024 - December 18, 2025" } +}; +export const formatEventDateTime = (startTime: Date, endTime: Date): string => { + // Formats the date as "Monday, September 16" + const formatFullDate = (date: Date): string => { + const options: Intl.DateTimeFormatOptions = { weekday: 'long', month: 'long', day: 'numeric' }; + return date.toLocaleDateString('en-US', options); + }; + + // Formats the date as "September 16" + const formatMonthDay = (date: Date): string => { + const options: Intl.DateTimeFormatOptions = { month: 'long', day: 'numeric' }; + return date.toLocaleDateString('en-US', options); + }; + + // Formats the date as "September 16, 2024" + const formatMonthDayYear = (date: Date): string => { + const options: Intl.DateTimeFormatOptions = { month: 'long', day: 'numeric', year: 'numeric' }; + return date.toLocaleDateString('en-US', options); + }; + + // Formats the time as "1:00pm" + const formatTime = (date: Date): string => { + const hours = date.getHours(); + const minutes = date.getMinutes(); + const amPm = hours >= 12 ? 'pm' : 'am'; + const formattedHour = hours % 12 === 0 ? 12 : hours % 12; + const formattedMinute = minutes.toString().padStart(2, '0'); + return `${formattedHour}:${formattedMinute}${amPm}`; + }; + + const isSameDay = startTime.getDate() === endTime.getDate() && + startTime.getMonth() === endTime.getMonth() && + startTime.getFullYear() === endTime.getFullYear(); + + const isSameMonth = startTime.getMonth() === endTime.getMonth() && + startTime.getFullYear() === endTime.getFullYear(); + + const isSameYear = startTime.getFullYear() === endTime.getFullYear(); if (isSameDay) { - return `${formatDate(startTime)}`; + return `${formatFullDate(startTime)}, ${formatTime(startTime)} - ${formatTime(endTime)}`; // "Monday, September 16, 1:00pm - 2:00pm" } else if (isSameMonth) { - return `${formatMonthDayOnly(startTime)} - ${endTime.getDate()}`; + return `${formatMonthDay(startTime)}, ${formatTime(startTime)} - ${formatMonthDay(endTime)}, ${formatTime(endTime)}`; // "September 16, 1:00pm - September 17, 2:00pm" } else if (isSameYear) { - return `${formatMonthDayOnly(startTime)} - ${formatDate(endTime)}`; + return `${formatMonthDay(startTime)}, ${formatTime(startTime)} - ${formatMonthDay(endTime)}, ${formatTime(endTime)}`; // "September 16, 1:00pm - October 18, 2:00pm" } else { - return `${formatDateWithYear(startTime)} - ${formatDateWithYear(endTime)}`; + return `${formatMonthDayYear(startTime)}, ${formatTime(startTime)} - ${formatMonthDayYear(endTime)}, ${formatTime(endTime)}`; // "December 17, 2024, 1:00pm - December 18, 2025, 2:00pm" } -}; \ No newline at end of file +}; + +export const formatEventTime = (startDate: Date, endDate: Date): string => { + // Helper function to format time + const formatTime = (date: Date): string => { + const hours = date.getHours(); + const minutes = date.getMinutes(); + const amPm = hours >= 12 ? 'pm' : 'am'; + const formattedHour = hours % 12 === 0 ? 12 : hours % 12; + const formattedMinute = minutes.toString().padStart(2, '0'); + return `${formattedHour}:${formattedMinute}${amPm}`; + }; + + const startFormatted = formatTime(startDate); + const endFormatted = formatTime(endDate); + + // Format time range output + return `${startFormatted} - ${endFormatted}`; +}; diff --git a/src/screens/events/EventInfo.tsx b/src/screens/events/EventInfo.tsx index ed767d0c..4a1b8950 100644 --- a/src/screens/events/EventInfo.tsx +++ b/src/screens/events/EventInfo.tsx @@ -3,10 +3,11 @@ import React, { useContext, useEffect, useState } from 'react' import { RouteProp, useRoute } from '@react-navigation/core'; import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'; import { Octicons, FontAwesome6, Entypo } from '@expo/vector-icons'; +import { LinearGradient } from 'expo-linear-gradient'; import { auth } from "../../config/firebaseConfig"; import { getAttendanceNumber, getPublicUserData, getUsers, signInToEvent, signOutOfEvent, getUserEventLog, fetchEventLogs, deleteEventLog } from '../../api/firebaseUtils'; import { UserContext } from '../../context/UserContext'; -import { MillisecondTimes, formatEventDate, formatEventTime } from '../../helpers/timeUtils'; +import { MillisecondTimes, formatEventDate, formatEventDateTime, formatEventTime } from '../../helpers/timeUtils'; import { EventType, SHPEEvent, SHPEEventLog, getStatusMessage } from '../../types/events'; import { Images } from '../../../assets'; import { StatusBar } from 'expo-status-bar'; @@ -15,7 +16,16 @@ import { PublicUserInfo } from '../../types/user'; import { reverseFormattedFirebaseName } from '../../types/committees'; import MembersList from '../../components/MembersList'; import { EventProps, EventsStackParams } from '../../types/navigation'; -import { LinearGradient } from 'expo-linear-gradient'; +import CalendarIconBlack from '../../../assets/calendar-days-solid_black.svg' +import CalendarIconWhite from '../../../assets/calendar-days-solid_white.svg' +import ClockIconBlack from '../../../assets/clock-solid_black.svg' +import ClockIconWhite from '../../../assets/clock-solid_white.svg' +import LocationDotIconBlack from '../../../assets/location-dot-solid_black.svg' +import LocationDotIconWhite from '../../../assets/location-dot-solid_white.svg' + + + + const EventInfo = ({ navigation }: EventProps) => { const route = useRoute(); @@ -142,6 +152,15 @@ const EventInfo = ({ navigation }: EventProps) => { const eventButtonState = getEventButtonState(event, userEventLog); + const isSameDay = (startDate: Date, endDate: Date): boolean => { + return startDate.getDate() === endDate.getDate() && + startDate.getMonth() === endDate.getMonth() && + startDate.getFullYear() === endDate.getFullYear(); + }; + const sameDay = startTime && endTime && isSameDay(startTime.toDate(), endTime.toDate()); + + + return ( @@ -309,41 +328,41 @@ const EventInfo = ({ navigation }: EventProps) => { {name} {eventType} - {committee && (" • " + reverseFormattedFirebaseName(committee))} • {calculateMaxPossiblePoints(event)} points - - - Hosted By {creatorData?.name} + {committee && (" • " + reverseFormattedFirebaseName(committee))} • {calculateMaxPossiblePoints(event)} points • {creatorData?.name} {/* Date, Time and Location */} - - - - Date - {(startTime && endTime) ? formatEventDate(startTime.toDate(), endTime.toDate()) : ""} + + + + {darkMode ? : } + + {(startTime && endTime) ? + (sameDay ? + formatEventDate(startTime.toDate(), endTime.toDate()) + : formatEventDateTime(startTime.toDate(), endTime.toDate()) + ) + : "" + } + + - - Time - {startTime && endTime && formatEventTime(startTime.toDate(), endTime.toDate())} + + {sameDay && ( + + + {darkMode ? : } + + + {startTime && endTime && formatEventTime(startTime.toDate(), endTime.toDate())} + - + )} {locationName && ( - Location {geolocation ? ( { handleLinkPress(`https://www.google.com/maps/dir/?api=1&destination=${geolocation.latitude},${geolocation.longitude}`); } }} + className='flex-row items-center' > - + + {darkMode ? : } + + {locationName} ) : ( - + {locationName} )} @@ -372,8 +395,8 @@ const EventInfo = ({ navigation }: EventProps) => { {/* Description */} {(description && description.trim() != "") && ( - - About Event + + Description {description} )} From 1385a5bc5a854d3513f381ab26e6465008e5ae22 Mon Sep 17 00:00:00 2001 From: Jason Date: Mon, 16 Sep 2024 15:49:54 -0500 Subject: [PATCH 14/15] update finalize event --- src/screens/events/FinalizeEvent.tsx | 97 +++++++++++++++++----------- 1 file changed, 60 insertions(+), 37 deletions(-) diff --git a/src/screens/events/FinalizeEvent.tsx b/src/screens/events/FinalizeEvent.tsx index 5e9d678d..36d6de74 100644 --- a/src/screens/events/FinalizeEvent.tsx +++ b/src/screens/events/FinalizeEvent.tsx @@ -6,14 +6,21 @@ import { Octicons } from '@expo/vector-icons'; import { UserContext } from '../../context/UserContext'; import { RouteProp, useRoute } from '@react-navigation/core'; import { Images } from '../../../assets'; -import { MillisecondTimes, formatEventDate, formatEventTime } from '../../helpers/timeUtils'; +import { MillisecondTimes, formatEventDate, formatEventDateTime, formatEventTime } from '../../helpers/timeUtils'; import { StatusBar } from 'expo-status-bar'; import { handleLinkPress } from '../../helpers/links'; -import { SHPEEvent } from '../../types/events'; +import { EventType, SHPEEvent } from '../../types/events'; import { LinearGradient } from 'expo-linear-gradient'; import { reverseFormattedFirebaseName } from '../../types/committees'; import InteractButton from '../../components/InteractButton'; import { createEvent } from '../../api/firebaseUtils'; +import CalendarIconBlack from '../../../assets/calendar-days-solid_black.svg' +import CalendarIconWhite from '../../../assets/calendar-days-solid_white.svg' +import ClockIconBlack from '../../../assets/clock-solid_black.svg' +import ClockIconWhite from '../../../assets/clock-solid_white.svg' +import LocationDotIconBlack from '../../../assets/location-dot-solid_black.svg' +import LocationDotIconWhite from '../../../assets/location-dot-solid_white.svg' + const FinalizeEvent = ({ navigation }: EventProps) => { const route = useRoute(); @@ -30,6 +37,13 @@ const FinalizeEvent = ({ navigation }: EventProps) => { const colorScheme = useColorScheme(); const darkMode = useSystemDefault ? colorScheme === 'dark' : fixDarkMode; + const isSameDay = (startDate: Date, endDate: Date): boolean => { + return startDate.getDate() === endDate.getDate() && + startDate.getMonth() === endDate.getMonth() && + startDate.getFullYear() === endDate.getFullYear(); + }; + const sameDay = startTime && endTime && isSameDay(startTime.toDate(), endTime.toDate()); + return ( @@ -79,51 +93,57 @@ const FinalizeEvent = ({ navigation }: EventProps) => { {/* General Details */} {nationalConventionEligible && ( - - This event is eligible for national convention requirements* + This event is eligible for national convention requirements* + + )} + + {(eventType === EventType.STUDY_HOURS) && ( + + Feel free to leave the area. Just be sure to scan in and out at the event location to fully earn your points! )} + {name} {eventType} - {committee && (" • " + reverseFormattedFirebaseName(committee))} • {calculateMaxPossiblePoints(event)} points - - - Hosted By {userInfo?.publicInfo?.name} + {committee && (" • " + reverseFormattedFirebaseName(committee))} • {calculateMaxPossiblePoints(event)} points • {userInfo?.publicInfo?.name} - {/* Date, Time and Location */} - - - - Date - {(startTime && endTime) ? formatEventDate(startTime.toDate(), endTime.toDate()) : ""} - - - Time - {startTime && endTime && formatEventTime(startTime.toDate(), endTime.toDate())} + {/* Date, Time and Location */} + + + + {darkMode ? : } + + {(startTime && endTime) ? + (sameDay ? + formatEventDate(startTime.toDate(), endTime.toDate()) + : formatEventDateTime(startTime.toDate(), endTime.toDate()) + ) + : "" + } + + {sameDay && ( + + + {darkMode ? : } + + + {startTime && endTime && formatEventTime(startTime.toDate(), endTime.toDate())} + + + )} + {locationName && ( - Location {geolocation ? ( { handleLinkPress(`https://www.google.com/maps/dir/?api=1&destination=${geolocation.latitude},${geolocation.longitude}`); } }} + className='flex-row items-center' > - + + {darkMode ? : } + + {locationName} ) : ( - + {locationName} )} )} + + {/* Description */} {(description && description.trim() != "") && ( - - About Event + + Description {description} )} @@ -173,9 +199,6 @@ const FinalizeEvent = ({ navigation }: EventProps) => { }} /> - - - ) } From 9678fad9824cdb4d896bf97ed276ad3dcb690470 Mon Sep 17 00:00:00 2001 From: Jason Date: Mon, 16 Sep 2024 16:27:54 -0500 Subject: [PATCH 15/15] version bump to 1.0.17 --- app.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app.json b/app.json index ab63ad26..ede0ea96 100644 --- a/app.json +++ b/app.json @@ -2,7 +2,7 @@ "expo": { "name": "TAMU SHPE", "slug": "TAMU-SHPE", - "version": "1.0.16", + "version": "1.0.17", "owner": "tamu-shpe", "orientation": "portrait", "icon": "./assets/icon.png", diff --git a/package.json b/package.json index 661ed312..f10f3e1b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "shpe-app", - "version": "1.0.16", + "version": "1.0.17", "scripts": { "start": "npx expo start --dev-client", "test": "jest --coverage=true --verbose --bail --config ./jest.config.ts",