Skip to content

Commit

Permalink
feat: Saving Tracks (#271)
Browse files Browse the repository at this point in the history
* wip nav changes to allow displaying modal without changing screen

* add custom tab bar icon and label

* add modal with different options

* tracks poc

* add check if permission granted, add function to request about permission

* add animation, add function for check permission disable strart tracking button

* wip nav changes to allow displaying modal without changing screen

* add custom tab bar icon and label

* add modal with different options

* tracks poc

* add check if permission granted, add function to request about permission

* add animation, add function for check permission disable strart tracking button

* fixed problem with custom modal

* fix hooks dependencies, user location flick

* cleanup

* place observations exactly at track line

* store observations found on track

* add check if foreground permission is granted, add gps indicator

* move gps indicator to map header

* create custom bottom sheet component with clicalbe backdrop

* add icon to start tracking and stop tracking button

* fix hooks dependencies, user location flick

* cleanup

* place observations exactly at track line

* store observations found on track

* add check if foreground permission is granted, add gps indicator

* move gps indicator to map header

* fix bug where observations don't display when there's no tracking line

* cleanup track store naming

* extract track layer to separate component

* correct imports, remove unused ones

* implement user location tooltip

* path drawing fixes

* add timer indicator to stop tracking modal

* add show timer indicator in bottom tab bar

* remove tab bar label, changed tabbar height

* save only required data for track path drawing

* move TrackPathLayer to track folder

* move task definition to different place

* fix layers order

* create context where we keep timer data

* extract types from store to common types

* pre-review cleanups

* add comment explaining distance calc algo

* correct import

* use luxon for duration formatting

* fix eslint rule

* add translations to gps modal

* add changes to en.json

* changed GPS pill

* add enum type with tab names,changes check in navigation listener

* set default route name to navigation store, use enum names in tabs name

* add test case for calculate distance function, rename varialbes

* changed gps pill padding

* using in setCurrentTab function enum

* create save track screen, create discard modal

* add translations

* update calculate distance function

* remove comment

* fix problem with timing calculation

* improve typing a bit

* add shared location context

* fix problem with gps modal and tab screen

* add back rounding of gps precision

* add description option to save track screen

* restore changes in podfile

* create all fields in save track screen

* add translations

* update packages, create hook for creating track

* add discard button icon

* update calculate distance function

* remove comment

* fix problem with timing calculation

* fix problem with gps modal and tab screen

* improve typing a bit

* add shared location context

* chore: update Mapeo deps

This updates `@mapeo/core`, `@mapeo/schema`, and `@mapeo/ipc`.

* update libs, wip track saving

* Fix @rollup/plugin-esm-shim bug

* changed track description

* fixed problem with GPSmodal

* code quality improvement

* implement track saving

* remove temp initial route

* remove irrelevant change

* move BottomSheet to shared directory

* general cleanups

* extract inline style to stylesheet

* remove unused var

* use cheap-ruler instead own distance calc

* Use ellipsis symbol instead triple dot

Co-authored-by: Andrew Chou <[email protected]>

* add header to tab navigator, fixed problem with navigator in homeHeader component

* rename gpsModal to GPSPermissionModal also renamed other connected compontents

* fixed problem with dependency in useTracking hook

* Apply review suggestions

* remove irrelevant changes to navigation

* rename store from useNvigatorStore to useTabNvigatorStore

* restore changes with GPSPill

* rename gps components

* rename gps components

* fix bug with distance not zeroing, adjust store to type changes

* extract inline style to stylesheet

* unify keys in queries

* improve i18n

* fixed issue with not resetitng trackingSince, move SharedLocationContextProvider to home stack

* changed timer type

* remove unnecessary props isFocused

* remove navigation cast type to as never, add navigation types

* remove unnecessary hook and inline task registration

* fixed timer nulable case

* add another solution to track timer

* save track notes

* revert changes in package.json and package.lock

* revert changes with mapeo/ipc

* revert changes package.json and package-lock.json

* add edit track screen, add update track method

* bring back feature flag, remove unnecessary checks

* fixed issue with discard track modal, using already created bottom sheet modal, and fixed padding in discription input

* create share component DiscardModal, fixed dependency in useTracking hook

* add edit track screen, add update track method

* add edit track screen, add update track method

* add back handler on Save Track Screen

* changed save track screen, using header from react-native-navigation instead custom header

---------

Co-authored-by: bohdanprog <[email protected]>
Co-authored-by: Evan Hahn <[email protected]>
Co-authored-by: Andrew Chou <[email protected]>
  • Loading branch information
4 people authored Apr 30, 2024
1 parent dd9101b commit 0359735
Show file tree
Hide file tree
Showing 20 changed files with 540 additions and 47 deletions.
32 changes: 32 additions & 0 deletions messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,24 @@
"description": "Title of dialog that shows when cancelling a new observation",
"message": "Discard observation?"
},
"Modal.DiscardTrack.description": {
"message": "Your Track will not be saved. This cannot be undone."
},
"Modal.DiscardTrack.title": {
"message": "Discard Track?"
},
"Modal.GPSDisable.button": {
"message": "Enable"
},
"Modal.GPSDisable.defaultButton": {
"message": "Continue Editing"
},
"Modal.GPSDisable.description": {
"message": "To create a Track CoMapeo needs access to your location and GPS."
},
"Modal.GPSDisable.discardButton": {
"message": "Discard Track"
},
"Modal.GPSDisable.title": {
"message": "GPS Disabled"
},
Expand Down Expand Up @@ -426,6 +438,26 @@
"description": "message shown whilst observations are loading",
"message": "Loading… this can take a while after synchronizing with a new device"
},
"screens.SaveTrack.TrackEditView.descriptionPlaceholder": {
"description": "Placeholder for description/notes field",
"message": "What is happening here?"
},
"screens.SaveTrack.TrackEditView.saveTrackCamera": {
"description": "Button label for adding photo",
"message": "Camera"
},
"screens.SaveTrack.TrackEditView.saveTrackDetails": {
"description": "Button label for check details",
"message": "Details"
},
"screens.SaveTrack.TrackEditView.title": {
"description": "Title for new track screen",
"message": "New Track"
},
"screens.SaveTrack.track": {
"description": "Category title for new track screen",
"message": "Track"
},
"screens.Security.obscurePassDescriptonPassNotSet": {
"message": "To use, enable App Passcode"
},
Expand Down
10 changes: 9 additions & 1 deletion src/frontend/Navigation/ScreenGroups/AppScreens.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,10 @@ import {TrackingTabBarIcon} from './TabBar/TrackingTabBarIcon';
import {TabName} from '../types';
import {CameraTabBarIcon} from './TabBar/CameraTabBarIcon';
import {MapTabBarIcon} from './TabBar/MapTabBarIcon';
import {SaveTrackScreen} from '../../screens/MapScreen/track/SaveTrackScreen';
import {InviteDeclined} from '../../screens/Settings/ProjectSettings/YourTeam/InviteDeclined';
import {UnableToCancelInvite} from '../../screens/Settings/ProjectSettings/YourTeam/ReviewAndInvite/UnableToCancelInvite';
import {SharedLocationContextProvider} from '../../contexts/SharedLocationContext';
import {
SyncScreen,
createNavigationOptions as createSyncNavOptions,
Expand Down Expand Up @@ -139,6 +141,7 @@ export type AppList = {
UnableToCancelInvite: InviteProps;
DeviceNameDisplay: undefined;
DeviceNameEdit: undefined;
SaveTrack: undefined;
Sync: undefined;
};

Expand Down Expand Up @@ -208,7 +211,11 @@ export const createDefaultScreenGroup = (
<RootStack.Screen
name="Home"
options={{headerShown: false}}
component={HomeTabs}
children={() => (
<SharedLocationContextProvider>
<HomeTabs />
</SharedLocationContextProvider>
)}
/>
<RootStack.Screen
name="AuthScreen"
Expand Down Expand Up @@ -360,6 +367,7 @@ export const createDefaultScreenGroup = (
component={GpsModal}
options={createGpsModalNavigationOptions({intl})}
/>
<RootStack.Screen name="SaveTrack" component={SaveTrackScreen} />
<RootStack.Screen
name="InviteDeclined"
component={InviteDeclined}
Expand Down
15 changes: 5 additions & 10 deletions src/frontend/contexts/ExternalProviders.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import {BottomSheetModalProvider} from '@gorhom/bottom-sheet';
import {AppStackList} from '../Navigation/AppStack';
import {GPSModalContextProvider} from './GPSModalContext';
import {TrackTimerContextProvider} from './TrackTimerContext';
import {SharedLocationContextProvider} from './SharedLocationContext';

type ExternalProvidersProp = {
children: React.ReactNode;
Expand All @@ -29,17 +28,13 @@ export const ExternalProviders = ({
return (
<QueryClientProvider client={queryClient}>
<GestureHandlerRootView style={{flex: 1}}>
<SharedLocationContextProvider>
<TrackTimerContextProvider>
<GPSModalContextProvider>
<TrackTimerContextProvider>
<BottomSheetModalProvider>
<NavigationContainer ref={navRef}>
{children}
</NavigationContainer>
</BottomSheetModalProvider>
</TrackTimerContextProvider>
<NavigationContainer ref={navRef}>
<BottomSheetModalProvider>{children}</BottomSheetModalProvider>
</NavigationContainer>
</GPSModalContextProvider>
</SharedLocationContextProvider>
</TrackTimerContextProvider>
</GestureHandlerRootView>
</QueryClientProvider>
);
Expand Down
55 changes: 55 additions & 0 deletions src/frontend/hooks/server/track.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import {
useQueryClient,
useMutation,
useSuspenseQuery,
} from '@tanstack/react-query';
import {useProject} from './projects';
import {TrackValue} from '@mapeo/schema';

export const TRACK_KEY = 'tracks';

export function useCreateTrack() {
const queryClient = useQueryClient();
const project = useProject();
return useMutation({
mutationFn: async (params: TrackValue) => {
return project.track.create(params);
},
onSuccess: () => {
queryClient.invalidateQueries({queryKey: [TRACK_KEY]});
},
});
}

export function useTracksQuery() {
const project = useProject();
return useSuspenseQuery({
queryKey: [TRACK_KEY],
queryFn: async () => {
return project.track.getMany();
},
});
}

export function useTrackQuery(docId: string) {
const project = useProject();
return useSuspenseQuery({
queryKey: [TRACK_KEY, docId],
queryFn: async () => {
return project.track.getByDocId(docId);
},
});
}

export function useDeleteTrackMutation() {
const queryClient = useQueryClient();
const project = useProject();
return useMutation({
mutationFn: async (docId: string) => {
return project.track.delete(docId);
},
onSuccess: () => {
queryClient.invalidateQueries({queryKey: [TRACK_KEY]});
},
});
}
11 changes: 9 additions & 2 deletions src/frontend/hooks/tracks/useCurrentTrackStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ type TracksStoreState = {
distance: number;
addNewObservation: (observationId: string) => void;
addNewLocations: (locationData: LocationHistoryPoint[]) => void;
clearLocationHistory: () => void;
clearCurrentTrack: () => void;
setTracking: (val: boolean) => void;
} & (
| {
Expand Down Expand Up @@ -54,7 +54,14 @@ export const useCurrentTrackStore = create<TracksStoreState>(set => ({
distance: distance + calculateTotalDistance([lastLocation, ...data]),
};
}),
clearLocationHistory: () => set(() => ({locationHistory: []})),
clearCurrentTrack: () =>
set(() => ({
locationHistory: [],
trackingSince: null,
distance: 0,
isTracking: false,
observations: [],
})),
setTracking: (val: boolean) =>
set(() =>
val
Expand Down
46 changes: 25 additions & 21 deletions src/frontend/hooks/tracks/useTracking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {useCallback, useState} from 'react';
import {useCurrentTrackStore} from './useCurrentTrackStore';
import React from 'react';
import {FullLocationData} from '../../sharedTypes/location';
import {useGPSModalContext} from '../../contexts/GPSModalContext';

export const LOCATION_TASK_NAME = 'background-location-task';

Expand All @@ -13,32 +14,34 @@ type LocationCallbackInfo = {
};

export function useTracking() {
const {bottomSheetRef} = useGPSModalContext();
const [loading, setLoading] = useState(false);
const addNewLocations = useCurrentTrackStore(state => state.addNewLocations);
const setTracking = useCurrentTrackStore(state => state.setTracking);
const isTracking = useCurrentTrackStore(state => state.isTracking);

const addNewTrackLocations = useCallback(
({data, error}: LocationCallbackInfo) => {
if (error) {
console.error('Error while processing location update callback', error);
}
if (data?.locations) {
addNewLocations(
data.locations.map(loc => ({
latitude: loc.coords.latitude,
longitude: loc.coords.longitude,
timestamp: loc.timestamp,
})),
);
}
},
[addNewLocations],
);

React.useEffect(() => {
TaskManager.defineTask(LOCATION_TASK_NAME, addNewTrackLocations);
}, [addNewTrackLocations]);
TaskManager.defineTask(
LOCATION_TASK_NAME,
({data, error}: LocationCallbackInfo) => {
if (error) {
console.error(
'Error while processing location update callback',
error,
);
}
if (data?.locations) {
addNewLocations(
data.locations.map(loc => ({
latitude: loc.coords.latitude,
longitude: loc.coords.longitude,
timestamp: loc.timestamp,
})),
);
}
},
);
}, [addNewLocations]);

const startTracking = useCallback(async () => {
if (isTracking) {
Expand All @@ -60,8 +63,9 @@ export function useTracking() {

const cancelTracking = useCallback(async () => {
await Location.stopLocationUpdatesAsync(LOCATION_TASK_NAME);
bottomSheetRef.current?.close();
setTracking(false);
}, [setTracking]);
}, [bottomSheetRef, setTracking]);

return {isTracking, startTracking, cancelTracking, loading};
}
18 changes: 18 additions & 0 deletions src/frontend/images/Track.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/frontend/images/camera.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/frontend/images/close.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions src/frontend/images/delete.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/frontend/images/details.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 0359735

Please sign in to comment.