Skip to content
Draft
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
23 changes: 10 additions & 13 deletions apps/mobile/src/app/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,18 +84,15 @@ export default function App() {

const { colorScheme, setColorScheme } = useColorScheme();

if (storage.getString("colorScheme")) {
Appearance.setColorScheme(storage.getString("colorScheme") as ColorSchemeName);
setColorScheme(storage.getString("colorScheme") as ColorSchemeName);
} else {
if (Appearance.getColorScheme()) {
storage.set("colorScheme", Appearance.getColorScheme());
setColorScheme(Appearance.getColorScheme());
} else {
storage.set("colorScheme", "light");
setColorScheme("light");
}
}
useEffect(() => {
const savedScheme = storage.getString("colorScheme");
const scheme = savedScheme ?? Appearance.getColorScheme() ?? "light";

if (!savedScheme) storage.set("colorScheme", scheme);

Appearance.setColorScheme(scheme as ColorSchemeName);
setColorScheme(scheme as ColorSchemeName);
}, [setColorScheme]);

if (storage.getString("passingPeriods") == "undefined") storage.set("passingPeriods", "true");

Expand All @@ -110,7 +107,7 @@ export default function App() {

return (
<>
<StatusBar style={storage.getString("colorScheme") == "dark" ? "light" : "dark"} />
<StatusBar style={colorScheme == "dark" ? "light" : "dark"} />
<GestureHandlerRootView style={{ flex: 1 }}>
<NativeTabs
backgroundColor={colorScheme == "dark" ? "#000000" : "#ddeff0"}
Expand Down
14 changes: 6 additions & 8 deletions apps/mobile/src/app/settings/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { impactAsync } from "expo-haptics";
import { router, usePathname } from "expo-router";
import { styled, useColorScheme } from "nativewind";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Alert, Platform, Pressable, Switch, Text, View } from "react-native";
import { Alert, Appearance, Platform, Pressable, Switch, Text, View } from "react-native";

import WidgetUpdaterModule from "@/modules/widget-updater/src/WidgetUpdaterModule";
import Import from "@/src/app/settings/import";
Expand Down Expand Up @@ -199,13 +199,11 @@ export default function Settings() {
thumbColor="#ddeff0"
trackColor={{ false: "#bfe0e2", true: "#3b757f" }}
onValueChange={() => {
if (colorScheme == "dark") {
setColorScheme("light");
storage.set("colorScheme", "light");
} else {
setColorScheme("dark");
storage.set("colorScheme", "dark");
}
const nextScheme = colorScheme == "dark" ? "light" : "dark";

storage.set("colorScheme", nextScheme);
Appearance.setColorScheme(nextScheme);
setColorScheme(nextScheme);

WidgetUpdaterModule.update();
impactAsync();
Expand Down
61 changes: 12 additions & 49 deletions apps/mobile/src/components/settings/AddEventModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
} from "react-native";
import DateTimePickerModal from "react-native-modal-datetime-picker";

import { findEventConflict } from "@/src/utils/eventValidation";
import storage from "@/src/utils/storage";

import TextModal from "./TextModal";
Expand Down Expand Up @@ -43,16 +44,6 @@ function createCustomDate(timestamp: number) {
return timeString;
}

function createCustomTime(inputTime: string) {
const currentDate = new Date();

const [inputHourRaw, inputMinuteRaw] = inputTime.split(":");
const inputMinute = inputMinuteRaw.replace(/[A-Za-z]/g, ""); // Remove any non-numeric characters

currentDate.setHours(parseInt(inputHourRaw), parseInt(inputMinute), 0, 0);
return currentDate.getTime();
}

function sortByStartTime(array: UnparsedEvent[]) {
return array.sort((a, b) => {
const startTimeA = a.startTime.split(":").map(Number);
Expand All @@ -66,35 +57,6 @@ function sortByStartTime(array: UnparsedEvent[]) {
});
}

function areEventsValid(events: UnparsedEvent[]) {
if (events.length <= 1) {
return true; // Single event is always valid
}

for (let i = 0; i < events.length; i++) {
const currentEvent = events[i];

const startTime = createCustomTime(currentEvent.startTime);
const endTime = createCustomTime(currentEvent.endTime);

if (startTime >= endTime) {
return false; // End time is not after start time
}

if (i < events.length - 1) {
// Check for event overlap
const nextEvent = events[i + 1];
const nextStartTime = createCustomTime(nextEvent.startTime);

if (endTime > nextStartTime) {
return false; // Events overlap
}
}
}

return true; // All events are valid
}

function addEvent(
schedule: UnparsedSchedule,
setSchedule: React.Dispatch<React.SetStateAction<UnparsedSchedule>>,
Expand All @@ -116,15 +78,15 @@ function addEvent(
newSchedule["routines"][currentRoutine]["events"],
);

if (!areEventsValid(newSchedule["routines"][currentRoutine]["events"])) {
return Alert.alert(
"Error",
"This event overlaps with another event or has an invalid start/end time.",
);
} else {
setSchedule(newSchedule);
storage.set("currentSchedule", JSON.stringify(newSchedule));
const conflict = findEventConflict(newSchedule["routines"][currentRoutine]["events"]);
if (conflict) {
Alert.alert("Error", conflict);
return false;
}

setSchedule(newSchedule);
storage.set("currentSchedule", JSON.stringify(newSchedule));
return true;
}

export default function AddEventModal(props: {
Expand Down Expand Up @@ -286,15 +248,16 @@ export default function AddEventModal(props: {
accessibilityLabel="Finish"
className="mt-3 bg-wedgewood-300 rounded shadow-xl p-4 border-2 border-wedgewood-400 active:bg-wedgewood-500 dark:active:bg-wedgewood-800 flex flex-row items-center justify-center dark:bg-wedgewood-950 dark:border-wedgewood-600"
onPress={() => {
addEvent(
const added = addEvent(
props.scheduleDB,
props.setScheduleDB,
props.currentRoutine,
name,
createCustomDate(startTime.getTime()),
createCustomDate(endTime.getTime()),
);
props.setModalVisible(false);

if (added) props.setModalVisible(false);
}}
>
<StyledText className="mr-2 font-poppinsBold text-center text-wedgewood-950 dark:text-wedgewood-300">
Expand Down
27 changes: 21 additions & 6 deletions apps/mobile/src/components/settings/AddRoutineModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,18 @@ function createNewRoutine(
name: string,
weekdays: number[],
) {
if (!name) return Alert.alert("Error", "You must provide a name.");

const newSchedule = { ...schedule };
if (!name) {
Alert.alert("Error", "You must provide a name.");
return false;
}

if (newSchedule["routines"][name]) {
return Alert.alert("Error", "Routine with same name already exists.");
if (schedule["routines"][name]) {
Alert.alert("Error", `A routine named "${name}" already exists. Pick a different name.`);
return false;
}

const newSchedule = JSON.parse(JSON.stringify(schedule)) as UnparsedSchedule;

newSchedule["routines"][name] = {
officialName: name,
days: weekdays,
Expand All @@ -55,6 +59,7 @@ function createNewRoutine(
storage.set("currentSchedule", JSON.stringify(newSchedule));

Alert.alert("Success", "Successfully created a new routine.");
return true;
}

export default function AddRoutineModal(props: {
Expand Down Expand Up @@ -138,7 +143,17 @@ export default function AddRoutineModal(props: {
accessibilityLabel="Finish"
className="mt-4 bg-wedgewood-300 rounded shadow-xl p-4 border-2 border-wedgewood-400 active:bg-wedgewood-500 dark:active:bg-wedgewood-800 flex flex-row items-center justify-center dark:bg-wedgewood-950 dark:border-wedgewood-600"
onPress={() => {
createNewRoutine(props.scheduleDB, props.setScheduleDB, name, weekdays);
const created = createNewRoutine(
props.scheduleDB,
props.setScheduleDB,
name,
weekdays,
);

if (!created) return;

setName("New Routine!");
setWeekdays([]);
props.setModalVisible(false);
}}
>
Expand Down
59 changes: 11 additions & 48 deletions apps/mobile/src/components/settings/EventModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
} from "react-native";
import DateTimePickerModal from "react-native-modal-datetime-picker";

import { findEventConflict } from "@/src/utils/eventValidation";
import storage from "@/src/utils/storage";

import TextModal from "./TextModal";
Expand Down Expand Up @@ -56,45 +57,6 @@ function createCustomDateString(timestamp: number) {
return timeString;
}

function createCustomTime(inputTime: string) {
const currentDate = new Date();

const [inputHourRaw, inputMinuteRaw] = inputTime.split(":");
const inputMinute = inputMinuteRaw.replace(/[A-Za-z]/g, ""); // Remove any non-numeric characters

currentDate.setHours(parseInt(inputHourRaw), parseInt(inputMinute), 0, 0);
return currentDate.getTime();
}

function areEventsValid(events: UnparsedEvent[]) {
if (events.length <= 1) {
return true; // Single event is always valid
}

for (let i = 0; i < events.length; i++) {
const currentEvent = events[i];

const startTime = createCustomTime(currentEvent.startTime);
const endTime = createCustomTime(currentEvent.endTime);

if (startTime >= endTime) {
return false; // End time is not after start time
}

if (i < events.length - 1) {
// Check for event overlap
const nextEvent = events[i + 1];
const nextStartTime = createCustomTime(nextEvent.startTime);

if (endTime > nextStartTime) {
return false; // Events overlap
}
}
}

return true; // All events are valid
}

function modifyEvent(
schedule: UnparsedSchedule,
setSchedule: React.Dispatch<React.SetStateAction<UnparsedSchedule>>,
Expand Down Expand Up @@ -127,15 +89,15 @@ function modifyEventTimes(
newSchedule["routines"][currentRoutine]["events"],
);

if (!areEventsValid(newSchedule["routines"][currentRoutine]["events"])) {
return Alert.alert(
"Error",
"This event overlaps with another event or has an invalid start/end time.",
);
const conflict = findEventConflict(newSchedule["routines"][currentRoutine]["events"]);
if (conflict) {
Alert.alert("Error", conflict);
return false;
}

setSchedule(newSchedule);
storage.set("currentSchedule", JSON.stringify(newSchedule));
return true;
}

function removeEvent(
Expand Down Expand Up @@ -420,10 +382,7 @@ export default function EventModal(props: {
return Alert.alert("Error", "End time must be after start time.");
}

props.setStartTime(updatedStartTime);
props.setEndTime(updatedEndTime);

modifyEventTimes(
const saved = modifyEventTimes(
props.scheduleDB,
props.setScheduleDB,
props.currentRoutine,
Expand All @@ -432,6 +391,10 @@ export default function EventModal(props: {
createCustomDateString(updatedEndTime.getTime()),
);

if (!saved) return;

props.setStartTime(updatedStartTime);
props.setEndTime(updatedEndTime);
props.setModalVisible(false);
}}
>
Expand Down
28 changes: 28 additions & 0 deletions apps/mobile/src/utils/eventValidation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { UnparsedEvent } from "@scheduli/types";

function toTimestamp(inputTime: string) {
const date = new Date();

const [hourRaw, minuteRaw] = inputTime.split(":");
const minute = minuteRaw.replace(/[A-Za-z]/g, "");

date.setHours(parseInt(hourRaw), parseInt(minute), 0, 0);
return date.getTime();
}

export function findEventConflict(events: UnparsedEvent[]) {
for (let i = 0; i < events.length; i++) {
const current = events[i];

if (toTimestamp(current.startTime) >= toTimestamp(current.endTime)) {
return `"${current.name}" has to end after it starts.`;
}

const next = events[i + 1];
if (next && toTimestamp(current.endTime) > toTimestamp(next.startTime)) {
return `"${current.name}" overlaps with "${next.name}". Change one of their times so they don't run at the same time.`;
}
}

return null;
}
Loading