Skip to content

Commit

Permalink
Merge pull request #154 from Reveille-Rides/starter-projects/stop-cal…
Browse files Browse the repository at this point in the history
…lout-upgrades

Stop Callout Upgrades
  • Loading branch information
bwees authored Mar 18, 2024
2 parents f6ec61f + 078c6de commit 4e21ba0
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 36 deletions.
88 changes: 62 additions & 26 deletions app/components/map/StopCallout.tsx
Original file line number Diff line number Diff line change
@@ -1,49 +1,85 @@
import React, { memo, useState, useCallback } from 'react'
import { View, Text, LayoutChangeEvent } from 'react-native'
import React, { memo } from 'react'
import { View, Text, ActivityIndicator, TouchableOpacity } from 'react-native'
import { Callout } from 'react-native-maps'
import BusIcon from '../ui/BusIcon'
import { IDirection, IMapRoute, IStop } from '../../../utils/interfaces'
import AmenityRow from "../ui/AmenityRow";
import { useStopEstimate } from 'app/data/api_query'
import moment from 'moment'
import CalloutTimeBubble from '../ui/CalloutTimeBubble'
import { lightMode } from 'app/theme'
import AmenityRow from '../ui/AmenityRow'
import useAppStore from 'app/data/app_state'

interface Props {
stop: IStop
tintColor: string
route: IMapRoute
direction: IDirection
stop: IStop
tintColor: string
route: IMapRoute
direction: IDirection
}

