Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
099c937
tag the queries that actually make nw reqs
michaeljwalla Aug 15, 2026
4a193dd
basic state manager and popup
michaeljwalla Aug 15, 2026
163b558
another style (w Empty)
michaeljwalla Aug 16, 2026
c33b8c3
caching mechanism (still need to show)
michaeljwalla Aug 16, 2026
7f34e7a
proxy useRoutes() to attempt to pass cachedRoutes
michaeljwalla Aug 16, 2026
dd75cde
5 hours of coding will save you from 5 minutes of documentation or so…
michaeljwalla Aug 16, 2026
5e302e1
requery loops w backoff and concrete status resolution
michaeljwalla Aug 16, 2026
0bac410
move to ConnectionBar component + relevant changes
michaeljwalla Aug 16, 2026
7ed5c6b
make pill; claude did the anims
michaeljwalla Aug 16, 2026
a3671d4
simpler anim alternative for prior commit
michaeljwalla Aug 16, 2026
75ccc57
dev button for invalidating cache (regen uuids)
michaeljwalla Aug 16, 2026
b774c89
(more) explicit debugging mode for the con man
michaeljwalla Aug 17, 2026
1926396
cache checking funcs
michaeljwalla Aug 17, 2026
b3f3a19
add placeholderData field to both features (for consistency)
michaeljwalla Aug 17, 2026
b89956c
an example proposal basing pages off [id] (which is pretty bad)
michaeljwalla Aug 17, 2026
a842413
initial move to routeCode
michaeljwalla Aug 17, 2026
bdf6fec
some more work to align cached/live state. also surfaced a small bug …
michaeljwalla Aug 17, 2026
eb9b928
connection pill flickering on launch
michaeljwalla Aug 17, 2026
6522989
time-based cache expiry
michaeljwalla Aug 17, 2026
03267b1
utils/cache.ts & connectivity man refactoring
michaeljwalla Aug 17, 2026
276c939
clear cache debug option
michaeljwalla Aug 18, 2026
19aa4f8
spinner discrepancy in favs tab
michaeljwalla Aug 18, 2026
4c13869
bolding on dashed stops and some linebreaks not applying properly
michaeljwalla Aug 19, 2026
f2c51ed
cache load outpacing this.map leads to no zoom-on-launch
michaeljwalla Aug 19, 2026
da298ca
another instance (my loc) of improper instruction formatting
michaeljwalla Aug 19, 2026
3b2a837
planner time field text un-centered on droid
michaeljwalla Aug 19, 2026
da1a187
align connection pill w map controls
michaeljwalla Aug 19, 2026
3c397d1
revert: 4c13869 and da298ca to be applied in new branch
michaeljwalla Aug 19, 2026
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
2 changes: 2 additions & 0 deletions android/app/capacitor.build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ android {

apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
dependencies {
implementation project(':capacitor-app')
implementation project(':capacitor-dialog')
implementation project(':capacitor-filesystem')
implementation project(':capacitor-geolocation')
implementation project(':capacitor-preferences')
Expand Down
6 changes: 6 additions & 0 deletions android/capacitor.settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
include ':capacitor-android'
project(':capacitor-android').projectDir = new File('../node_modules/.pnpm/@capacitor+android@8.3.0_patch_hash=5f151b861cf2cde88167eece495aecb0862a61bea5115b27870_874123a7c3556940d002a867e09a2b84/node_modules/@capacitor/android/capacitor')

include ':capacitor-app'
project(':capacitor-app').projectDir = new File('../node_modules/.pnpm/@capacitor+app@8.1.1_@capacitor+core@8.3.0/node_modules/@capacitor/app/android')

include ':capacitor-dialog'
project(':capacitor-dialog').projectDir = new File('../node_modules/.pnpm/@capacitor+dialog@8.0.1_@capacitor+core@8.3.0/node_modules/@capacitor/dialog/android')

include ':capacitor-filesystem'
project(':capacitor-filesystem').projectDir = new File('../node_modules/.pnpm/@capacitor+filesystem@8.1.2_@capacitor+core@8.3.0/node_modules/@capacitor/filesystem/android')

Expand Down
4 changes: 4 additions & 0 deletions ios/App/CapApp-SPM/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ let package = Package(
],
dependencies: [
.package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", exact: "8.3.0"),
.package(name: "CapacitorApp", path: "../../../node_modules/.pnpm/@capacitor+app@8.1.1_@capacitor+core@8.3.0/node_modules/@capacitor/app"),
.package(name: "CapacitorDialog", path: "../../../node_modules/.pnpm/@capacitor+dialog@8.0.1_@capacitor+core@8.3.0/node_modules/@capacitor/dialog"),
.package(name: "CapacitorFilesystem", path: "../../../node_modules/.pnpm/@capacitor+filesystem@8.1.2_@capacitor+core@8.3.0/node_modules/@capacitor/filesystem"),
.package(name: "CapacitorGeolocation", path: "../../../node_modules/.pnpm/@capacitor+geolocation@8.0.0_@capacitor+core@8.3.0/node_modules/@capacitor/geolocation"),
.package(name: "CapacitorPreferences", path: "../../../node_modules/.pnpm/@capacitor+preferences@8.0.1_@capacitor+core@8.3.0/node_modules/@capacitor/preferences")
Expand All @@ -22,6 +24,8 @@ let package = Package(
dependencies: [
.product(name: "Capacitor", package: "capacitor-swift-pm"),
.product(name: "Cordova", package: "capacitor-swift-pm"),
.product(name: "CapacitorApp", package: "CapacitorApp"),
.product(name: "CapacitorDialog", package: "CapacitorDialog"),
.product(name: "CapacitorFilesystem", package: "CapacitorFilesystem"),
.product(name: "CapacitorGeolocation", package: "CapacitorGeolocation"),
.product(name: "CapacitorPreferences", package: "CapacitorPreferences")
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@
},
"dependencies": {
"@capacitor/android": "8.3.0",
"@capacitor/app": "^8.1.1",
"@capacitor/core": "^8.3.0",
"@capacitor/dialog": "^8.0.1",
"@capacitor/filesystem": "^8.1.2",
"@capacitor/geolocation": "^8.0.0",
"@capacitor/ios": "^8.3.0",
Expand Down
24 changes: 24 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

64 changes: 64 additions & 0 deletions src/lib/components/ConnectionPill.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<script lang="ts">
import { fly } from 'svelte/transition';
import * as Card from './ui/card';
import { onMount } from 'svelte';
import * as Empty from './ui/empty';
import { connectivityManager } from '$lib/managers/connectivity.manager.svelte';
import { CloudOff } from '@lucide/svelte';
import { Spinner } from './ui/spinner';
import { ConnectionStatus } from '$lib/data/types';

type Props = {
animateTime?: number;
initialHideTime?: number; // prevent popup from showing on mount (ex, at homepage startup)
};

const { animateTime = 500, initialHideTime = 0 }: Props = $props();

let mayShow = $state(false);
let contentWidth = $state(0);

const status = $derived.by(connectivityManager.getConnectionStatus);
const isBadStatus = $derived(status !== ConnectionStatus.ONLINE);

onMount(() => {
if (initialHideTime > 0) {
mayShow = false;
setTimeout(() => (mayShow = true), initialHideTime);
}
});
</script>

{#if mayShow && isBadStatus}
{@const Icon = status === ConnectionStatus.OFFLINE ? CloudOff : Spinner}
<!-- top-16/h-12 mirror MapControls' offset and icon-xl button size, so the pill lines up with its first icon -->
<div
class="pointer-events-none fixed inset-x-0 top-16 z-50 flex h-12 items-center justify-center"
style="
padding-left: calc(env(safe-area-inset-left, 0px) + 0.75rem);
padding-right: calc(env(safe-area-inset-right, 0px) + 0.75rem);
"
>
<div
in:fly={{ y: -100, duration: animateTime, opacity: 1 }}
out:fly={{ y: -100, duration: animateTime, opacity: 1 }}
>
<Card.Root
class="flex-row items-center overflow-hidden rounded-full shadow-md transition-[width] duration-300 ease-out motion-reduce:transition-none"
style="width: {contentWidth ? `${contentWidth + 2}px` : 'auto'}"
>
<div
bind:clientWidth={contentWidth}
class="flex w-max shrink-0 flex-row items-center gap-2 py-2 pr-4 pl-2 whitespace-nowrap"
>
<Empty.Media variant="icon" class="mb-0 size-7 rounded-full">
<Icon class="size-4" />
</Empty.Media>
<Empty.Title class="text-sm font-semibold tracking-wide">
{ConnectionStatus.asMessage(status)}
</Empty.Title>
</div>
</Card.Root>
</div>
</div>
{/if}
2 changes: 1 addition & 1 deletion src/lib/components/TimeInput.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,5 @@
value={value.toFormat('HH:mm')}
oninput={handleInput}
onmousedown={(e) => e.stopPropagation()}
class="w-auto appearance-none tabular-nums [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none"
class="w-auto appearance-none text-center tabular-nums [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none [&::-webkit-date-and-time-value]:m-0"
/>
12 changes: 6 additions & 6 deletions src/lib/components/map/MapElements.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import { goto } from '$app/navigation';

const routes = useRoutes();
const drawnRouteIds = $derived(mapManager.drawnRoutes.map((route) => route.id));
const drawnRouteIds = $derived(mapManager.drawnRoutes.map((route) => route.routeCode));
const busLocations = $derived(useVehicles(() => ({ route: mapManager.selectedRoute })));

let userLocation: Location | null = $state(null);
Expand Down Expand Up @@ -58,21 +58,21 @@
flushScheduled = false;
const topmost = requestedRoutes.reduce((a, b) => (b.index > a.index ? b : a)).route;
requestedRoutes.length = 0; //cursed
goto(`/route/${topmost.id}`);
goto(`/route/${topmost.routeCode}`);
}
</script>

{#snippet routeLine(route: Route, index: number)}
{#each route.directions as direction (`${route.id}-${direction.id}`)}
{#each route.directions as direction (`${route.routeCode}-${direction.id}`)}
{@const isSelected =
direction.id === mapManager.selectedDirectionId || mapManager.selectedDirectionId == ''}
{@const isShown = drawnRouteIds.includes(route.id)}
{@const isShown = drawnRouteIds.includes(route.routeCode)}
{@const opacity = isShown ? (isSelected ? 1 : 0.5) : 0}

<MapRoute
coordinates={direction.pathPoints.map((point) => [point.longitude, point.latitude])}
color={getRouteTint(route, themeManager.theme)}
id={`${route.id}-${direction.id}`}
id={`${route.routeCode}-${direction.id}`}
onclick={() => {
if (!isSelected) return; //opacity check instead of isShown for ghost directional routes
requestRoute(route, index);
Expand Down Expand Up @@ -103,7 +103,7 @@
</MapMarker>
{/snippet}

{#each routes?.data as route, i (route.id)}
{#each routes?.data as route, i (route.routeCode)}
{@render routeLine(route, i)}
{/each}

Expand Down
47 changes: 46 additions & 1 deletion src/lib/components/ui/map/MapControls.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@
import { mapManager } from '$lib/managers/map.manager.svelte';
import { cn } from '$lib/utils.js';
import { Geolocation } from '@capacitor/geolocation';
import { Locate, LocateFixed } from '@lucide/svelte';
import { Braces, Locate, LocateFixed, Trash2 } from '@lucide/svelte';
import { getContext } from 'svelte';
import Button from '../button/button.svelte';
import Spinner from '../spinner/spinner.svelte';
import type { MapContext } from './Map.svelte';
import { connectivityManager } from '$lib/managers/connectivity.manager.svelte';
import { Dialog } from '@capacitor/dialog';
import { Capacitor, CapacitorException } from '@capacitor/core';
import { App } from '@capacitor/app';
import type { Route } from '$lib/data/types';
interface Props {
position?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
showZoom?: boolean;
Expand Down Expand Up @@ -67,6 +72,25 @@
waitingForLocation = false;
}
}
async function handleCacheScramble(replace?: Route[]) {
const { message, status } = await connectivityManager.scrambleCacheUUIDS(replace);
await Dialog.alert({
title: 'Cache',
message: message,
});
if (!status) return;
// true = exit
try {
await App.exitApp();
} catch (error) {
if (!(error instanceof CapacitorException)) return;
await Dialog.alert({
title: 'Cache',
message: 'assuming webpage... reloading',
});
window.location.reload();
}
}
</script>

{#if loaded}
Expand All @@ -86,5 +110,26 @@
<Locate class="size-6 stroke-muted-foreground" />
{/if}
</Button>
<!-- for uuid scrambling -->
{#if connectivityManager.debugOptions}
<Button
variant="outline"
size="icon-xl"
onclick={() => handleCacheScramble()}
aria-label="Scramble"
class="dark:bg-card"
>
<Braces class="size-6 stroke-muted-foreground" />
</Button>
<Button
variant="outline"
size="icon-xl"
onclick={() => handleCacheScramble([])}
aria-label="Clear"
class="dark:bg-card"
>
<Trash2 class="size-6 stroke-muted-foreground" />
</Button>
{/if}
</div>
{/if}
9 changes: 9 additions & 0 deletions src/lib/data/api/aggie_spirit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export const useAuthCodeAPI = () => {
const query = createLoggingQuery<string>(() => ({
label: ASAPIQueryKey.AUTH_CODE,
queryKey: [ASAPIQueryKey.AUTH_CODE],
meta: { network: true, auth: true },
queryFn: async () => {
const authCodeB64 = await (await fetch('https://auth.maroonrides.app')).text();
return atob(authCodeB64);
Expand All @@ -60,6 +61,7 @@ export const useAuthTokenAPI = () => {

const query = createDependencyQuery<Headers>(() => ({
queryKey: [ASAPIQueryKey.AUTH_TOKEN],
meta: { network: true },
queryFn: async () => {
var res = await fetch('https://aggiespirit.ts.tamu.edu/', { credentials: 'omit' });

Expand All @@ -84,6 +86,7 @@ export const useRoutePlanAuthTokenAPI = (params: () => { queryString: string })
const { queryString } = params();
return {
queryKey: [ASAPIQueryKey.ROUTE_PLAN_AUTH_TOKEN],
meta: { network: true },
queryFn: async () => {
var res = await fetch(`https://aggiespirit.ts.tamu.edu/TripPlanner/${queryString}`, {
credentials: 'omit',
Expand Down Expand Up @@ -111,6 +114,7 @@ export const useBaseDataAPI = () => {

const query = createDependencyQuery<IGetBaseDataResponse>(() => ({
queryKey: [ASAPIQueryKey.BASE_DATA],
meta: { network: true },
queryFn: async () => {
const baseData = await getBaseData(authTokenQuery.data!);
GetBaseDataResponseSchema.parse(baseData);
Expand All @@ -130,6 +134,7 @@ export const usePatternPathsAPI = () => {

const query = createDependencyQuery<IGetPatternPathsResponse>(() => ({
queryKey: [ASAPIQueryKey.PATTERN_PATHS],
meta: { network: true },
queryFn: async () => {
const baseData = baseDataQuery.data as IGetBaseDataResponse;

Expand Down Expand Up @@ -174,6 +179,7 @@ export const useStopEstimateAPI = (
const { routeKey, directionKey, stopCode } = params();
return {
queryKey: [ASAPIQueryKey.STOP_ESTIMATE, routeKey, directionKey, stopCode],
meta: { network: true },
queryFn: async () => {
const response = await getNextDepartureTimes(
routeKey,
Expand All @@ -200,6 +206,7 @@ export const useTimetableEstimateAPI = (params: () => { stopCode: string; date:
const { stopCode, date } = params();
return {
queryKey: [ASAPIQueryKey.TIMETABLE_ESTIMATE, stopCode, moment(date).format('YYYY-MM-DD')],
meta: { network: true },
queryFn: async () => {
const response = await getStopEstimates(stopCode, date, authTokenQuery.data!);
GetStopEstimatesResponseSchema.parse(response);
Expand All @@ -221,6 +228,7 @@ export const useStopScheduleAPI = (params: () => { stopCode: string; date: Date
const { stopCode, date } = params();
return {
queryKey: [ASAPIQueryKey.STOP_SCHEDULE, stopCode, moment(date).format('YYYY-MM-DD')],
meta: { network: true },
queryFn: async () => {
const response = await getStopSchedules(stopCode, date, authTokenQuery.data!);
GetStopSchedulesResponseSchema.parse(response);
Expand All @@ -242,6 +250,7 @@ export const useVehiclesAPI = (params: () => { routeKey: string }) => {
const { routeKey } = params();
return {
queryKey: [ASAPIQueryKey.VEHICLES, routeKey],
meta: { network: true },
queryFn: async () => {
let busesResponse = (await getVehicles(
[routeKey],
Expand Down
2 changes: 2 additions & 0 deletions src/lib/data/api/route_planning.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export const useSearchSuggestionAPI = (params: () => { query: string }) => {
const { query } = params();
return {
queryKey: [ASRoutePlanQueryKey.SEARCH_SUGGESTION, query],
meta: { network: true },
queryFn: async () => {
// we need data from pattern paths to get the stop GPS locations
// This is limitation of the API where we can't get the GPS location of a stop directly
Expand Down Expand Up @@ -93,6 +94,7 @@ export const useTripPlanAPI = (
const { origin, destination, date, deadline } = params();
return {
queryKey: [ASRoutePlanQueryKey.TRIP_PLAN, origin, destination, date, deadline],
meta: { network: true },
queryFn: async () => {
let response = await getTripPlan(
routePlanAuthToken.data!,
Expand Down
Loading