Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/components/CarouselSection.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ function CarouselSection({ navigation, data, onRefresh }) {
location={event.Location}
date={formatDate(event.Date)}
image={event.Image}
posters={event.Posters}
description={event.Description ?? ""}
tags={
event.Tags == null
Expand Down
168 changes: 168 additions & 0 deletions src/components/PostersButton.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import MaterialCommunityIcons from "@expo/vector-icons/MaterialCommunityIcons";
import { Image } from "expo-image";
import { useRef, useState } from "react";
import {
Dimensions,
FlatList,
Modal,
Pressable,
StyleSheet,
Text,
View,
} from "react-native";

import colors from "../constants/colors";

const { width, height } = Dimensions.get("window");

export default function PostersButton({ posters }) {
const [showGallery, setShowGallery] = useState(false);
const [currentIndex, setCurrentIndex] = useState(0);
const flatListRef = useRef(null);

const images = Array.from(new Set(posters));
const count = images.length;
const looped = count > 0 ? [...images, ...images, ...images] : [];

function openGallery() {
setCurrentIndex(count);
setShowGallery(true);
setTimeout(() => {
flatListRef.current?.scrollToIndex({ index: count, animated: false });
}, 0);
}

function onMomentumScrollEnd(e) {
const idx = Math.round(e.nativeEvent.contentOffset.x / width);
let normalized = idx;

if (idx < count) {
normalized = idx + count;
flatListRef.current?.scrollToIndex({
index: normalized,
animated: false,
});
} else if (idx >= count * 2) {
normalized = idx - count;
flatListRef.current?.scrollToIndex({
index: normalized,
animated: false,
});
}

setCurrentIndex(normalized);
}

if (count === 0) return null;

return (
<View>
<Pressable onPress={openGallery} style={styles.button}>
<MaterialCommunityIcons
name="image-multiple-outline"
size={22}
color={colors.primary}
/>
<Text style={styles.buttonText}>Show Posters & Programs</Text>
</Pressable>

<Modal
visible={showGallery}
transparent={false}
animationType="fade"
onRequestClose={() => setShowGallery(false)}
>
<View style={styles.modalContainer}>
<Pressable
style={styles.closeButton}
onPress={() => setShowGallery(false)}
>
<MaterialCommunityIcons name="close" size={28} color="white" />
</Pressable>

<FlatList
ref={flatListRef}
data={looped}
horizontal
pagingEnabled
showsHorizontalScrollIndicator={false}
keyExtractor={(_, i) => String(i)}
getItemLayout={(_, i) => ({
length: width,
offset: width * i,
index: i,
})}
initialScrollIndex={count}
onMomentumScrollEnd={onMomentumScrollEnd}
renderItem={({ item }) => (
<View style={styles.imagePage}>
<Image
source={{ uri: item }}
style={styles.image}
contentFit="contain"
/>
</View>
)}
/>

<Text style={styles.footer}>
{(currentIndex % count) + 1} / {count}
</Text>
</View>
</Modal>
</View>
);
}

const styles = StyleSheet.create({
button: {
flexDirection: "row",
alignItems: "center",
gap: 15,
paddingVertical: 10,
paddingHorizontal: 15,
borderRadius: 15,
borderWidth: 1,
borderColor: colors.primary,
alignSelf: "center",
marginTop: 20,
},
buttonText: {
fontSize: 22,
color: colors.primary,
fontWeight: "500",
},
modalContainer: {
flex: 1,
backgroundColor: "black",
justifyContent: "center",
},
closeButton: {
position: "absolute",
top: 50,
right: 20,
zIndex: 10,
},
imagePage: {
width,
height,
justifyContent: "center",
alignItems: "center",
paddingTop: 50,
paddingBottom: 50,
paddingLeft: 50,
paddingRight: 50,
},
image: {
width: "100%",
height: "100%",
},
footer: {
color: "white",
fontSize: 16,
textAlign: "center",
position: "absolute",
bottom: 30,
width: "100%",
},
});
2 changes: 2 additions & 0 deletions src/components/VolunteerOpportunity.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export default function VolunteerOpportunity({
location,
date,
image,
posters,
description,
tags,
formURL,
Expand All @@ -46,6 +47,7 @@ export default function VolunteerOpportunity({
location,
date,
image,
posters,
description,
tags,
formURL,
Expand Down
6 changes: 6 additions & 0 deletions src/screens/HomeScreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,12 @@ export default function HomeScreen({ navigation, route }) {
opportunity.Location ??= "Unknown Location";
opportunity.Date = strToDate(opportunity.Date) ?? new Date(0);

opportunity.Posters = opportunity.Posters
? opportunity.Posters.split(",")
.map((s) => s.trim())
.filter(Boolean)
: [];

const event_midnight = new Date(opportunity.Date);
event_midnight.setHours(23, 59, 59, 999);

Expand Down
6 changes: 0 additions & 6 deletions src/screens/VolunteerFormScreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -181,12 +181,6 @@ export default function VolunteerFormScreen({ navigation, route }) {
</View>
{/* Render each question component from form */}
<View style={styles.form}>
{(() => {
const Bad = () => {
throw new Error("Test error");
};
return <Bad />;
})()}
{form
.questions()
.filter((question) => question?.isVisible())
Expand Down
25 changes: 11 additions & 14 deletions src/screens/VolunteerOpportunityScreen.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,25 @@
/**
/* VolunteerOpportunityScreen.js
* VolunteerOpportunityScreen.js
* Detailed view of a volunteer event.
* - Displays banner image with gradient overlay and title
* - Shows event date and location (tap to open in maps)
* - Optionally displays description and tags
* - Provides Sign Up button (navigates to form or Google Forms URL)
* - Provides Show Posters & Programs button to display all show programs and posters associated with the event
* - Show Posters & Programs are clickable and pop-up when clicked
*/

import Markdown from "react-native-markdown-display";
import MaterialCommunityIcons from "@expo/vector-icons/MaterialCommunityIcons";
import SimpleLineIcons from "@expo/vector-icons/SimpleLineIcons";
import { ImageBackground } from "expo-image";
import { LinearGradient } from "expo-linear-gradient";
import {
Linking,
Pressable,
SafeAreaView,
StyleSheet,
Text,
View,
} from "react-native";
import { Pressable, SafeAreaView, StyleSheet, Text, View } from "react-native";

import Heading from "../components/Heading";
import NextButton from "../components/NextButton";
import PersistScrollView from "../components/PersistScrollView";
import PostersButton from "../components/PostersButton";
import Tag from "../components/Tag";
import colors from "../constants/colors";
import { openInMaps } from "../utils";
Expand All @@ -35,6 +31,7 @@ export default function VolunteerOpportunityScreen({ route, navigation }) {
location,
date,
image,
posters,
description,
tags,
formURL,
Expand All @@ -53,15 +50,15 @@ export default function VolunteerOpportunityScreen({ route, navigation }) {
{/* Banner with background image and gradient overlay */}
<View style={styles.banner}>
<ImageBackground
source={{ uri: image, width: 0, height: 0 }}
source={{ width: 0, height: 0, uri: image }}
style={styles.backgroundImage}
placeholder={{ blurhash: "LT9Hq#RPVrt8%%RjWCkCR:WWtSWB" }}
transition={500}
cachePolicy="memory"
>
<LinearGradient
colors={["transparent", "rgba(0,0,0,0.8)"]}
style={{ position: "absolute", width: "100%", height: "100%" }}
style={{ width: "100%", height: "100%", position: "absolute" }}
/>
</ImageBackground>
{/* Overlay title text at bottom-left of banner */}
Expand All @@ -71,7 +68,6 @@ export default function VolunteerOpportunityScreen({ route, navigation }) {
</Text>
</View>
</View>

{/* Scrollable content area */}
<PersistScrollView style={styles.scrollContainer}>
<View style={styles.subContainer}>
Expand Down Expand Up @@ -103,7 +99,6 @@ export default function VolunteerOpportunityScreen({ route, navigation }) {
</Pressable>
</View>
</View>

{/* Optional description section */}
{typeof description === "string" && description.trim() !== "" && (
<View style={styles.about}>
Expand Down Expand Up @@ -151,7 +146,9 @@ export default function VolunteerOpportunityScreen({ route, navigation }) {
<View style={styles.tags}>{tagsIcons}</View>
</View>
)}
{/* Sign Up button with warning if already submitted */}

<PostersButton posters={posters} />

<View style={styles.lowerRight}>
{isSubmitted && (
<Text style={styles.alreadySubmitted} selectable>
Expand Down
2 changes: 0 additions & 2 deletions src/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,6 @@ export async function sendErrorEmail(error) {
publicKey: process.env.EXPO_PUBLIC_EMAIL_PUBLIC_KEY,
},
);

console.log("SUCCESS!");
} catch (err) {
if (err instanceof EmailJSResponseStatus) {
console.log("EmailJS Request Failed...", err);
Expand Down
Loading