Skip to content
Open
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
465 changes: 465 additions & 0 deletions .checkpoints/checkpoint-20260412-140915.diff

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node-linker=hoisted
1 change: 1 addition & 0 deletions .npmrc 2
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node-linker=hoisted
36 changes: 36 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"deno.enablePaths": ["supabase/functions"],
"deno.lint": true,
"deno.unstable": [
"bare-node-builtins",
"byonm",
"sloppy-imports",
"unsafe-proto",
"webgpu",
"broadcast-channel",
"worker-options",
"cron",
"kv",
"ffi",
"fs",
"http",
"net"
],
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}
134 changes: 107 additions & 27 deletions app/(tabs)/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { BottomSheetModal } from "@gorhom/bottom-sheet";
import { useBottomTabBarHeight } from "@react-navigation/bottom-tabs";
import * as turf from "@turf/turf";
import { Stack } from "expo-router";
import { useCallback, useMemo, useRef, useState, useEffect } from "react";
import { useCallback, useMemo, useRef, useState } from "react";
import { View } from "react-native";
import MapView, { Polygon, Marker, LatLng } from "react-native-maps";
import { useSafeAreaInsets } from "react-native-safe-area-context";
Expand All @@ -19,20 +19,22 @@ import {
useInsertAvoidanceArea,
} from "~/utils/api-hooks";
import useMapIcons from "~/utils/useMapIcons";
import buildingsData from "../../assets/geojson/buildings_simple.json";

import { SearchBar } from "~/components/SearchBar";
import { SearchDropdown } from "~/components/SearchDropdown";
import {
LocationDetailsBottomSheet,
type LocationDetailsBottomSheetRef,
} from "~/components/LocationDetailsBottomSheet";
import { searchPlaces, getPlaceDetails } from "~/utils/googlePlaces";
} from "../../components/LocationDetailsBottomSheet";
import { getPlaceDetails, searchPlaces } from "~/utils/googlePlaces";