// Stop callout with amentities
// Stop callout with time bubbles
const StopCallout: React.FC<Props> = ({ stop, tintColor, route, direction }) => {

// Calculate size of callout based on the contentSize
const [contentSize, setContentSize] = useState([100, 15]);
const scrollToStop = useAppStore(state => state.scrollToStop);

const { data: estimate } = useStopEstimate(route.key, direction.key, stop.stopCode);

const handleLayout = useCallback((event: LayoutChangeEvent) => {
const { width, height } = event.nativeEvent.layout;

setContentSize([width, height]);
}, [setContentSize]);
const { data: estimate, isLoading } = useStopEstimate(route.key, direction.key, stop.stopCode);

return (
<Callout
style={{
<Callout
style={{
alignItems: 'center',
width: contentSize[0],
height: contentSize[1],
justifyContent: 'center',
width: 215,
height: 50,
zIndex: 1000,
elevation: 1000
}}
>
<View onLayout={handleLayout}>
<View style={{ flexDirection: "row", justifyContent: "center", alignItems: "center", alignSelf: "flex-start" }} >
<View>
<TouchableOpacity
style={{
flexDirection: "row",
justifyContent: "center",
alignItems: "center",
alignSelf: "flex-start"
}}
onPress={() => { scrollToStop(stop) }}
>
<BusIcon name={route.shortName} color={tintColor} isCallout={true} style={{ marginRight: 8 }} />
<Text style={{ maxWidth: 200, fontWeight: 'bold' }} numberOfLines={1}>{stop.name}</Text>
</View>
<Text style={{ flex: 1, fontWeight: 'bold' }} numberOfLines={2} >{stop.name}</Text>
<AmenityRow amenities={estimate?.amenities || []} color={lightMode.subtitle} size={18}/>
</TouchableOpacity>

<AmenityRow amenities={estimate?.amenities ?? []} color="gray" size={20} style={{ marginTop: 4 }} />
{ estimate?.routeDirectionTimes[0]?.nextDeparts.length !== 0 ?
<View style={{
flexDirection: "row",
justifyContent: "center",
alignItems: "center",
alignSelf: "flex-start",
marginTop: 8
}}>
<View style={{flex: 1}} />
{ estimate?.routeDirectionTimes[0]?.nextDeparts.map((departureTime, index) => {
const date = moment(departureTime.estimatedDepartTimeUtc ?? departureTime.scheduledDepartTimeUtc ?? "");
const relative = date.diff(moment(), "minutes");
return (
<CalloutTimeBubble
key={index}
time={relative <= 0 ? "Now" : relative.toString() + " min"}
color={index == 0 ? tintColor + "60" : lightMode.nextStopBubble}
textColor={index == 0 ? tintColor : lightMode.text}
live={departureTime.estimatedDepartTimeUtc == null ? false : true}
/>
)
})}
<View style={{flex: 1}} />
</View>
: ( isLoading ?
<ActivityIndicator style={{ marginTop: 8 }} />
:
<Text style={{ marginTop: 8, alignSelf: "center", color: lightMode.subtitle, fontSize: 12 }}>No upcoming departures</Text>
)
}
</View>
</Callout>
)
Expand Down
22 changes: 21 additions & 1 deletion app/components/sheets/RouteDetails.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useEffect, useState } from "react";
import { View, Text, TouchableOpacity, NativeSyntheticEvent } from "react-native";
import { BottomSheetModal, BottomSheetView, BottomSheetFlatList } from "@gorhom/bottom-sheet";
import { BottomSheetModal, BottomSheetView, BottomSheetFlatList, BottomSheetFlatListMethods } from "@gorhom/bottom-sheet";
import SegmentedControl, { NativeSegmentedControlIOSChangeEvent } from "@react-native-segmented-control/segmented-control";
import { Ionicons } from '@expo/vector-icons';
import { IMapRoute, IPatternPath, IStop } from "../../../utils/interfaces";
Expand All @@ -18,13 +18,19 @@ interface SheetProps {

// Display details when a route is selected
const RouteDetails: React.FC<SheetProps> = ({ sheetRef }) => {

const flatListRef = React.useRef<BottomSheetFlatListMethods>(null);

const currentSelectedRoute = useAppStore((state) => state.selectedRoute);
const clearSelectedRoute = useAppStore((state) => state.clearSelectedRoute);

const [futurePosition, setFuturePosition] = useState(-1);

const setSelectedRouteDirection = useAppStore(state => state.setSelectedRouteDirection);
const setSelectedStop = useAppStore(state => state.setSelectedStop);
const setPoppedUpStopCallout = useAppStore(state => state.setPoppedUpStopCallout);
const selectedRouteDirection = useAppStore(state => state.selectedRouteDirection);
const setScrollToStop = useAppStore(state => state.setScrollToStop);
const theme = useAppStore(state => state.theme);

const { data: stopEstimates } = useStopEstimate(
Expand All @@ -33,6 +39,13 @@ const RouteDetails: React.FC<SheetProps> = ({ sheetRef }) => {
currentSelectedRoute?.patternPaths[0]?.patternPoints[0]?.stop?.stopCode ?? ""
)

setScrollToStop((stop) => {
sheetRef.current?.snapToIndex(2);
const index = processedStops.findIndex(st => st.stopCode === stop.stopCode);

setFuturePosition(index);
})


// Controls SegmentedControl
const [selectedDirectionIndex, setSelectedDirectionIndex] = useState(0);
Expand Down Expand Up @@ -121,6 +134,12 @@ const RouteDetails: React.FC<SheetProps> = ({ sheetRef }) => {
enablePanDownToClose={false}
backgroundStyle={{ backgroundColor: theme.background }}
handleIndicatorStyle={{backgroundColor: theme.divider}}
onChange={() => {
if (futurePosition !== -1) {
flatListRef.current?.scrollToIndex({ index: futurePosition, animated: true });
setFuturePosition(-1);
}
}}
>
{selectedRoute &&
<BottomSheetView>
Expand Down Expand Up @@ -158,6 +177,7 @@ const RouteDetails: React.FC<SheetProps> = ({ sheetRef }) => {

{ selectedRoute &&
<BottomSheetFlatList
ref={flatListRef}
data={processedStops}
extraData={stopEstimates?.routeDirectionTimes[0]}
style={{ height: "100%" }}
Expand Down
37 changes: 37 additions & 0 deletions app/components/ui/CalloutTimeBubble.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React from 'react'
import { View, Text } from 'react-native'
import { MaterialCommunityIcons } from '@expo/vector-icons';

interface Props {
time: string
color: string,
textColor?: string,
live?: boolean
}

const CalloutTimeBubble: React.FC<Props> = ({time, color, textColor, live}) => {
return (
<View
style={{
backgroundColor: color,
borderRadius: 6,
alignItems: 'center',
justifyContent: "center",
alignSelf: 'center',
flexDirection: "row",
padding: 4,
paddingHorizontal: 8,
marginRight: 4
}}>
<Text style={{fontSize: 12, textAlign: 'center', fontWeight: '600', color: textColor ?? 'white' }}>
{time}
</Text>

{ live &&
<MaterialCommunityIcons name="rss" size={12} color={textColor ?? "white"} style={{marginRight: -2, paddingLeft: 1, alignSelf: "flex-start"}} />
}
</View>
)
}

export default CalloutTimeBubble;
12 changes: 6 additions & 6 deletions app/components/ui/StopCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const StopCell: React.FC<Props> = ({ stop, route, direction, color, disabled, se

const estimate = stopEstimate.routeDirectionTimes[0]!;

let totalDeviation = 0;
let deviation = 0;

for (const departTime of estimate.nextDeparts) {
const estimatedTime = moment(departTime.estimatedDepartTimeUtc ?? "");
Expand All @@ -43,23 +43,23 @@ const StopCell: React.FC<Props> = ({ stop, route, direction, color, disabled, se
const delayLength = estimatedTime.diff(scheduledTime, "seconds");

if (!isNaN(delayLength)) {
totalDeviation += delayLength;
deviation = delayLength;
break;
}
}

const avgDeviation = totalDeviation / estimate.nextDeparts.length / (60);
const roundedDeviation = Math.round(avgDeviation);
const roundedDeviation = Math.round(deviation / 60);

if (estimate.directionKey === "") {
setStatus('Loading');
} else if (estimate.nextDeparts.length === 0) {
setStatus("No times to show");
setStatus("No upcoming departures");
} else if (roundedDeviation > 0) {
setStatus(`${roundedDeviation} ${roundedDeviation > 1 ? "minutes" : "minute"} late`);
} else if (roundedDeviation < 0) {
setStatus(`${Math.abs(roundedDeviation)} ${Math.abs(roundedDeviation) > 1 ? "minutes" : "minute"} early`);
} else {
setStatus('On Time');
setStatus('On time');
}
}, [stopEstimate]);

Expand Down
12 changes: 9 additions & 3 deletions app/data/app_state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ interface AppState {
selectedStop: IStop | null,
setSelectedStop: (selectedStop: IStop | null) => void,

// TODO: Switch to Provider Functions
// TODO: Switch to ContextProvider Functions
presentSheet: (sheet: "routeDetails" | "alerts" | "stopTimetable" | "settings" | "alertsDetail") => void
setPresentSheet: (presentSheet: (sheet: "routeDetails" | "alerts" | "stopTimetable" | "settings" | "alertsDetail") => void) => void

Expand All @@ -32,12 +32,15 @@ interface AppState {
selectedTimetableDate: Date | null,
setSelectedTimetableDate: (selectedTimetableDate: Date | null) => void

// TODO: Switch to Provider Functions
// TODO: Switch to Context Provider Functions
zoomToStopLatLng: (lat: number, lng: number) => void
setZoomToStopLatLng: (zoomToStopLatLng: (lat: number, lng: number) => void) => void

poppedUpStopCallout: IStop | null,
setPoppedUpStopCallout: (poppedUpStopCallout: IStop | null) => void

scrollToStop: (stop: IStop) => void
setScrollToStop: (scrollToStop: (stop: IStop) => void) => void
}

const useAppStore = create<AppState>()((set) => ({
Expand Down Expand Up @@ -74,7 +77,10 @@ const useAppStore = create<AppState>()((set) => ({
setZoomToStopLatLng: (zoomToStopLatLng) => set(() => ({ zoomToStopLatLng })),

poppedUpStopCallout: null,
setPoppedUpStopCallout: (poppedUpStopCallout) => set(() => ({ poppedUpStopCallout }))
setPoppedUpStopCallout: (poppedUpStopCallout) => set(() => ({ poppedUpStopCallout })),

scrollToStop: (stop) => {console.log(stop)},
setScrollToStop: (scrollToStop) => set(() => ({ scrollToStop: scrollToStop }))
}));

export default useAppStore;

0 comments on commit 4e21ba0

Please sign in to comment.