diff --git a/App.js b/App.js index 352ecbe..4e1a2fe 100644 --- a/App.js +++ b/App.js @@ -3,7 +3,7 @@ import React from 'react'; import { Provider as PaperProvider } from 'react-native-paper'; import { RecoilRoot } from 'recoil'; -import Main from './Main'; +import Main from './src/Main'; const App = () => { React.useEffect(() => { diff --git a/app.config.js b/app.config.js index e3812f6..b31e3df 100644 --- a/app.config.js +++ b/app.config.js @@ -2,7 +2,7 @@ module.exports = { expo: { name: 'Ames Ride', slug: 'AmesRide', - version: '1.0.4', + version: '1.0.5', orientation: 'portrait', icon: './assets/icon.png', userInterfaceStyle: 'light', @@ -20,7 +20,7 @@ module.exports = { supportsTablet: true, }, android: { - versionCode: 6, + versionCode: 7, config: { googleMaps: { apiKey: process.env.GOOGLE_MAPS_API_KEY, diff --git a/components/Map/components/Stops.js b/components/Map/components/Stops.js deleted file mode 100644 index 74d7483..0000000 --- a/components/Map/components/Stops.js +++ /dev/null @@ -1,88 +0,0 @@ -import React from 'react'; -import Svg, { Path } from 'react-native-svg'; -import { useRecoilValue } from 'recoil'; - -import { favoriteStopsState } from '../../../state/atoms'; -import { currentRouteStopDetailsState, favoriteStopDetailsState } from '../../../state/selectors'; -import ImagePin from './ImagePin'; - -const Stops = ({ onPress }) => { - const favoriteStopIDs = useRecoilValue(favoriteStopsState); - const favoriteStopDetails = useRecoilValue(favoriteStopDetailsState); - const stops = useRecoilValue(currentRouteStopDetailsState); - - if (stops && stops.length > 0) { - return stops.map((s, i) => { - const isFavorite = favoriteStopIDs.has(s.RtpiNumber); - // the seprate rendering of ImagePin is required to ensure a rerender of the element - // otherwise performance will be 0 fps - if (isFavorite) - return ( - - - - ); - return ( - - - - ); - }); - } - - if (favoriteStopDetails && favoriteStopDetails.length > 0) { - return favoriteStopDetails.map((s, i) => ( - - - - )); - } - - return null; -}; - -const StopImage = (props) => ( - - - - -); - -const FavoriteImage = (props) => ( - - - - -); - -export default Stops; diff --git a/components/StopInfo/components/StopDetailsView.js b/components/StopInfo/components/StopDetailsView.js deleted file mode 100644 index 16fedfc..0000000 --- a/components/StopInfo/components/StopDetailsView.js +++ /dev/null @@ -1,204 +0,0 @@ -import { Text } from '@ui-kitten/components'; -import React from 'react'; -import { AppState, ScrollView, View } from 'react-native'; -import { Button, Menu } from 'react-native-paper'; -import GestureRecognizer from 'react-native-swipe-gestures'; -import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; -import Pressable from 'react-native/Libraries/Components/Pressable/Pressable'; -import { useRecoilValue } from 'recoil'; - -import { - currentStopState, - dispatcherState, - favoriteRoutesState, - loadingArrivalsState, - routePatternsState, - routesState, - userSettingsState, -} from '../../../state/atoms'; -import { isCurrentStopFavorite, upcomingArrivalsSorted } from '../../../state/selectors'; -import LoadingIndicator from '../../ArrivalsLoadingIndicator'; - -const StopDetailsView = () => { - const stop = useRecoilValue(currentStopState); - const upcomingArrivals = useRecoilValue(upcomingArrivalsSorted); - const routes = useRecoilValue(routesState); - const routePatterns = useRecoilValue(routePatternsState); - const dispatcher = useRecoilValue(dispatcherState); - const settings = useRecoilValue(userSettingsState); - const favoriteRouteIDs = useRecoilValue(favoriteRoutesState); - const currentStopFavorite = useRecoilValue(isCurrentStopFavorite); - const isLoadingArrivals = useRecoilValue(loadingArrivalsState); - const [menuOpen, setMenuOpen] = React.useState(false); - - // used so text doesn't disappear when sliding out - const [cached, setCached] = React.useState({ stopName: '', arrivals: [] }); - - const storeStopNameInCache = () => { - setCached((c) => ({ ...c, stopName: stop.Name })); - }; - - const toggleFilterSetting = () => { - dispatcher?.toggleUserSetting('showFavoriteArrivalsOnly'); - }; - - const openMenu = () => { - setMenuOpen(true); - }; - const closeMenu = () => { - setMenuOpen(false); - }; - - const handleUpdatedStop = () => { - if (!stop) return; - - storeStopNameInCache(); - // fetch bus and upcoming arrivals when app comes back into foreground - const subscription = AppState.addEventListener('change', (nextAppState) => { - if (nextAppState === 'active') { - dispatcher?.fetchUpcomingArrivals(stop); - } - }); - - const interval = fetchUpcomingArrivalsOnInterval(stop, dispatcher); - return () => { - clearInterval(interval); - subscription.remove(); - }; - }; - - const storeArrivalsInCache = () => { - if (!upcomingArrivals) return; - - setCached((c) => ({ ...c, arrivals: upcomingArrivals })); - }; - - const favoriteStop = () => { - dispatcher?.toggleFavoriteStop(stop.RtpiNumber); - }; - - React.useEffect(handleUpdatedStop, [stop]); - React.useEffect(storeArrivalsInCache, [upcomingArrivals]); - - return ( - - - - - {stop?.Name || cached.stopName} - - - - } - onDismiss={closeMenu}> - - - - - - - - - - - Upcoming Arrivals{settings.showFavoriteArrivalsOnly && ' (favorite routes)'} - - {upcomingArrivals === null ? null : upcomingArrivals.length === 0 ? ( - No upcoming arrivals. - ) : ( - upcomingArrivals.map((arrival) => { - const r = routes[routePatterns[arrival.RouteID]]; - - if (settings?.showFavoriteArrivalsOnly && !favoriteRouteIDs.has(r.ID)) return null; - - const arrivalDate = new Date(new Date().getTime()); - const minutes = Number.parseInt( - arrival.ArriveTime.substring( - arrival.ArriveTime.indexOf(':') + 1, - arrival.ArriveTime.indexOf(':') + 3 - ) - ); - arrivalDate.setMinutes(minutes); - let hours = Number.parseInt( - arrival.ArriveTime.substring(0, arrival.ArriveTime.indexOf(':')) - ); - if (hours == 12 && arrival.ArriveTime.endsWith('AM')) hours = 0; - arrivalDate.setHours(hours); - - if (new Date().getHours() >= 12 && arrival.ArriveTime.endsWith('AM')) { - arrivalDate.setTime(arrivalDate.getTime() + 24 * 60 * 60 * 1000); - } else if (arrival.ArriveTime.endsWith('PM')) { - arrivalDate.setTime(arrivalDate.getTime() + 12 * 60 * 60 * 1000); - } - - const diffMins = Math.round((arrivalDate - new Date()) / 1000 / 60); - - if (diffMins > 180) return null; - - return ( - { - dispatcher?.updateCurrentRoute(r.ID, false); - }}> - - - {r.DisplayName} - {arrival.ArriveTime} ( - {diffMins > 1 - ? diffMins + ' minutes' - : diffMins === 1 - ? '1 minute' - : 'arriving'} - ) - - - - ); - }) - )} - - - - ); -}; - -const fetchUpcomingArrivalsOnInterval = (stop, dispatcher) => { - const fetchUpcomingArrivals = () => dispatcher?.fetchUpcomingArrivals(stop); - fetchUpcomingArrivals(); - return setInterval(fetchUpcomingArrivals, 15 * 1000); -}; - -export default StopDetailsView; diff --git a/package.json b/package.json index aae3516..663ce78 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ames-ride", - "version": "1.0.4", + "version": "1.0.5", "main": "node_modules/expo/AppEntry.js", "license": "GNU3", "scripts": { diff --git a/Main.js b/src/Main.js similarity index 100% rename from Main.js rename to src/Main.js diff --git a/components/ArrivalsLoadingIndicator.js b/src/components/LoadingIndicator.js similarity index 100% rename from components/ArrivalsLoadingIndicator.js rename to src/components/LoadingIndicator.js diff --git a/components/Map/Map.js b/src/components/Map/Map.js similarity index 75% rename from components/Map/Map.js rename to src/components/Map/Map.js index c9afa6b..7e224ed 100644 --- a/components/Map/Map.js +++ b/src/components/Map/Map.js @@ -14,6 +14,13 @@ const { width, height } = Dimensions.get('window'); const FETCH_BUS_SECONDS_INTERVAL = 5000; +const isuCampusRegion = { + latitude: 42.02663, + longitude: -93.6466, + latitudeDelta: 0.01, + longitudeDelta: 0.01, +}; + const Map = () => { const route = useRecoilValue(currentRoute); const location = useRecoilValue(userLocationState); @@ -21,32 +28,32 @@ const Map = () => { const mapRef = useRef(null); - React.useEffect(() => { - const interval = fetchVehiclesOnInterval(route, dispatcher); + const updateVehiclesOnForeground = () => { + const fetchVehiclesInterval = fetchVehiclesOnInterval(route, dispatcher); // fetch bus arrivals when app comes back into foreground - const subscription = AppState.addEventListener('change', (nextAppState) => { + const appToForegroundSubscription = AppState.addEventListener('change', (nextAppState) => { if (nextAppState === 'active') { fetchVehicle(route, dispatcher); } }); return () => { - clearInterval(interval); - subscription.remove(); + clearInterval(fetchVehiclesInterval); + appToForegroundSubscription.remove(); }; - }, [route]); + }; - React.useEffect(() => { + const getCurrentUserLocation = () => { (async () => { const location = await Location.getCurrentPositionAsync(); dispatcher?.setUserLocation(location); })().catch(() => { console.log('No location permissions granted.'); }); - }, []); + }; - React.useEffect(() => { + const moveMapToUser = () => { if (!location) return; mapRef.current.animateToRegion({ @@ -55,22 +62,17 @@ const Map = () => { longitudeDelta: 0.01, latitudeDelta: 0.01, }); - }, [location]); - - const setStop = (stop) => { - dispatcher?.setCurrentStop(stop); }; + React.useEffect(updateVehiclesOnForeground, [route]); + React.useEffect(getCurrentUserLocation, []); + React.useEffect(moveMapToUser, [location]); + return ( { showsIndoors={false} showsTraffic={false}> - + diff --git a/components/Map/components/ImagePin.js b/src/components/Map/components/ImagePin.js similarity index 100% rename from components/Map/components/ImagePin.js rename to src/components/Map/components/ImagePin.js diff --git a/components/Map/components/RouteLine.js b/src/components/Map/components/RouteLine.js similarity index 69% rename from components/Map/components/RouteLine.js rename to src/components/Map/components/RouteLine.js index 4129262..992e276 100644 --- a/components/Map/components/RouteLine.js +++ b/src/components/Map/components/RouteLine.js @@ -1,14 +1,17 @@ +import React from 'react'; import { Polyline } from 'react-native-maps'; import { useRecoilValue } from 'recoil'; import { routesState } from '../../../state/atoms'; +import { ALL_ROUTES, FAVORITE_ROUTES } from '../../../state/constants'; const RouteLine = ({ route }) => { let stops = []; const routes = useRecoilValue(routesState); - if (route.ID === -2) return null; - if (route.ID === -1) { + if (route.ID === FAVORITE_ROUTES) return null; + + if (route.ID === ALL_ROUTES) { stops = Object.values(routes); } else { stops = [route]; @@ -24,4 +27,4 @@ const RouteLine = ({ route }) => { )); }; -export default RouteLine; +export default React.memo(RouteLine); diff --git a/src/components/Map/components/Stops.js b/src/components/Map/components/Stops.js new file mode 100644 index 0000000..2a3420b --- /dev/null +++ b/src/components/Map/components/Stops.js @@ -0,0 +1,60 @@ +import React from 'react'; +import { useRecoilValue } from 'recoil'; + +import { dispatcherState, favoriteStopsState } from '../../../state/atoms'; +import { currentRouteStopDetailsState, favoriteStopDetailsState } from '../../../state/selectors'; +import FavoriteStopImage from '../../icons/FavoriteStopImage'; +import RegularStopImage from '../../icons/RegularStopImage'; +import ImagePin from './ImagePin'; + +const Stops = () => { + const favoriteStopIDs = useRecoilValue(favoriteStopsState); + const favoriteStopDetails = useRecoilValue(favoriteStopDetailsState); + const stops = useRecoilValue(currentRouteStopDetailsState); + const dispatcher = useRecoilValue(dispatcherState); + + const setActiveStop = (stop) => { + dispatcher.setCurrentStop(stop); + }; + + if (stops && stops.length > 0) { + return stops.map((s, i) => { + const isFavorite = favoriteStopIDs.has(s.RtpiNumber); + // the seprate rendering of ImagePin is required to ensure a rerender of the element + // otherwise performance will be 0 fps + if (isFavorite) + return ( + + + + ); + return ( + + + + ); + }); + } + + if (favoriteStopDetails && favoriteStopDetails.length > 0) { + return favoriteStopDetails.map((s, i) => ( + + + + )); + } + + return null; +}; + +export default React.memo(Stops); diff --git a/components/Map/components/VehicleView.js b/src/components/Map/components/VehicleView.js similarity index 90% rename from components/Map/components/VehicleView.js rename to src/components/Map/components/VehicleView.js index f6efda9..6b68796 100644 --- a/components/Map/components/VehicleView.js +++ b/src/components/Map/components/VehicleView.js @@ -1,10 +1,12 @@ +import React from 'react'; import { Image, View } from 'react-native'; import { Marker } from 'react-native-maps'; -import busImage from '../../../assets/arrow.png'; +import busImage from '../../../../assets/arrow.png'; const VehicleView = ({ details }) => { const transformMarkerDegrees = calculateMarkerRotation(details); + return ( { return 0; }; -export default VehicleView; +export default React.memo(VehicleView); diff --git a/components/Map/components/Vehicles.js b/src/components/Map/components/Vehicles.js similarity index 65% rename from components/Map/components/Vehicles.js rename to src/components/Map/components/Vehicles.js index 68ba293..fd0ff5c 100644 --- a/components/Map/components/Vehicles.js +++ b/src/components/Map/components/Vehicles.js @@ -2,21 +2,22 @@ import React from 'react'; import { ToastAndroid } from 'react-native'; import { useRecoilValue } from 'recoil'; -import { currentRouteRowState, vehicleLocationState } from '../../../state/atoms'; +import { vehicleLocationState } from '../../../state/atoms'; +import { isIndividualRoute } from '../../../state/selectors'; import VehicleView from './VehicleView'; const Vehicles = () => { const vehicles = useRecoilValue(vehicleLocationState); - const currentRouteID = useRecoilValue(currentRouteRowState); + const isRegularRoute = useRecoilValue(isIndividualRoute); React.useEffect(() => { if (vehicles === null || vehicles.length > 0) return; ToastAndroid.show('No busses found on route.', ToastAndroid.SHORT); }, [vehicles]); - if (currentRouteID < 0) return null; + if (!isRegularRoute) return null; return vehicles?.map((v) => ); }; -export default Vehicles; +export default React.memo(Vehicles); diff --git a/components/RouteSelect.js b/src/components/RouteSelect.js similarity index 93% rename from components/RouteSelect.js rename to src/components/RouteSelect.js index 27fcba8..d7d2ce3 100644 --- a/components/RouteSelect.js +++ b/src/components/RouteSelect.js @@ -12,8 +12,9 @@ import { favoriteRoutesState, loadingVehiclesState, } from '../state/atoms'; +import { ALL_ROUTES, FAVORITE_ROUTES } from '../state/constants'; import { favoriteRoutesOnlyState, routesSortedState } from '../state/selectors'; -import LoadingIndicator from './ArrivalsLoadingIndicator'; +import LoadingIndicator from './LoadingIndicator'; const setup = async () => { await localForage.defineDriver(fsDriver); @@ -43,8 +44,8 @@ const RouteSelect = () => { React.useEffect(() => { setRouteList([ - { label: 'Favorite Stops', value: -2, key: 'fav', index: 0 }, - { label: 'All Stops/Routes', value: -1, key: 'all', index: 1 }, + { label: 'Favorite Stops', value: FAVORITE_ROUTES, key: 'fav', index: 0 }, + { label: 'All Stops/Routes', value: ALL_ROUTES, key: 'all', index: 1 }, ...favoriteRoutes.map((r, i) => ({ label: r.DisplayName, value: r.ID, @@ -70,7 +71,7 @@ const RouteSelect = () => { const handleSelect = (index) => { setShowDropDown(false); - if (index == currentRoute) { + if (index === currentRoute) { return; } if (index === 0) dispatcher?.updateCurrentRoute(null); diff --git a/components/StopInfo/StopInfo.js b/src/components/StopInfo/StopInfo.js similarity index 100% rename from components/StopInfo/StopInfo.js rename to src/components/StopInfo/StopInfo.js diff --git a/components/StopInfo/components/BottomSheet.js b/src/components/StopInfo/components/BottomSheet.js similarity index 100% rename from components/StopInfo/components/BottomSheet.js rename to src/components/StopInfo/components/BottomSheet.js diff --git a/src/components/StopInfo/components/StopDetailsView.js b/src/components/StopInfo/components/StopDetailsView.js new file mode 100644 index 0000000..26d401a --- /dev/null +++ b/src/components/StopInfo/components/StopDetailsView.js @@ -0,0 +1,53 @@ +import React from 'react'; +import { AppState, ScrollView, View } from 'react-native'; +import { useRecoilValue } from 'recoil'; + +import { currentStopState, dispatcherState, loadingArrivalsState } from '../../../state/atoms'; +import LoadingIndicator from '../../LoadingIndicator'; +import TopBanner from './TopBanner'; +import UpcomingArrivals from './UpcomingArrivals'; + +const StopDetailsView = () => { + const stop = useRecoilValue(currentStopState); + const dispatcher = useRecoilValue(dispatcherState); + const isLoadingArrivals = useRecoilValue(loadingArrivalsState); + + const handleUpdatedStop = () => { + if (!stop) return; + + // fetch upcoming arrivals when app comes back into foreground + const subscription = AppState.addEventListener('change', (nextAppState) => { + if (nextAppState === 'active') { + dispatcher?.fetchUpcomingArrivals(stop); + } + }); + + const interval = fetchUpcomingArrivalsOnInterval(stop, dispatcher); + return () => { + clearInterval(interval); + subscription.remove(); + }; + }; + + React.useEffect(handleUpdatedStop, [stop]); + + return ( + + + + + + + + + + ); +}; + +const fetchUpcomingArrivalsOnInterval = (stop, dispatcher) => { + const fetchUpcomingArrivals = () => dispatcher?.fetchUpcomingArrivals(stop); + fetchUpcomingArrivals(); + return setInterval(fetchUpcomingArrivals, 15 * 1000); +}; + +export default StopDetailsView; diff --git a/src/components/StopInfo/components/TopBanner.js b/src/components/StopInfo/components/TopBanner.js new file mode 100644 index 0000000..7bb0433 --- /dev/null +++ b/src/components/StopInfo/components/TopBanner.js @@ -0,0 +1,94 @@ +import { Text } from '@ui-kitten/components'; +import React from 'react'; +import { View } from 'react-native'; +import { Button, Menu } from 'react-native-paper'; +import GestureRecognizer from 'react-native-swipe-gestures'; +import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; +import { useRecoilValue } from 'recoil'; + +import { dispatcherState, currentStopState, userSettingsState } from '../../../state/atoms'; +import { isCurrentStopFavorite } from '../../../state/selectors'; + +const TopBanner = ({ stop }) => { + const dispatcher = useRecoilValue(dispatcherState); + const currentStopFavorite = useRecoilValue(isCurrentStopFavorite); + const settings = useRecoilValue(userSettingsState); + + const [menuOpen, setMenuOpen] = React.useState(false); + + // used so text doesn't disappear when sliding out + const [cached, setCached] = React.useState(''); + + const toggleFilterSetting = () => { + dispatcher?.toggleUserSetting('showFavoriteArrivalsOnly'); + }; + + const openMenu = () => { + setMenuOpen(true); + }; + const closeMenu = () => { + setMenuOpen(false); + }; + + const favoriteStop = () => { + dispatcher?.toggleFavoriteStop(stop.RtpiNumber); + }; + + const storeStopNameInCache = () => { + setCached(stop.Name); + }; + + const handleUpdatedStop = () => { + if (!stop) return; + + storeStopNameInCache(); + }; + + React.useEffect(handleUpdatedStop, [stop]); + + return ( + + + + {stop?.Name || cached} + + + + } + onDismiss={closeMenu}> + + + + + + + ); +}; + +export default React.memo(TopBanner); diff --git a/src/components/StopInfo/components/UpcomingArrivals.js b/src/components/StopInfo/components/UpcomingArrivals.js new file mode 100644 index 0000000..9c36bf9 --- /dev/null +++ b/src/components/StopInfo/components/UpcomingArrivals.js @@ -0,0 +1,106 @@ +import { Text } from '@ui-kitten/components'; +import React from 'react'; +import { View } from 'react-native'; +import Pressable from 'react-native/Libraries/Components/Pressable/Pressable'; +import { useRecoilValue } from 'recoil'; + +import { + dispatcherState, + favoriteRoutesState, + routePatternsState, + routesState, + userSettingsState, +} from '../../../state/atoms'; +import { upcomingArrivalsSorted } from '../../../state/selectors'; + +const UpcomingArrivals = () => { + const upcomingArrivals = useRecoilValue(upcomingArrivalsSorted); + const routes = useRecoilValue(routesState); + const routePatterns = useRecoilValue(routePatternsState); + const dispatcher = useRecoilValue(dispatcherState); + const settings = useRecoilValue(userSettingsState); + const favoriteRouteIDs = useRecoilValue(favoriteRoutesState); + + let renderArrivals = []; + if (upcomingArrivals) { + renderArrivals = upcomingArrivals.map((arrival) => { + const r = routes[routePatterns[arrival.RouteID]]; + + if (settings?.showFavoriteArrivalsOnly && !favoriteRouteIDs.has(r.ID)) return null; + + const diffMins = getMinsToTime(arrival.ArriveTime); + + if (diffMins > 180) return null; + + return ( + { + dispatcher?.updateCurrentRoute(r.ID, false); + }}> + + + {r.DisplayName} - {arrival.ArriveTime} ( + {diffMins > 1 ? diffMins + ' minutes' : diffMins === 1 ? '1 minute' : 'arriving'}) + + + + ); + }); + } + return ( + <> + + Upcoming Arrivals{settings.showFavoriteArrivalsOnly && ' (favorite routes)'} + + + {upcomingArrivals === null ? null : upcomingArrivals.length === 0 ? ( + No upcoming arrivals. + ) : renderArrivals.filter((arrival) => arrival !== null).length > 0 ? ( + renderArrivals + ) : ( + No upcoming arrivals on favorited routes. + )} + + ); +}; + +/** + * Get the minutes until a given time. Assumes the time is not in the past. + * @param {*} arriveTime arrival time in format HH:MM [AM|PM] + * @returns minutes until the given time + */ +const getMinsToTime = (arriveTime) => { + const arrivalDate = new Date(new Date().getTime()); + const minutes = Number.parseInt( + arriveTime.substring(arriveTime.indexOf(':') + 1, arriveTime.indexOf(':') + 3), + 10 + ); + arrivalDate.setMinutes(minutes); + let hours = Number.parseInt(arriveTime.substring(0, arriveTime.indexOf(':')), 10); + + // if hour === 12 and it is am, set hour to zero since it is 12am hour + if (hours === 12 && arriveTime.endsWith('AM')) hours = 0; + + arrivalDate.setHours(hours); + + // if it is currently pm and the arrival time is in the am + if (new Date().getHours() >= 12 && arriveTime.endsWith('AM')) { + // add 24 hours to make it be the next day + arrivalDate.setTime(arrivalDate.getTime() + 24 * 60 * 60 * 1000); + } + // if the given time is in the PM but not in the hour of 12, increase by 12 hours to make date pm + else if (hours < 12 && arriveTime.endsWith('PM')) { + arrivalDate.setTime(arrivalDate.getTime() + 12 * 60 * 60 * 1000); + } + + // get minutes between calculated arrival date and current date + return Math.round((arrivalDate - new Date()) / 1000 / 60); +}; + +export default UpcomingArrivals; diff --git a/src/components/icons/FavoriteStopImage.js b/src/components/icons/FavoriteStopImage.js new file mode 100644 index 0000000..7ba5a00 --- /dev/null +++ b/src/components/icons/FavoriteStopImage.js @@ -0,0 +1,21 @@ +import React from 'react'; +import Svg, { Path } from 'react-native-svg'; + +const FavoriteStopImage = (props) => ( + + + + +); + +export default React.memo(FavoriteStopImage); diff --git a/src/components/icons/RegularStopImage.js b/src/components/icons/RegularStopImage.js new file mode 100644 index 0000000..c35432a --- /dev/null +++ b/src/components/icons/RegularStopImage.js @@ -0,0 +1,21 @@ +import React from 'react'; +import Svg, { Path } from 'react-native-svg'; + +const RegularStopImage = (props) => ( + + + + +); + +export default React.memo(RegularStopImage); diff --git a/state/atoms.js b/src/state/atoms.js similarity index 95% rename from state/atoms.js rename to src/state/atoms.js index 3cfa907..e2bda57 100644 --- a/state/atoms.js +++ b/src/state/atoms.js @@ -1,5 +1,6 @@ import { atom } from 'recoil'; +import { FAVORITE_ROUTES } from './constants'; import { localForageEffectSet, localForageEffect } from './utilities/localforage/updateEffects'; export const routesState = atom({ @@ -15,7 +16,7 @@ export const vehicleLocationState = atom({ export const currentRouteRowState = atom({ key: 'currentRouteRowState', - default: -2, + default: FAVORITE_ROUTES, }); export const dispatcherState = atom({ diff --git a/src/state/constants.js b/src/state/constants.js new file mode 100644 index 0000000..431b999 --- /dev/null +++ b/src/state/constants.js @@ -0,0 +1,2 @@ +export const ALL_ROUTES = -1; +export const FAVORITE_ROUTES = -2; diff --git a/state/dispatcher.js b/src/state/dispatcher.js similarity index 100% rename from state/dispatcher.js rename to src/state/dispatcher.js diff --git a/state/dispatchers/favoriteStops.js b/src/state/dispatchers/favoriteStops.js similarity index 88% rename from state/dispatchers/favoriteStops.js rename to src/state/dispatchers/favoriteStops.js index b0504b9..71a0467 100644 --- a/state/dispatchers/favoriteStops.js +++ b/src/state/dispatchers/favoriteStops.js @@ -1,4 +1,5 @@ import { currentRouteRowState, favoriteStopsState } from '../atoms'; +import { ALL_ROUTES } from '../constants'; import { getSetFromLocalStorage } from '../utilities/localforage/getFromLocalStorage'; export const removeFavoriteStop = @@ -24,8 +25,8 @@ export const fetchFavoriteStops = async () => { const favoritesArray = await getSetFromLocalStorage('favorite-stops'); set(favoriteStopsState, favoritesArray); - if (favoritesArray.size == 0) { - set(currentRouteRowState, -1); + if (favoritesArray.size === 0) { + set(currentRouteRowState, ALL_ROUTES); } }; diff --git a/state/dispatchers/favorites.js b/src/state/dispatchers/favorites.js similarity index 100% rename from state/dispatchers/favorites.js rename to src/state/dispatchers/favorites.js diff --git a/state/dispatchers/routes.js b/src/state/dispatchers/routes.js similarity index 100% rename from state/dispatchers/routes.js rename to src/state/dispatchers/routes.js diff --git a/state/dispatchers/stops.js b/src/state/dispatchers/stops.js similarity index 100% rename from state/dispatchers/stops.js rename to src/state/dispatchers/stops.js diff --git a/state/dispatchers/userLocation.js b/src/state/dispatchers/userLocation.js similarity index 100% rename from state/dispatchers/userLocation.js rename to src/state/dispatchers/userLocation.js diff --git a/state/dispatchers/userSettings.js b/src/state/dispatchers/userSettings.js similarity index 100% rename from state/dispatchers/userSettings.js rename to src/state/dispatchers/userSettings.js diff --git a/state/dispatchers/vehicles.js b/src/state/dispatchers/vehicles.js similarity index 100% rename from state/dispatchers/vehicles.js rename to src/state/dispatchers/vehicles.js diff --git a/state/selectors.js b/src/state/selectors.js similarity index 92% rename from state/selectors.js rename to src/state/selectors.js index d933d04..45101bc 100644 --- a/state/selectors.js +++ b/src/state/selectors.js @@ -9,6 +9,7 @@ import { stopsState, upcomingArrivalsState, } from './atoms'; +import { ALL_ROUTES } from './constants'; export const currentRoute = selector({ key: 'currentRoute', @@ -111,7 +112,7 @@ export const currentRouteStopDetailsState = selector({ const currentRouteID = get(currentRouteRowState); const allRoutes = get(routesState); - if (currentRouteID === -1) { + if (currentRouteID === ALL_ROUTES) { return Object.values(allStops); } @@ -130,3 +131,11 @@ export const currentRouteStopDetailsState = selector({ return result; }, }); + +export const isIndividualRoute = selector({ + key: 'isIndividualRouteSelector', + get: ({ get }) => { + const currentRouteID = get(currentRouteRowState); + return currentRouteID >= 0; + }, +}); diff --git a/state/utilities/localforage/getFromLocalStorage.js b/src/state/utilities/localforage/getFromLocalStorage.js similarity index 100% rename from state/utilities/localforage/getFromLocalStorage.js rename to src/state/utilities/localforage/getFromLocalStorage.js diff --git a/state/utilities/localforage/updateEffects.js b/src/state/utilities/localforage/updateEffects.js similarity index 100% rename from state/utilities/localforage/updateEffects.js rename to src/state/utilities/localforage/updateEffects.js diff --git a/state/utilities/network/jsonFetch.js b/src/state/utilities/network/jsonFetch.js similarity index 100% rename from state/utilities/network/jsonFetch.js rename to src/state/utilities/network/jsonFetch.js diff --git a/state/utilities/request/getArrivals.js b/src/state/utilities/request/getArrivals.js similarity index 100% rename from state/utilities/request/getArrivals.js rename to src/state/utilities/request/getArrivals.js diff --git a/state/utilities/request/getRoutes.js b/src/state/utilities/request/getRoutes.js similarity index 100% rename from state/utilities/request/getRoutes.js rename to src/state/utilities/request/getRoutes.js diff --git a/state/utilities/request/getStops.js b/src/state/utilities/request/getStops.js similarity index 100% rename from state/utilities/request/getStops.js rename to src/state/utilities/request/getStops.js diff --git a/state/utilities/request/getVehicleLocations.js b/src/state/utilities/request/getVehicleLocations.js similarity index 100% rename from state/utilities/request/getVehicleLocations.js rename to src/state/utilities/request/getVehicleLocations.js diff --git a/state/utilities/request/getWaypoints.js b/src/state/utilities/request/getWaypoints.js similarity index 100% rename from state/utilities/request/getWaypoints.js rename to src/state/utilities/request/getWaypoints.js