export default function Home() {
// hooks
const insets = useSafeAreaInsets();
const mapIcons = useMapIcons();
const bottomTabBarHeight = useBottomTabBarHeight();
const mapRef = useRef<MapView>(null);
const avoidanceAreaBottomSheetRef = useRef<BottomSheetModal>(null);
const poiBottomSheetRef = useRef<BottomSheetModal>(null);
const bottomSheetRef = useRef<BottomSheetModal>(null);
Expand All @@ -46,7 +48,7 @@ export default function Home() {
const [zoomLevel, setZoomLevel] = useState(15);

// Minimum zoom level to show POIs (higher = more zoomed in)
const MIN_ZOOM_FOR_POIS = 16;
const MIN_ZOOM_FOR_POIS = 15;
const [isSearchActive, setIsSearchActive] = useState(false);
const [searchQuery, setSearchQuery] = useState("");

Expand All @@ -56,20 +58,56 @@ export default function Home() {
const { data: POIs } = usePOIs();
const { mutateAsync: insertAvoidanceArea } = useInsertAvoidanceArea();

const testGooglePlaces = async () => {
console.log("Testing Google Places...");
const results = await searchPlaces("Texas Global");
console.log("Search results:", results);

if (results.length > 0) {
const details = await getPlaceDetails(results[0].place_id);
console.log("Place details:", details);
const findCampusBuildingFeature = (
latitude: number,
longitude: number,
placeName?: string,
placeAddress?: string,
) => {
const point = turf.point([longitude, latitude]);
const buildingsAny = buildingsData as any;
const features: any[] = buildingsAny.features ?? [];

const polygonMatch = features.find((feature) =>
feature?.geometry && turf.booleanPointInPolygon(point, feature),
);

if (polygonMatch) {
return polygonMatch;
}

// Only trust strict polygon containment.
// If we cannot confidently match, fall back to the generic location sheet.
return null;
};

useEffect(() => {
testGooglePlaces();
}, []);
const buildPoiFromCampusBuilding = (
placeDetails: any,
buildingFeature: any,
) => {
const buildingAbbr = buildingFeature?.properties?.Building_Abbr;
const buildingName = buildingFeature?.properties?.Description;

if (!buildingAbbr || !buildingName) {
return null;
}

return {
id: `search-${placeDetails.place_id ?? buildingAbbr}`,
poi_type: "accessible_entrance",
location_geojson: {
type: "Point",
coordinates: [
placeDetails.geometry.location.lng,
placeDetails.geometry.location.lat,
],
},
metadata: {
name: buildingName,
bld_name: `(${buildingAbbr}) ${buildingName}`,
},
};
};

const getMapIcon = useCallback(
(poiType: any, metadata: any) => {
Expand Down Expand Up @@ -132,9 +170,10 @@ export default function Home() {
// Handle POI click
const handlePOIPress = (poi: any) => {
if (isReportMode) return;
const currentId = poi.placeId || poi.id;
poiBottomSheetRef.current?.present({ poi });
if (polygonId[0] == 'C') return; // construction areas
bottomSheetRef.current?.present({ id: polygonId });
if (currentId && currentId[0] === 'C') return;
bottomSheetRef.current?.present({ id: currentId });
};

const polygons = useMemo(
Expand Down Expand Up @@ -239,19 +278,59 @@ export default function Home() {
// Close search
setIsSearchActive(false);
setSearchQuery("");


// Resolve missing place_id for recent/manual locations so recenter still works.
let resolvedPlaceId = location.place_id;
if (!resolvedPlaceId) {
const primaryQuery = [location.name, location.address].filter(Boolean).join(" ");
const fallbackQuery = location.name;

const primaryResults = await searchPlaces(primaryQuery);
resolvedPlaceId = primaryResults[0]?.place_id;

if (!resolvedPlaceId && fallbackQuery) {
const fallbackResults = await searchPlaces(fallbackQuery);
resolvedPlaceId = fallbackResults[0]?.place_id;
}
}

// Fetch full place details
if (location.place_id) {
const placeDetails = await getPlaceDetails(location.place_id);
if (resolvedPlaceId) {
const placeDetails = await getPlaceDetails(resolvedPlaceId);

if (placeDetails) {
// TODO: Get user's current location to calculate distance
// For now, using a placeholder
//const distance = "2.4 Mi";

// Open location details bottom sheet with real data
locationBottomSheetRef.current?.present(placeDetails);
const matchingBuilding = findCampusBuildingFeature(
placeDetails.geometry.location.lat,
placeDetails.geometry.location.lng,
placeDetails.name,
placeDetails.formatted_address,
);

mapRef.current?.animateToRegion(
{
latitude: placeDetails.geometry.location.lat,
longitude: placeDetails.geometry.location.lng,
latitudeDelta: 0.006,
longitudeDelta: 0.006,
},
450,
);

const buildingPoi = buildPoiFromCampusBuilding(
placeDetails,
matchingBuilding,
);

if (buildingPoi) {
locationBottomSheetRef.current?.dismiss();
poiBottomSheetRef.current?.present({ poi: buildingPoi });
} else {
// Open the generic sheet when we cannot map the place to an official building.
locationBottomSheetRef.current?.present(placeDetails);
}
}
} else {
console.error("Could not resolve place_id for selected location", location);
}
};

Expand Down Expand Up @@ -310,9 +389,10 @@ export default function Home() {
<LocationDetailsBottomSheet ref={locationBottomSheetRef} />

<MapView
ref={mapRef}
style={{ flex: 1 }}
onPress={handleMapPress}
region={{
initialRegion={{
latitude: 30.282,
longitude: -97.733,
latitudeDelta: 0.01,
Expand Down
2 changes: 1 addition & 1 deletion babel.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ module.exports = function (api) {
],

plugins: [
"react-native-reanimated/plugin",
"react-native-worklets-core/plugin",
"react-native-reanimated/plugin",
],
};
};
Loading