Skip to content

Commit

Permalink
Merge pull request #172 from GTBitsOfGood/jay/modal-queue
Browse files Browse the repository at this point in the history
Fix modal queue
  • Loading branch information
SamratSahoo authored Nov 15, 2024
2 parents 7295f7f + 21f7dfd commit 8a76ae4
Show file tree
Hide file tree
Showing 5 changed files with 777 additions and 1,086 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState, useEffect, useRef } from "react";
import React from "react";
import {
View,
Modal,
Expand All @@ -10,58 +10,24 @@ import {
import { MaterialIcons } from "@expo/vector-icons";
import { ModalContent } from "../utils/types";

const ModalQueueManager = ({
modals,
onComplete,
const DashboardModal = ({
content,
onClose,
}: {
modals: ModalContent[];
onComplete: () => void;
content: ModalContent | null;
onClose: () => void;
}) => {
const [currentModal, setCurrentModal] = useState<ModalContent | null>(null);
const [queue, setQueue] = useState<ModalContent[]>([]);
const seenTypes = useRef<Set<ModalContent["type"]>>(new Set());

// Process new modals
useEffect(() => {
if (modals.length > 0) {
// Filter out modals that have already been seen based on type
const newModals = modals.filter((modal) => {
if (!seenTypes.current.has(modal.type)) {
seenTypes.current.add(modal.type); // Mark as seen immediately
return true;
}
return false;
});

if (newModals.length > 0) {
setQueue((prev) => [...prev, ...newModals]);
}
}
}, [modals]);

// Show next modal
useEffect(() => {
if (queue.length > 0 && !currentModal) {
const nextModal = queue[0];
setCurrentModal(nextModal);
setQueue((prev) => prev.slice(1));
}
}, [queue, currentModal]);

const handleCloseModal = () => {
setCurrentModal(null);
if (queue.length === 0) {
onComplete?.();
}
onClose();
};

if (!currentModal) return null;
if (!content) return null;

return (
<Modal
animationType="fade"
transparent={true}
visible={!!currentModal}
visible={!!content}
onShow={() => Vibration.vibrate(10000)}
>
<View style={styles.centeredView}>
Expand All @@ -72,11 +38,9 @@ const ModalQueueManager = ({
>
<MaterialIcons name="close" size={20} color="grey" />
</Pressable>
<Text style={styles.modalText}>{currentModal.content}</Text>
{currentModal.additionalContent && (
<Text style={styles.modalText}>
{currentModal.additionalContent}
</Text>
<Text style={styles.modalText}>{content.content}</Text>
{content.additionalContent && (
<Text style={styles.modalText}>{content.additionalContent}</Text>
)}
</View>
</View>
Expand Down Expand Up @@ -127,4 +91,4 @@ const styles = StyleSheet.create({
},
});

export default ModalQueueManager;
export default DashboardModal;
11 changes: 6 additions & 5 deletions mobile/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,24 @@
"babel-preset-expo": "~12.0.0",
"crypto-browserify": "^3.12.0",
"dayjs": "^1.11.13",
"expo": "^52.0.5",
"expo": "^52.0.7",
"expo-av": "~15.0.1",
"expo-constants": "~17.0.2",
"expo-constants": "~17.0.3",
"expo-doctor": "^1.10.1",
"expo-file-system": "~18.0.3",
"expo-haptics": "~14.0.0",
"expo-image-picker": "~16.0.1",
"expo-image-picker": "~16.0.2",
"expo-linking": "~7.0.2",
"expo-media-library": "~17.0.2",
"expo-splash-screen": "~0.29.8",
"expo-splash-screen": "~0.29.10",
"expo-status-bar": "~2.0.0",
"expo-video-thumbnails": "~9.0.2",
"firebase": "^9.9.4",
"mongoose": "^6.5.2",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-native": "0.76.1",
"react-native": "0.76.2",
"react-native-config": "^1.5.3",
"react-native-dropdown-picker": "^5.4.2",
"react-native-fiesta": "^0.7.0",
"react-native-get-random-values": "~1.11.0",
Expand Down
125 changes: 76 additions & 49 deletions mobile/screens/User/UserDashboardScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react";
import React, { useEffect, useReducer, useState } from "react";
import {
BackHandler,
StyleSheet,
Expand All @@ -16,7 +16,6 @@ import { userGetAnimal, userUpdateAnimal } from "../../actions/Animal";
import {
Announcement,
ModalContent,
NotificationState,
Screens,
ServiceAnimal,
TrainingLog,
Expand All @@ -38,7 +37,50 @@ import { endOfExecutionHandler, ErrorWrapper } from "../../utils/error";
import ErrorBox from "../../components/ErrorBox";
import shadowStyle from "../../utils/styles";
import { userSendEmail } from "../../actions/Email";
import ModalQueueManager from "../../components/ModalQueueManager";
import DashboardModal from "../../components/DashboardModal";

type ModalQueueState = {
birthdayQueued: boolean;
prescriptionQueued: boolean;
shotQueued: boolean;

modals: ModalContent[];
};

function modalQueueReducer(
state: ModalQueueState,
action: ModalContent | "close"
): ModalQueueState {
if (action === "close") {
return { ...state, modals: state.modals.slice(1) };
}

switch (action.type) {
case "birthday":
if (state.birthdayQueued) return state;
return {
...state,
modals: [...state.modals, action],
birthdayQueued: true,
};
case "prescription":
if (state.prescriptionQueued) return state;
return {
...state,
modals: [...state.modals, action],
prescriptionQueued: true,
};
case "shot":
if (state.shotQueued) return state;
return {
...state,
modals: [...state.modals, action],
shotQueued: true,
};
default:
return state;
}
}

export default function UserDashboardScreen(props: any) {
const [hoursCompleted, setHoursCompleted] = useState(0);
Expand All @@ -49,30 +91,20 @@ export default function UserDashboardScreen(props: any) {
const [announcements, setAnnouncements] = useState<Announcement[]>([]);
const [error, setError] = useState("");
const [calculatedShotDate, setCalculatedShotDate] = useState<Date>();
const [modalQueue, setModalQueue] = useState<ModalContent[]>([]);
const [notificationsShown, setNotificationsShown] =
useState<NotificationState>({
birthday: false,
prescription: false,
shot: false,
});

const addToModalQueue = (
type: ModalContent["type"],
content: string,
additionalContent?: string
) => {
if (!notificationsShown[type]) {
setModalQueue((prev) => [...prev, { type, content, additionalContent }]);
setNotificationsShown((prev) => ({ ...prev, [type]: true }));
}
};
const [modalQueue, modalQueueDispatch] = useReducer(modalQueueReducer, {
modals: [],

birthdayQueued: false,
prescriptionQueued: false,
shotQueued: false,
});

const checkPrescriptionReminder = async (
userInformation: User,
animalInformation: ServiceAnimal
) => {
if (notificationsShown.prescription) return;
if (modalQueue.prescriptionQueued) return;

if (!userInformation) {
userInformation = (await ErrorWrapper({
Expand Down Expand Up @@ -113,10 +145,10 @@ export default function UserDashboardScreen(props: any) {
}

if (today >= new Date(userInformation?.nextPrescriptionReminder)) {
addToModalQueue(
"prescription",
`This is a reminder to fill your prescription for ${animalInformation.name}'s heartworm preventative pill.`
);
modalQueueDispatch({
type: "prescription",
content: `This is a reminder to fill your prescription for ${animalInformation.name}'s heartworm preventative pill.`,
});

await userUpdateUser(
userInformation.roles,
Expand All @@ -137,7 +169,7 @@ export default function UserDashboardScreen(props: any) {
animalInformation: ServiceAnimal,
userInformation: User
) => {
if (notificationsShown.shot) return;
if (modalQueue.shotQueued) return;

if (!animalInformation) {
animalInformation = (await ErrorWrapper({
Expand Down Expand Up @@ -167,18 +199,18 @@ export default function UserDashboardScreen(props: any) {

if (newDate > new Date()) return;

addToModalQueue(
"shot",
`${animalInformation?.name} needs their rabies shot.`,
`${animalInformation?.name} was due on ${newDate.toLocaleDateString(
modalQueueDispatch({
type: "shot",
content: `${animalInformation?.name} needs their rabies shot.`,
additionalContent: `${animalInformation?.name} was due on ${newDate.toLocaleDateString(
"en-US",
{
year: "numeric",
month: "long",
day: "numeric",
}
)}`
);
)}`,
});

const newAnimal = await userUpdateAnimal(
animalInformation?.name,
Expand Down Expand Up @@ -222,22 +254,22 @@ export default function UserDashboardScreen(props: any) {
};

const checkBirthday = (animal: ServiceAnimal) => {
if (notificationsShown.birthday) return;
if (modalQueue.birthdayQueued) return;

if (
new Date(animal?.dateOfBirth as Date).getMonth() ===
new Date().getMonth() &&
new Date(animal?.dateOfBirth as Date).getDate() === new Date().getDate()
) {
addToModalQueue(
"birthday",
`Happy birthday ${animal.name}!!! \uE312`,
`${animal.name} turned ${calculateAge(
modalQueueDispatch({
type: "birthday",
content: `Happy birthday ${animal.name}!!! \uE312`,
additionalContent: `${animal.name} turned ${calculateAge(
new Date(animal.dateOfBirth ?? "")
)} year${
calculateAge(new Date(animal.dateOfBirth ?? "")) !== 1 ? "s" : ""
} old today!`
);
} old today!`,
});
}
};

Expand Down Expand Up @@ -287,13 +319,6 @@ export default function UserDashboardScreen(props: any) {
}
}

// Reset notifications shown state when component mounts
setNotificationsShown({
birthday: false,
prescription: false,
shot: false,
});

getUserDashboardInformation().catch();

BackHandler.addEventListener("hardwareBackPress", function () {
Expand Down Expand Up @@ -325,9 +350,11 @@ export default function UserDashboardScreen(props: any) {
{new Date().getDate() === 1 ? <PillBanner /> : null}
{new Date().getMonth() === 1 ? <CleaningBanner /> : null}

<ModalQueueManager
modals={modalQueue}
onComplete={() => setModalQueue([])}
<DashboardModal
content={modalQueue.modals.length ? modalQueue.modals[0] : null}
onClose={() => {
modalQueueDispatch("close");
}}
/>

<TouchableOpacity
Expand Down
Loading

0 comments on commit 8a76ae4

Please sign in to comment.