From 77245eea2c7e4eb494e44bc0670af64512a74d18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20Stefa=C5=84czyk?= Date: Thu, 11 Apr 2024 10:25:12 +0200 Subject: [PATCH 001/123] wip nav changes to allow displaying modal without changing screen --- .../Navigation/ScreenGroups/AppScreens.tsx | 66 +++++++++++++------ src/frontend/hooks/useNavigationStore.ts | 11 ++++ src/frontend/screens/TrackingScreen.tsx | 33 ++++++++++ 3 files changed, 91 insertions(+), 19 deletions(-) create mode 100644 src/frontend/hooks/useNavigationStore.ts create mode 100644 src/frontend/screens/TrackingScreen.tsx diff --git a/src/frontend/Navigation/ScreenGroups/AppScreens.tsx b/src/frontend/Navigation/ScreenGroups/AppScreens.tsx index ed60f5890..f13ded1b8 100644 --- a/src/frontend/Navigation/ScreenGroups/AppScreens.tsx +++ b/src/frontend/Navigation/ScreenGroups/AppScreens.tsx @@ -1,5 +1,5 @@ import {createBottomTabNavigator} from '@react-navigation/bottom-tabs'; -import {NavigatorScreenParams} from '@react-navigation/native'; +import {NavigatorScreenParams, useNavigation} from '@react-navigation/native'; import * as React from 'react'; import MaterialIcons from 'react-native-vector-icons/MaterialIcons'; @@ -45,10 +45,12 @@ import { EditScreen as DeviceNameEditScreen, createNavigationOptions as createDeviceNameEditNavOptions, } from '../../screens/Settings/ProjectSettings/DeviceName/EditScreen'; +import {useNavigationStore} from '../../hooks/useNavigationStore'; export type HomeTabsList = { Map: undefined; Camera: undefined; + Tracking: undefined; }; export type AppList = { @@ -125,24 +127,50 @@ export type AppList = { }; const Tab = createBottomTabNavigator(); - -const HomeTabs = () => ( - ({ - tabBarIcon: ({color}) => { - const iconName = route.name === 'Map' ? 'map' : 'photo-camera'; - return ; - }, - header: () => , - headerTransparent: true, - tabBarTestID: 'tabBarButton' + route.name, - })} - initialRouteName="Map" - backBehavior="initialRoute"> - - - -); +const HomeTabs = () => { + const navigationStore = useNavigationStore(); + return ( + { + if (defaultPrevented) { + return; + } + navigationStore.setCurrentTab(target?.split('-')[0] || 'Map'); + }, + }} + screenOptions={({route}) => ({ + tabBarIcon: ({color}) => { + const icons = { + Map: 'map', + Camera: 'photo-camera', + Tracking: 'nordic-walking', + }; + console.log(navigationStore.currentTab); + return ( + + ); + }, + header: () => , + headerTransparent: true, + tabBarTestID: 'tabBarButton' + route.name, + })} + initialRouteName="Map" + backBehavior="initialRoute"> + + + <>} + listeners={({navigation}) => ({ + tabPress: e => { + navigation.navigate('Map'); + }, + })} + /> + + ); +}; // **NOTE**: No hooks allowed here (this is not a component, it is a function // that returns a react element) diff --git a/src/frontend/hooks/useNavigationStore.ts b/src/frontend/hooks/useNavigationStore.ts new file mode 100644 index 000000000..8b6fce22e --- /dev/null +++ b/src/frontend/hooks/useNavigationStore.ts @@ -0,0 +1,11 @@ +import {create} from 'zustand'; + +type NavigationStoreState = { + currentTab: string; + setCurrentTab: (tab: string) => void; +}; + +export const useNavigationStore = create(set => ({ + currentTab: 'Map', + setCurrentTab: (tab: string) => set(() => ({currentTab: tab})), +})); diff --git a/src/frontend/screens/TrackingScreen.tsx b/src/frontend/screens/TrackingScreen.tsx new file mode 100644 index 000000000..b2f67c0e7 --- /dev/null +++ b/src/frontend/screens/TrackingScreen.tsx @@ -0,0 +1,33 @@ +import * as React from 'react'; +import {View, StyleSheet} from 'react-native'; +import {useIsFocused} from '@react-navigation/native'; + +import {CameraView} from '../sharedComponents/CameraView'; +import {NativeHomeTabsNavigationProps} from '../sharedTypes'; +import {CapturedPictureMM} from '../contexts/PhotoPromiseContext/types'; +import {useDraftObservation} from '../hooks/useDraftObservation'; + +export const TrackingScreen = ({ + navigation, +}: NativeHomeTabsNavigationProps<'Tracking'>) => { + const isFocused = useIsFocused(); + const {newDraft} = useDraftObservation(); + + function handleAddPress(photoPromise: Promise) { + newDraft(photoPromise); + navigation.navigate('PresetChooser'); + } + + return ( + + {isFocused ? : null} + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: 'black', + }, +}); From 2ba1cea6d57fd3341eb69855d572c1506c6b2452 Mon Sep 17 00:00:00 2001 From: bohdanprog Date: Thu, 11 Apr 2024 14:15:36 +0200 Subject: [PATCH 002/123] add custom tab bar icon and label --- .../Navigation/ScreenGroups/AppScreens.tsx | 101 +++++++++++++----- .../ScreenGroups/TabBar/TabBarIcon.tsx | 22 ++++ .../ScreenGroups/TabBar/TabBarLabel.tsx | 16 +++ src/frontend/hooks/useNavigationStore.ts | 11 +- 4 files changed, 119 insertions(+), 31 deletions(-) create mode 100644 src/frontend/Navigation/ScreenGroups/TabBar/TabBarIcon.tsx create mode 100644 src/frontend/Navigation/ScreenGroups/TabBar/TabBarLabel.tsx diff --git a/src/frontend/Navigation/ScreenGroups/AppScreens.tsx b/src/frontend/Navigation/ScreenGroups/AppScreens.tsx index f13ded1b8..27d5a4d90 100644 --- a/src/frontend/Navigation/ScreenGroups/AppScreens.tsx +++ b/src/frontend/Navigation/ScreenGroups/AppScreens.tsx @@ -1,7 +1,12 @@ import {createBottomTabNavigator} from '@react-navigation/bottom-tabs'; -import {NavigatorScreenParams, useNavigation} from '@react-navigation/native'; +import { + EventArg, + getFocusedRouteNameFromRoute, + NavigatorScreenParams, + useNavigation, + useRoute, +} from '@react-navigation/native'; import * as React from 'react'; -import MaterialIcons from 'react-native-vector-icons/MaterialIcons'; import {HomeHeader} from '../../sharedComponents/HomeHeader'; import {RootStack} from '../AppStack'; @@ -46,6 +51,10 @@ import { createNavigationOptions as createDeviceNameEditNavOptions, } from '../../screens/Settings/ProjectSettings/DeviceName/EditScreen'; import {useNavigationStore} from '../../hooks/useNavigationStore'; +import {TabBarLabel} from './TabBar/TabBarLabel'; +import {TabBarIcon} from './TabBar/TabBarIcon'; + +export type TabName = keyof HomeTabsList; export type HomeTabsList = { Map: undefined; @@ -128,45 +137,83 @@ export type AppList = { const Tab = createBottomTabNavigator(); const HomeTabs = () => { - const navigationStore = useNavigationStore(); + const {setCurrentTab, currentTab} = useNavigationStore(); + const navigation = useNavigation(); + const route = useRoute(); + + const handleTabPress = ({ + target, + preventDefault, + }: EventArg<'tabPress', true, undefined>) => { + if (target?.split('-')[0] === 'Tracking') { + preventDefault(); + const currentTab = getFocusedRouteNameFromRoute(route); + if (currentTab === 'Camera') { + navigation.navigate('Map'); + } + } + setCurrentTab((target?.split('-')[0] || 'Map') as unknown as TabName); + }; + return ( { - if (defaultPrevented) { - return; - } - navigationStore.setCurrentTab(target?.split('-')[0] || 'Map'); - }, + tabPress: handleTabPress, }} screenOptions={({route}) => ({ - tabBarIcon: ({color}) => { - const icons = { - Map: 'map', - Camera: 'photo-camera', - Tracking: 'nordic-walking', - }; - console.log(navigationStore.currentTab); - return ( - - ); - }, header: () => , headerTransparent: true, tabBarTestID: 'tabBarButton' + route.name, })} initialRouteName="Map" backBehavior="initialRoute"> - - + ( + + ), + tabBarLabel: params => ( + + ), + }} + /> + ( + + ), + tabBarLabel: params => ( + + ), + }} + /> ( + + ), + tabBarLabel: params => ( + + ), + }} children={() => <>} - listeners={({navigation}) => ({ - tabPress: e => { - navigation.navigate('Map'); - }, - })} /> ); diff --git a/src/frontend/Navigation/ScreenGroups/TabBar/TabBarIcon.tsx b/src/frontend/Navigation/ScreenGroups/TabBar/TabBarIcon.tsx new file mode 100644 index 000000000..25786f48d --- /dev/null +++ b/src/frontend/Navigation/ScreenGroups/TabBar/TabBarIcon.tsx @@ -0,0 +1,22 @@ +import * as React from 'react'; +import MaterialIcons from 'react-native-vector-icons/MaterialIcons'; +import {FC} from 'react'; + +export interface TabBarIcon { + size: number; + focused: boolean; + color: string; + isFocused: boolean; + iconName: string; +} +export const TabBarIcon: FC = ({size, isFocused, iconName}) => { + const color1 = 'rgb(0, 122, 255)'; + const color2 = '#8E8E8F'; + return ( + + ); +}; diff --git a/src/frontend/Navigation/ScreenGroups/TabBar/TabBarLabel.tsx b/src/frontend/Navigation/ScreenGroups/TabBar/TabBarLabel.tsx new file mode 100644 index 000000000..689c49e40 --- /dev/null +++ b/src/frontend/Navigation/ScreenGroups/TabBar/TabBarLabel.tsx @@ -0,0 +1,16 @@ +import * as React from 'react'; +import {Text} from 'react-native'; +import {FC} from 'react'; +import {LabelPosition} from '@react-navigation/bottom-tabs/lib/typescript/src/types'; + +export interface TabBarLabel { + isFocused: boolean; + color: string; + position: LabelPosition; + children: string; +} +export const TabBarLabel: FC = ({children, isFocused}) => { + const color1 = 'rgb(0, 122, 255)'; + const color2 = '#8E8E8F'; + return {children}; +}; diff --git a/src/frontend/hooks/useNavigationStore.ts b/src/frontend/hooks/useNavigationStore.ts index 8b6fce22e..840eb1d02 100644 --- a/src/frontend/hooks/useNavigationStore.ts +++ b/src/frontend/hooks/useNavigationStore.ts @@ -1,11 +1,14 @@ import {create} from 'zustand'; +import {HomeTabsList} from '../Navigation/ScreenGroups/AppScreens'; + +type TabName = keyof HomeTabsList; type NavigationStoreState = { - currentTab: string; - setCurrentTab: (tab: string) => void; + currentTab: TabName; + setCurrentTab: (tab: TabName) => void; }; export const useNavigationStore = create(set => ({ - currentTab: 'Map', - setCurrentTab: (tab: string) => set(() => ({currentTab: tab})), + currentTab: 'Map' as TabName, + setCurrentTab: (tab: TabName) => set(() => ({currentTab: tab})), })); From 733746e4565b155022892c10c5e3cbfcd6ac9c79 Mon Sep 17 00:00:00 2001 From: bohdanprog Date: Thu, 11 Apr 2024 17:12:40 +0200 Subject: [PATCH 003/123] add modal with different options --- .../Navigation/ScreenGroups/AppScreens.tsx | 16 +++++--- src/frontend/contexts/ExternalProviders.tsx | 9 +++-- src/frontend/contexts/GPSModalContext.tsx | 36 +++++++++++++++++ src/frontend/images/alert-icon.png | Bin 0 -> 5067 bytes .../screens/MapScreen/gps/GPSDisabled.tsx | 37 ++++++++++++++++++ .../screens/MapScreen/gps/GPSEnabled.tsx | 14 +++++++ .../screens/MapScreen/gps/GPSModal.tsx | 34 ++++++++++++++++ src/frontend/screens/MapScreen/index.tsx | 3 ++ 8 files changed, 141 insertions(+), 8 deletions(-) create mode 100644 src/frontend/contexts/GPSModalContext.tsx create mode 100644 src/frontend/images/alert-icon.png create mode 100644 src/frontend/screens/MapScreen/gps/GPSDisabled.tsx create mode 100644 src/frontend/screens/MapScreen/gps/GPSEnabled.tsx create mode 100644 src/frontend/screens/MapScreen/gps/GPSModal.tsx diff --git a/src/frontend/Navigation/ScreenGroups/AppScreens.tsx b/src/frontend/Navigation/ScreenGroups/AppScreens.tsx index 27d5a4d90..04dcd5552 100644 --- a/src/frontend/Navigation/ScreenGroups/AppScreens.tsx +++ b/src/frontend/Navigation/ScreenGroups/AppScreens.tsx @@ -53,6 +53,7 @@ import { import {useNavigationStore} from '../../hooks/useNavigationStore'; import {TabBarLabel} from './TabBar/TabBarLabel'; import {TabBarIcon} from './TabBar/TabBarIcon'; +import {useGPSModalContext} from '../../contexts/GPSModalContext'; export type TabName = keyof HomeTabsList; @@ -140,17 +141,22 @@ const HomeTabs = () => { const {setCurrentTab, currentTab} = useNavigationStore(); const navigation = useNavigation(); const route = useRoute(); + const {setDisplayModal} = useGPSModalContext(); const handleTabPress = ({ target, preventDefault, }: EventArg<'tabPress', true, undefined>) => { - if (target?.split('-')[0] === 'Tracking') { + const targetTab = target?.split('-')[0]; + if (targetTab === 'Tracking') { preventDefault(); - const currentTab = getFocusedRouteNameFromRoute(route); - if (currentTab === 'Camera') { - navigation.navigate('Map'); - } + setDisplayModal(true); + } else { + setDisplayModal(false); + } + const currentTab = getFocusedRouteNameFromRoute(route); + if (currentTab === 'Camera') { + navigation.navigate('Map'); } setCurrentTab((target?.split('-')[0] || 'Map') as unknown as TabName); }; diff --git a/src/frontend/contexts/ExternalProviders.tsx b/src/frontend/contexts/ExternalProviders.tsx index 61a09d03d..898afd1e9 100644 --- a/src/frontend/contexts/ExternalProviders.tsx +++ b/src/frontend/contexts/ExternalProviders.tsx @@ -11,6 +11,7 @@ import { // See https://github.com/gorhom/react-native-bottom-sheet/issues/1157 import {BottomSheetModalProvider} from '@gorhom/bottom-sheet'; import {AppStackList} from '../Navigation/AppStack'; +import {GPSModalContextProvider} from './GPSModalContext'; type ExternalProvidersProp = { children: React.ReactNode; @@ -26,9 +27,11 @@ export const ExternalProviders = ({ return ( - - {children} - + + + {children} + + ); diff --git a/src/frontend/contexts/GPSModalContext.tsx b/src/frontend/contexts/GPSModalContext.tsx new file mode 100644 index 000000000..892b0433e --- /dev/null +++ b/src/frontend/contexts/GPSModalContext.tsx @@ -0,0 +1,36 @@ +import React, { + createContext, + Dispatch, + SetStateAction, + useContext, + useState, +} from 'react'; + +interface GPSModalContext { + displayModal: boolean; + setDisplayModal: Dispatch>; +} + +const GPSModalContext = createContext(null); + +const GPSModalContextProvider = ({children}: {children: React.ReactNode}) => { + const [displayModal, setDisplayModal] = useState(false); + + return ( + + {children} + + ); +}; + +function useGPSModalContext() { + const context = useContext(GPSModalContext); + if (!context) { + throw new Error( + 'useBottomSheetContext must be used within a BottomSheetContextProvider', + ); + } + return context; +} + +export {GPSModalContextProvider, useGPSModalContext}; diff --git a/src/frontend/images/alert-icon.png b/src/frontend/images/alert-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..8bcb094e8376df7602eef6d3ccc866fe802ac7a8 GIT binary patch literal 5067 zcmV;+6Ey6JP)gzWt~R z?PPvQ-Fls>TjyKf-|zg+@BB^~a}gv60xxgB*YW6icdoB%FO*!cKc#|_pio71+&uHl zGv3orKiyM&*|KH5%IRF-$^ss1%9#r!B?T%3r-v2M%HMFq4c--3Tw(Vwx#SXW%a$!> z=FFL9-MV$2&Y6=ZPX_FB{P5w!L3oW_fAYyEJpefXdn%-tVM-t+0V)d6Yp=cLb1VwV z-o1OhBS(&SYzq*3eDvti-s#h)?e>*dUTN>s(a{l%9XmE?Zf-WL&zw0El$V$10Fdpu zbLYamUcf|{7b{?~0u`zi4{yP7tZ8X!@vEw;yrV~tdfnaKer08)mrkes_V#vf{P^)6 zYs#0EmD%;FQ>S|F+11q*j2bm65Rd`esZ=TlfGV2oW00*$L*{m-{cx`QMenO&3`vS$w<#K*9ne;k4JM9fr#;yehHZAadKTw$< zlgZ??o6~(@S5~*kiIKUhuf95`;yJw()}B}#Jn+B+yfF8SLEbYCT80HwUhCw+q}y-5 zon2p*^rcD(F~L83_H2Usju|s1p$AR?j6g{U5TE0KB!;mMgN*h8#ae!jX_Xb=Y-e>o z2SB<{7SX-kvFyHm`?8l^c3A*;0xNg!v4Z>t;KBoi@aKAa{)P=3ER*!Gu*nw~fRt2) z6H>c`)=9un-I8KRg8$aq0>o{!4dXn4bnd~lhD9vcrI1M$5P(yWtd3{IN`T6UrTU9Z zRxcx4QBeU%Sv^MpU}{l=u*Ds+Dh>(akUjQV?2z7|uZQ*Zp#G(oUTT4mx+TRbfTHFp zRXnLOsgO}gRX8bCsh&Q4`poL;>RMe>tqP9ko;nY3f$q_!<*;nhp)bDp;*eUvU4W!> z8G)8zt^1`_KC5yWolEO)G5`o@b#-;w0|yS|Xf?56QTsJmzy=Id#1=VU2wULk&l9o{ z{_*3-6RNNU1#o1rQ!1AN908W%__%T7#@5%@Pn|e%;$Xq^x!iI6!KAW69lbvZ!Io;~{; z*Is+=cclt5ObJCViIj}+_KcE(|XbGos z`5=A#@yAK0YK}p&6BR03p)xgh+;PXFdYg;P1r!*zZQHgLJ9qBfujlB}p-#Ox3z$^0 zTjl`UhCR!MUt`twR{kGk)^`<{@Y{$ZSUXt1IRm&qNu^_pw086!Y;iv8VsKAc)3CK+wR zJi1awbusErnQ7Cenf$I$zW4+AZ0eUnR&6+fuXfvQxBXo1$vEwz0p-+#mrl})jg&7~u;5{}d=Hwhh^R%L{NRHRE}`MU zjnRuPhgCB6n3IIMIj-ALFx*^_QTR-p18M2EjRJ^KDKW~r0#m0>om5j(^Y`W}CcM~L zvu53j$kO@tXbpl-o4vKA=L%3 z4knC(rg{VAV%b+tZ`AsQj1tLPxYxWVf>6%{ouia_*NDi;_cD?Uk^|YJ=9Juny(<6^)WzP>X*9n z&O862)NInrxnMvcYLswqaG=o|>dXL#k5R|jHj$t~FGg_^2UDF%BKHgsBwg56V{LOx z2}Li+4t64RS`~x03@fFmuHT}5kb%JtLfQIn52H>WFzQn|*WK=LlJ1JoC1y>hF=3qy z0i_ObNq~Yqid~t99(u^K2?403OP8AY^XHq#9(&A!SPDTyMEXPN0YGG z&ZmkFH(57cEMmN^Odczp-cdq%v@V*{(9rNR6QjTuz;GZC_E5jkC`4lSqYOhHee_Yg z#4Q?>{ps4ZYhM;1ZK`{_4s~`Dk5Ob)|Ho*L4es>}S7E&5h%x4m`dLR4IwI2DnI=X7 zq{knB+^zv=$&w{T4sOA6D_5>G3c^{oVZ$Pc9I=RA%uE6Ci*w_wK8*5+U!hfC;{LCf z>LH5N*b!ZUYA?`*zFLBM6QjT+Kv0j~tX{pkAV>)3dC=v{ms>yqEN(+DKqfo2qt4Dw zaV|9IR-7^SoFQjNs%M9}-3{XO#MiA`XVnT2UVQOIyDlcEq*FVXg$;{Kuv3yt7>s4) zn!3X#l1Mo3Tm%c08>2@+Qz{3OFz9yC#gk1;;tVzw9|%ZJ6hSp%7EA1`(&%zEst2Q} zH~A6xl3~mDWvj=4Al@h_f~HzKdlJ=?Dps4A1a)(EsV{ZwPt-B6>!^{BLsZABAO!V= zB!$XYplAZ+WLb}#>=RlZF5;Zx;KPm*01lNy?T{%`rkJ5fm;hq$<(4?qkRg5Y9-_xw z-j;I@oH4gMaw=^e65H~|F>GG`4Td5Bgg@leZZIkr6`1IR`?17juUtKyHX*#2FgEw4 zF;@&KXliOAH^~`v3z(QS>_jYp!M+TYNri;_u|&Zes)!V4RNtO`w2BNrwq2fr^`kK6 z1gvDEY=)Z(61O3IYT^*=5eupU3x;ZAoO25BFTM1ViA&^MlQU$K&_ooQxTbiVw}1bB z+u|ZU#+-G1eLZi8d?Zo;@knzYe)!>G6O+Iw7~pKyV8yj|Fey%v0k`@~eFa#cQDmd= zcP~xqQ9XVb<6^Cr&jLjf7rBxoL6{gM+S)mLHBgCyJCqi)KanOKlv|J`6_UDhjLbck z7mB!Xteyj;^+{(*EG5e=3(DHxV`38gApoP&0|m=BAs74oNTa=U`75A2g#A-qL7L?fp)~iB-odzmt71jz;O`-u6^pMr%apz2_S^l zHi=e)C^@Mj>K7<1-a{Zeq>g(aKZ-axK|2^mRFn7&vGSB2aJ0PFzcew)^y$;>+WA7x zzjDd0FbH69hFmRRP9Wvf;yE^*OJj5wV5^vAAj6i_2t`us3iW*|Maj`J>{X9E^2iDqyFWK2 z5Ew;76BfCsCZOQ(#ch+KB4CcJTD5Av>W&}PuDW*USf_wXlRqW$*mx9WEu&mkopcgI z76@gGT$V-5vKhEK_UpR3x{?5u?`HwhTW`H(N-bi~@2Q(wx~$9&IqD?jSys{FMRU`8 z`g$D1pp*SiY1xUK14>2Gn>TO%EiHukBNCaEmF8UbumdZ1{P=O(;*qu(E$bqjK?s(e z9>E+F65D0ypq|a;7NgWCo?Ihi|F@<@f{N*dn7AZ&`m{Ow!3U;8iz;J}C911Uc|(KA z)z>%f+O_Kdx#{GnD-Zx)Ik^cr*-o3+8O=e+pSeH*-1r>X|DvExde_C|Z+AB~Hol^O z|DutecK|>qSFJK9Uw+wc_aU>tSylC;qcdha|M}dxyU`xftT8<;EiJtaIda~(o}(Od zPqu)vQf+MuCNwr~{QC0c{{&Fgu*;o0_V`hVnvX6d?ab?XF!b>c02A&iuUdf=L=6imQMNFUK=S3mB?i zMtvdFuv0$Qa)J1JGXeya%ZO3^N!8-TM?PP;aGC5&pCM5pGp44q^B)Q&=NANuf+78K zw1D_kOzjeb7(wk~`h*U3yz|aG-yaF~s_i$wDOzUhuYUD?xdWe>;?p_)vCzSyS;6=e z>i5w9$(%ZMD$U#yrZ54P`h+bq?oVE9u<(_N$*Fz&PM`^E`z|q=$%!VV(mQcU|Fs#RBA2;y)TmKi-awElbbYY`ZZ57Q zryv`XpvhJvTuiLl#*Yv&?&vG8yz=i07cM-c0mTtBC&tOR?c47fw`0c_@_mX9=&H7@ z`Nsw{`PzYnBg#IbGwM7t!fGc55;n$pXY}IDH{bl3?Akg~tS?0NaB9K?(({3wYA4Y z_bH|B8@4w#7IhEwkRN)!ze|<5RJ#@Z6m6aQ>8DTR9~{Uuv>p%D`ByND+278%GXb6H zsaj*;&uY6}fVImHd!4}8!Gj0?L#*3sViw}vvYr3k+SazRSlP~+ng)zcpO-&UI&^?=-(ARZdS=s%f{7EmJenY!s`n-Qx*)Y;0R>aYn$L|8nI!NghGu3hU%u4u?*3OZ1QFZ*x8bwT{-&?A%HH%Z@=Nf=SA*Yy?SDKHv50NY*LX&-zS*wk%F3@oJpJ{)Ry}4RF z72K$st!-#(+AzQ>#{WgKXv-M=umt;ViWf|CUn83!hmkcO-^#xiAJpPFYeLiRt7Hs! z^CcfZA+L*+CEOOK(7DGjF-z<~MJ34l03f!E5#R9RHy#6=%)Mbe@2_%l=7AVE7Dhk(=OprJHalumN`Rsde^JG`8TrS< h%6p||%AX`=`2)JX-WLf{z;gfq002ovPDHLkV1jntx^Dmg literal 0 HcmV?d00001 diff --git a/src/frontend/screens/MapScreen/gps/GPSDisabled.tsx b/src/frontend/screens/MapScreen/gps/GPSDisabled.tsx new file mode 100644 index 000000000..72b292396 --- /dev/null +++ b/src/frontend/screens/MapScreen/gps/GPSDisabled.tsx @@ -0,0 +1,37 @@ +import * as React from 'react'; +import {View, Image} from 'react-native'; +import {Button} from '../../../sharedComponents/Button'; +import {Text} from '../../../sharedComponents/Text'; + +export const GPSDisabled = () => { + return ( + + + + + GPS Disabled + + + To create a Track CoMapeo needs access to your location and GPS. + + + + ); +}; diff --git a/src/frontend/screens/MapScreen/gps/GPSEnabled.tsx b/src/frontend/screens/MapScreen/gps/GPSEnabled.tsx new file mode 100644 index 000000000..571910c8e --- /dev/null +++ b/src/frontend/screens/MapScreen/gps/GPSEnabled.tsx @@ -0,0 +1,14 @@ +import * as React from 'react'; +import {View} from 'react-native'; +import {Button} from '../../../sharedComponents/Button'; +import {Text} from '../../../sharedComponents/Text'; + +export const GPSEnabled = () => { + return ( + + + + ); +}; diff --git a/src/frontend/screens/MapScreen/gps/GPSModal.tsx b/src/frontend/screens/MapScreen/gps/GPSModal.tsx new file mode 100644 index 000000000..46551484e --- /dev/null +++ b/src/frontend/screens/MapScreen/gps/GPSModal.tsx @@ -0,0 +1,34 @@ +import React, {FC} from 'react'; +import {View, StyleSheet} from 'react-native'; +import {GPSDisabled} from './GPSDisabled'; +import {GPSEnabled} from './GPSEnabled'; +import {useGPSModalContext} from '../../../contexts/GPSModalContext'; + +interface GPSModal { + locationServicesEnabled: boolean; +} +export const GPSModal: FC = ({locationServicesEnabled}) => { + const {displayModal} = useGPSModalContext(); + + return ( + <> + {displayModal && ( + + {locationServicesEnabled ? : } + + )} + + ); +}; + +const styles = StyleSheet.create({ + wrapper: { + position: 'absolute', + bottom: 0, + left: 0, + width: '100%', + borderTopLeftRadius: 10, + borderTopRightRadius: 10, + backgroundColor: '#fff', + }, +}); diff --git a/src/frontend/screens/MapScreen/index.tsx b/src/frontend/screens/MapScreen/index.tsx index 2ab24bf32..c84666778 100644 --- a/src/frontend/screens/MapScreen/index.tsx +++ b/src/frontend/screens/MapScreen/index.tsx @@ -17,6 +17,7 @@ import {getCoords, useLocation} from '../../hooks/useLocation'; import {useIsFullyFocused} from '../../hooks/useIsFullyFocused'; import {useLastKnownLocation} from '../../hooks/useLastSavedLocation'; import {useLocationProviderStatus} from '../../hooks/useLocationProviderStatus'; +import {GPSModal} from './gps/GPSModal'; // This is the default zoom used when the map first loads, and also the zoom // that the map will zoom to if the user clicks the "Locate" button and the @@ -123,6 +124,8 @@ export const MapScreen = () => { onPress={handleAddPress} isLoading={!isFinishedLoading} /> + + ); }; From 6bd04616eb714d79ebd25edbec5b4f7b3edfc7fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20Stefa=C5=84czyk?= Date: Fri, 12 Apr 2024 08:39:47 +0200 Subject: [PATCH 004/123] tracks poc --- android/app/src/main/AndroidManifest.xml | 4 ++ app.json | 12 +++- package-lock.json | 34 ++++++++++-- package.json | 4 +- src/frontend/hooks/tracks/useTracking.ts | 55 +++++++++++++++++++ src/frontend/hooks/tracks/useTracksStore.ts | 39 +++++++++++++ .../screens/MapScreen/gps/GPSEnabled.tsx | 15 ++++- src/frontend/screens/MapScreen/index.tsx | 41 +++++++++++++- 8 files changed, 195 insertions(+), 9 deletions(-) create mode 100644 src/frontend/hooks/tracks/useTracking.ts create mode 100644 src/frontend/hooks/tracks/useTracksStore.ts diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index b4f50a7ce..9816b7164 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,6 +1,10 @@ + + + + =6.9.0" } }, + "node_modules/geojson": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/geojson/-/geojson-0.5.0.tgz", + "integrity": "sha512-/Bx5lEn+qRF4TfQ5aLu6NH+UKtvIv7Lhc487y/c8BdludrCTpiWf9wyI0RTyqg49MFefIAvFDuEi5Dfd/zgNxQ==", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/geojson-rbush": { "version": "3.2.0", "license": "MIT", @@ -23328,6 +23349,11 @@ "node": ">=4" } }, + "node_modules/unimodules-app-loader": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/unimodules-app-loader/-/unimodules-app-loader-4.5.0.tgz", + "integrity": "sha512-q/Xug4K6/20876Xac+tjOLOOAeHEu2zF66LNN/5c8EV4WPEe/+RYZEljN/woQt17KPIB2eyel9dc+d6qUMjUOg==" + }, "node_modules/unique-filename": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", diff --git a/package.json b/package.json index d4715570b..196cd2e95 100644 --- a/package.json +++ b/package.json @@ -44,9 +44,11 @@ "expo-camera": "~14.0.5", "expo-crypto": "~12.8.1", "expo-localization": "~14.8.3", - "expo-location": "~16.5.4", + "expo-location": "~16.5.5", "expo-secure-store": "~12.8.1", "expo-sensors": "~12.9.1", + "expo-task-manager": "~11.7.2", + "geojson": "^0.5.0", "lodash.isequal": "^4.5.0", "nanoid": "^5.0.1", "nodejs-mobile-react-native": "^18.17.7", diff --git a/src/frontend/hooks/tracks/useTracking.ts b/src/frontend/hooks/tracks/useTracking.ts new file mode 100644 index 000000000..b8b7aa2ca --- /dev/null +++ b/src/frontend/hooks/tracks/useTracking.ts @@ -0,0 +1,55 @@ +import * as Location from 'expo-location'; +import * as TaskManager from 'expo-task-manager'; +import {useCallback, useState} from 'react'; +import {FullLocationData, useTracksStore} from './useTracksStore'; + +export const LOCATION_TASK_NAME = 'background-location-task'; + +type LocationCallbackInfo = { + data: {locations: FullLocationData[]} | null; + error: TaskManager.TaskManagerError | null; +}; +export function useTracking() { + const tracksStore = useTracksStore(); + const isTracking = useTracksStore(state => state.isTracking); + const addNewTrackLocations = useCallback( + ({data, error}: LocationCallbackInfo) => { + if (error) { + console.error('Error while processing location update callback', error); + } + if (data?.locations) { + tracksStore.addNewLocations(data.locations); + } + }, + [], + ); + + const startTracking = useCallback(async () => { + TaskManager.defineTask(LOCATION_TASK_NAME, addNewTrackLocations); + + if (isTracking) { + console.warn('Start tracking attempt while tracking already enabled'); + return; + } + const requestForeground = Location.requestForegroundPermissionsAsync; + const requestBackground = Location.requestBackgroundPermissionsAsync; + + const foregroundRequest = await requestForeground(); + if (foregroundRequest.granted) { + const backgroundRequest = await requestBackground(); + if (backgroundRequest.granted) { + await Location.startLocationUpdatesAsync(LOCATION_TASK_NAME, { + accuracy: Location.Accuracy.Highest, + activityType: Location.LocationActivityType.Fitness, + }); + tracksStore.setTracking(true); + } + } + }, []); + + const cancelTracking = useCallback(async () => { + await TaskManager.unregisterTaskAsync(LOCATION_TASK_NAME); + tracksStore.setTracking(false); + }, []); + return {isTracking, startTracking, cancelTracking}; +} diff --git a/src/frontend/hooks/tracks/useTracksStore.ts b/src/frontend/hooks/tracks/useTracksStore.ts new file mode 100644 index 000000000..4bb2ae777 --- /dev/null +++ b/src/frontend/hooks/tracks/useTracksStore.ts @@ -0,0 +1,39 @@ +import {create} from 'zustand'; + +export type LocationData = { + coords: { + latitude: number; + accuracy: number; + longitude: number; + }; + timestamp: number; +}; +export type FullLocationData = { + coords: { + altitude: number; + altitudeAccuracy: number; + latitude: number; + accuracy: number; + longitude: number; + heading: number; + speed: number; + }; + timestamp: number; +}; +type TracksStoreState = { + isTracking: boolean; + locationHistory: FullLocationData[]; + addNewLocations: (locationData: FullLocationData[]) => void; + clearLocationHistory: () => void; + setTracking: (val: boolean) => void; +}; + +export const useTracksStore = create(set => ({ + isTracking: false, + locationHistory: [], + dupa: [], + addNewLocations: data => + set(state => ({locationHistory: [...state.locationHistory, ...data]})), + clearLocationHistory: () => set(() => ({locationHistory: []})), + setTracking: (val: boolean) => set(state => ({isTracking: val})), +})); diff --git a/src/frontend/screens/MapScreen/gps/GPSEnabled.tsx b/src/frontend/screens/MapScreen/gps/GPSEnabled.tsx index 571910c8e..620cbbfb2 100644 --- a/src/frontend/screens/MapScreen/gps/GPSEnabled.tsx +++ b/src/frontend/screens/MapScreen/gps/GPSEnabled.tsx @@ -2,12 +2,23 @@ import * as React from 'react'; import {View} from 'react-native'; import {Button} from '../../../sharedComponents/Button'; import {Text} from '../../../sharedComponents/Text'; +import {useTracking} from '../../../hooks/tracks/useTracking'; +import {useTracksStore} from '../../../hooks/tracks/useTracksStore'; export const GPSEnabled = () => { + const tracking = useTracking(); return ( - ); diff --git a/src/frontend/screens/MapScreen/index.tsx b/src/frontend/screens/MapScreen/index.tsx index c84666778..0035eedf4 100644 --- a/src/frontend/screens/MapScreen/index.tsx +++ b/src/frontend/screens/MapScreen/index.tsx @@ -1,11 +1,18 @@ import * as React from 'react'; -import Mapbox, {UserLocation} from '@rnmapbox/maps'; +import Mapbox, { + LineJoin, + LineLayer, + ShapeSource, + UserLocation, +} from '@rnmapbox/maps'; import config from '../../../config.json'; import {IconButton} from '../../sharedComponents/IconButton'; import { LocationFollowingIcon, LocationNoFollowIcon, } from '../../sharedComponents/icons'; +import {LineString} from 'geojson'; + import {View, StyleSheet} from 'react-native'; import {ObservationMapLayer} from './ObsevationMapLayer'; import {AddButton} from '../../sharedComponents/AddButton'; @@ -18,6 +25,10 @@ import {useIsFullyFocused} from '../../hooks/useIsFullyFocused'; import {useLastKnownLocation} from '../../hooks/useLastSavedLocation'; import {useLocationProviderStatus} from '../../hooks/useLocationProviderStatus'; import {GPSModal} from './gps/GPSModal'; +import { + FullLocationData, + useTracksStore, +} from '../../hooks/tracks/useTracksStore'; // This is the default zoom used when the map first loads, and also the zoom // that the map will zoom to if the user clicks the "Locate" button and the @@ -44,6 +55,8 @@ export const MapScreen = () => { const locationServicesEnabled = !!locationProviderStatus?.locationServicesEnabled; + const tracksStore = useTracksStore(); + const handleAddPress = () => { newDraft(); navigate('PresetChooser'); @@ -105,6 +118,23 @@ export const MapScreen = () => { minDisplacement={MIN_DISPLACEMENT} /> )} + {tracksStore.locationHistory.length > 1 && ( + <> + + + + + )} [ + location.coords.longitude, + location.coords.latitude, + ]), + }; +} From 1f597d71146d5a4c1cf84947294603460fde6d6a Mon Sep 17 00:00:00 2001 From: bohdanprog Date: Fri, 12 Apr 2024 08:39:42 +0200 Subject: [PATCH 005/123] add check if permission granted, add function to request about permission --- .../screens/MapScreen/gps/GPSDisabled.tsx | 4 +++- src/frontend/screens/MapScreen/gps/GPSModal.tsx | 17 +++++++++++------ src/frontend/screens/MapScreen/index.tsx | 2 +- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/frontend/screens/MapScreen/gps/GPSDisabled.tsx b/src/frontend/screens/MapScreen/gps/GPSDisabled.tsx index 72b292396..08fc9aa0d 100644 --- a/src/frontend/screens/MapScreen/gps/GPSDisabled.tsx +++ b/src/frontend/screens/MapScreen/gps/GPSDisabled.tsx @@ -2,8 +2,10 @@ import * as React from 'react'; import {View, Image} from 'react-native'; import {Button} from '../../../sharedComponents/Button'; import {Text} from '../../../sharedComponents/Text'; +import * as Location from 'expo-location'; export const GPSDisabled = () => { + const [status, requestPermission] = Location.useBackgroundPermissions(); return ( { diff --git a/src/frontend/screens/MapScreen/gps/GPSModal.tsx b/src/frontend/screens/MapScreen/gps/GPSModal.tsx index 46551484e..25065c391 100644 --- a/src/frontend/screens/MapScreen/gps/GPSModal.tsx +++ b/src/frontend/screens/MapScreen/gps/GPSModal.tsx @@ -1,20 +1,25 @@ -import React, {FC} from 'react'; +import React from 'react'; import {View, StyleSheet} from 'react-native'; import {GPSDisabled} from './GPSDisabled'; import {GPSEnabled} from './GPSEnabled'; import {useGPSModalContext} from '../../../contexts/GPSModalContext'; +import {useForegroundPermissions} from 'expo-location'; -interface GPSModal { - locationServicesEnabled: boolean; -} -export const GPSModal: FC = ({locationServicesEnabled}) => { +export const GPSModal = () => { const {displayModal} = useGPSModalContext(); + const [permissions] = useForegroundPermissions(); + + console.log(permissions, 'permissions'); return ( <> {displayModal && ( - {locationServicesEnabled ? : } + {permissions && !!permissions.granted ? ( + + ) : ( + + )} )} diff --git a/src/frontend/screens/MapScreen/index.tsx b/src/frontend/screens/MapScreen/index.tsx index 0035eedf4..a083fb88c 100644 --- a/src/frontend/screens/MapScreen/index.tsx +++ b/src/frontend/screens/MapScreen/index.tsx @@ -155,7 +155,7 @@ export const MapScreen = () => { isLoading={!isFinishedLoading} /> - + ); }; From bef576b20535ee9fd993e2df360781e2c86e4b05 Mon Sep 17 00:00:00 2001 From: bohdanprog Date: Fri, 12 Apr 2024 12:20:21 +0200 Subject: [PATCH 006/123] add animation, add function for check permission disable strart tracking button --- src/frontend/hooks/tracks/useTracking.ts | 6 +- .../screens/MapScreen/gps/GPSDisabled.tsx | 80 ++++++++++++++----- .../screens/MapScreen/gps/GPSEnabled.tsx | 45 +++++++---- .../screens/MapScreen/gps/GPSModal.tsx | 27 +++---- src/frontend/screens/MapScreen/index.tsx | 4 +- 5 files changed, 110 insertions(+), 52 deletions(-) diff --git a/src/frontend/hooks/tracks/useTracking.ts b/src/frontend/hooks/tracks/useTracking.ts index b8b7aa2ca..151d388d1 100644 --- a/src/frontend/hooks/tracks/useTracking.ts +++ b/src/frontend/hooks/tracks/useTracking.ts @@ -10,6 +10,7 @@ type LocationCallbackInfo = { error: TaskManager.TaskManagerError | null; }; export function useTracking() { + const [loading, setLoading] = useState(false); const tracksStore = useTracksStore(); const isTracking = useTracksStore(state => state.isTracking); const addNewTrackLocations = useCallback( @@ -25,10 +26,12 @@ export function useTracking() { ); const startTracking = useCallback(async () => { + setLoading(true); TaskManager.defineTask(LOCATION_TASK_NAME, addNewTrackLocations); if (isTracking) { console.warn('Start tracking attempt while tracking already enabled'); + setLoading(false); return; } const requestForeground = Location.requestForegroundPermissionsAsync; @@ -45,11 +48,12 @@ export function useTracking() { tracksStore.setTracking(true); } } + setLoading(false); }, []); const cancelTracking = useCallback(async () => { await TaskManager.unregisterTaskAsync(LOCATION_TASK_NAME); tracksStore.setTracking(false); }, []); - return {isTracking, startTracking, cancelTracking}; + return {isTracking, startTracking, cancelTracking, loading}; } diff --git a/src/frontend/screens/MapScreen/gps/GPSDisabled.tsx b/src/frontend/screens/MapScreen/gps/GPSDisabled.tsx index 08fc9aa0d..91214ca2b 100644 --- a/src/frontend/screens/MapScreen/gps/GPSDisabled.tsx +++ b/src/frontend/screens/MapScreen/gps/GPSDisabled.tsx @@ -1,39 +1,79 @@ import * as React from 'react'; -import {View, Image} from 'react-native'; +import {Image, Linking, StyleSheet} from 'react-native'; import {Button} from '../../../sharedComponents/Button'; import {Text} from '../../../sharedComponents/Text'; import * as Location from 'expo-location'; +import Animated, { + Easing, + FadeInDown, + FadeOutDown, +} from 'react-native-reanimated'; + +const handleOpenSettings = () => { + Linking.sendIntent('android.settings.LOCATION_SOURCE_SETTINGS'); +}; + +interface GPSDisabled { + setIsGranted: React.Dispatch>; +} +export const GPSDisabled: React.FC = ({setIsGranted}) => { + const requestForLocationPermissions = async () => { + const [foregroundPermission, backgroundPermission] = await Promise.all([ + Location.requestForegroundPermissionsAsync(), + Location.requestBackgroundPermissionsAsync(), + ]); + if (foregroundPermission.granted && backgroundPermission.granted) { + setIsGranted(true); + } else if ( + !foregroundPermission.canAskAgain || + !backgroundPermission.canAskAgain + ) { + handleOpenSettings(); + } + }; -export const GPSDisabled = () => { - const [status, requestPermission] = Location.useBackgroundPermissions(); return ( - + - - GPS Disabled - - + GPS Disabled + To create a Track CoMapeo needs access to your location and GPS. - + ); }; + +const styles = StyleSheet.create({ + wrapper: { + padding: 30, + zIndex: 11, + alignItems: 'center', + display: 'flex', + justifyContent: 'center', + }, + image: {marginBottom: 30}, + title: {fontSize: 24, fontWeight: 'bold', textAlign: 'center'}, + description: {fontSize: 20, textAlign: 'center', marginBottom: 30}, + button: {marginBottom: 20, marginVertical: 8.5}, + buttonText: {fontWeight: '500', color: '#fff'}, +}); diff --git a/src/frontend/screens/MapScreen/gps/GPSEnabled.tsx b/src/frontend/screens/MapScreen/gps/GPSEnabled.tsx index 620cbbfb2..a20a48552 100644 --- a/src/frontend/screens/MapScreen/gps/GPSEnabled.tsx +++ b/src/frontend/screens/MapScreen/gps/GPSEnabled.tsx @@ -1,25 +1,42 @@ import * as React from 'react'; -import {View} from 'react-native'; +import {StyleSheet, View} from 'react-native'; import {Button} from '../../../sharedComponents/Button'; import {Text} from '../../../sharedComponents/Text'; import {useTracking} from '../../../hooks/tracks/useTracking'; -import {useTracksStore} from '../../../hooks/tracks/useTracksStore'; +import Animated, { + Easing, + FadeInDown, + FadeOutDown, +} from 'react-native-reanimated'; export const GPSEnabled = () => { - const tracking = useTracking(); + const {isTracking, cancelTracking, startTracking, loading} = useTracking(); + + const handleTracking = () => { + isTracking ? cancelTracking() : startTracking(); + }; + + const getButtonTitle = () => { + if (loading) return 'Loading...'; + if (isTracking) return 'Stop Tracks'; + return 'Start Tracks'; + }; + return ( - - - + ); }; + +const styles = StyleSheet.create({ + wrapper: {paddingHorizontal: 20, paddingVertical: 30}, + buttonText: {fontWeight: '500', color: '#fff'}, +}); diff --git a/src/frontend/screens/MapScreen/gps/GPSModal.tsx b/src/frontend/screens/MapScreen/gps/GPSModal.tsx index 25065c391..08af061a2 100644 --- a/src/frontend/screens/MapScreen/gps/GPSModal.tsx +++ b/src/frontend/screens/MapScreen/gps/GPSModal.tsx @@ -1,28 +1,23 @@ -import React from 'react'; -import {View, StyleSheet} from 'react-native'; +import React, {useEffect, useState} from 'react'; +import {StyleSheet, View} from 'react-native'; import {GPSDisabled} from './GPSDisabled'; import {GPSEnabled} from './GPSEnabled'; -import {useGPSModalContext} from '../../../contexts/GPSModalContext'; import {useForegroundPermissions} from 'expo-location'; export const GPSModal = () => { - const {displayModal} = useGPSModalContext(); const [permissions] = useForegroundPermissions(); + const [isGranted, setIsGranted] = useState(null); - console.log(permissions, 'permissions'); + useEffect(() => { + if (permissions && isGranted === null) { + setIsGranted(permissions!.granted); + } + }, [permissions]); return ( - <> - {displayModal && ( - - {permissions && !!permissions.granted ? ( - - ) : ( - - )} - - )} - + + {isGranted ? : } + ); }; diff --git a/src/frontend/screens/MapScreen/index.tsx b/src/frontend/screens/MapScreen/index.tsx index a083fb88c..4fd35a24b 100644 --- a/src/frontend/screens/MapScreen/index.tsx +++ b/src/frontend/screens/MapScreen/index.tsx @@ -29,6 +29,7 @@ import { FullLocationData, useTracksStore, } from '../../hooks/tracks/useTracksStore'; +import {useGPSModalContext} from '../../contexts/GPSModalContext'; // This is the default zoom used when the map first loads, and also the zoom // that the map will zoom to if the user clicks the "Locate" button and the @@ -41,6 +42,7 @@ const MIN_DISPLACEMENT = 15; export const MAP_STYLE = Mapbox.StyleURL.Outdoors; export const MapScreen = () => { + const {displayModal} = useGPSModalContext(); const [zoom, setZoom] = React.useState(DEFAULT_ZOOM); const isFocused = useIsFullyFocused(); @@ -155,7 +157,7 @@ export const MapScreen = () => { isLoading={!isFinishedLoading} /> - + {displayModal && } ); }; From d9dfbdedfdac2958089705b34a02dbcbc2c626df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20Stefa=C5=84czyk?= Date: Thu, 11 Apr 2024 10:25:12 +0200 Subject: [PATCH 007/123] wip nav changes to allow displaying modal without changing screen --- .../Navigation/ScreenGroups/AppScreens.tsx | 66 +++++++++++++------ src/frontend/hooks/useNavigationStore.ts | 11 ++++ src/frontend/screens/TrackingScreen.tsx | 33 ++++++++++ 3 files changed, 91 insertions(+), 19 deletions(-) create mode 100644 src/frontend/hooks/useNavigationStore.ts create mode 100644 src/frontend/screens/TrackingScreen.tsx diff --git a/src/frontend/Navigation/ScreenGroups/AppScreens.tsx b/src/frontend/Navigation/ScreenGroups/AppScreens.tsx index ed60f5890..f13ded1b8 100644 --- a/src/frontend/Navigation/ScreenGroups/AppScreens.tsx +++ b/src/frontend/Navigation/ScreenGroups/AppScreens.tsx @@ -1,5 +1,5 @@ import {createBottomTabNavigator} from '@react-navigation/bottom-tabs'; -import {NavigatorScreenParams} from '@react-navigation/native'; +import {NavigatorScreenParams, useNavigation} from '@react-navigation/native'; import * as React from 'react'; import MaterialIcons from 'react-native-vector-icons/MaterialIcons'; @@ -45,10 +45,12 @@ import { EditScreen as DeviceNameEditScreen, createNavigationOptions as createDeviceNameEditNavOptions, } from '../../screens/Settings/ProjectSettings/DeviceName/EditScreen'; +import {useNavigationStore} from '../../hooks/useNavigationStore'; export type HomeTabsList = { Map: undefined; Camera: undefined; + Tracking: undefined; }; export type AppList = { @@ -125,24 +127,50 @@ export type AppList = { }; const Tab = createBottomTabNavigator(); - -const HomeTabs = () => ( - ({ - tabBarIcon: ({color}) => { - const iconName = route.name === 'Map' ? 'map' : 'photo-camera'; - return ; - }, - header: () => , - headerTransparent: true, - tabBarTestID: 'tabBarButton' + route.name, - })} - initialRouteName="Map" - backBehavior="initialRoute"> - - - -); +const HomeTabs = () => { + const navigationStore = useNavigationStore(); + return ( + { + if (defaultPrevented) { + return; + } + navigationStore.setCurrentTab(target?.split('-')[0] || 'Map'); + }, + }} + screenOptions={({route}) => ({ + tabBarIcon: ({color}) => { + const icons = { + Map: 'map', + Camera: 'photo-camera', + Tracking: 'nordic-walking', + }; + console.log(navigationStore.currentTab); + return ( + + ); + }, + header: () => , + headerTransparent: true, + tabBarTestID: 'tabBarButton' + route.name, + })} + initialRouteName="Map" + backBehavior="initialRoute"> + + + <>} + listeners={({navigation}) => ({ + tabPress: e => { + navigation.navigate('Map'); + }, + })} + /> + + ); +}; // **NOTE**: No hooks allowed here (this is not a component, it is a function // that returns a react element) diff --git a/src/frontend/hooks/useNavigationStore.ts b/src/frontend/hooks/useNavigationStore.ts new file mode 100644 index 000000000..8b6fce22e --- /dev/null +++ b/src/frontend/hooks/useNavigationStore.ts @@ -0,0 +1,11 @@ +import {create} from 'zustand'; + +type NavigationStoreState = { + currentTab: string; + setCurrentTab: (tab: string) => void; +}; + +export const useNavigationStore = create(set => ({ + currentTab: 'Map', + setCurrentTab: (tab: string) => set(() => ({currentTab: tab})), +})); diff --git a/src/frontend/screens/TrackingScreen.tsx b/src/frontend/screens/TrackingScreen.tsx new file mode 100644 index 000000000..b2f67c0e7 --- /dev/null +++ b/src/frontend/screens/TrackingScreen.tsx @@ -0,0 +1,33 @@ +import * as React from 'react'; +import {View, StyleSheet} from 'react-native'; +import {useIsFocused} from '@react-navigation/native'; + +import {CameraView} from '../sharedComponents/CameraView'; +import {NativeHomeTabsNavigationProps} from '../sharedTypes'; +import {CapturedPictureMM} from '../contexts/PhotoPromiseContext/types'; +import {useDraftObservation} from '../hooks/useDraftObservation'; + +export const TrackingScreen = ({ + navigation, +}: NativeHomeTabsNavigationProps<'Tracking'>) => { + const isFocused = useIsFocused(); + const {newDraft} = useDraftObservation(); + + function handleAddPress(photoPromise: Promise) { + newDraft(photoPromise); + navigation.navigate('PresetChooser'); + } + + return ( + + {isFocused ? : null} + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: 'black', + }, +}); From 9620c124f021d19ae689e0ce43c96126721b1002 Mon Sep 17 00:00:00 2001 From: bohdanprog Date: Thu, 11 Apr 2024 14:15:36 +0200 Subject: [PATCH 008/123] add custom tab bar icon and label --- .../Navigation/ScreenGroups/AppScreens.tsx | 101 +++++++++++++----- .../ScreenGroups/TabBar/TabBarIcon.tsx | 22 ++++ .../ScreenGroups/TabBar/TabBarLabel.tsx | 16 +++ src/frontend/hooks/useNavigationStore.ts | 11 +- 4 files changed, 119 insertions(+), 31 deletions(-) create mode 100644 src/frontend/Navigation/ScreenGroups/TabBar/TabBarIcon.tsx create mode 100644 src/frontend/Navigation/ScreenGroups/TabBar/TabBarLabel.tsx diff --git a/src/frontend/Navigation/ScreenGroups/AppScreens.tsx b/src/frontend/Navigation/ScreenGroups/AppScreens.tsx index f13ded1b8..27d5a4d90 100644 --- a/src/frontend/Navigation/ScreenGroups/AppScreens.tsx +++ b/src/frontend/Navigation/ScreenGroups/AppScreens.tsx @@ -1,7 +1,12 @@ import {createBottomTabNavigator} from '@react-navigation/bottom-tabs'; -import {NavigatorScreenParams, useNavigation} from '@react-navigation/native'; +import { + EventArg, + getFocusedRouteNameFromRoute, + NavigatorScreenParams, + useNavigation, + useRoute, +} from '@react-navigation/native'; import * as React from 'react'; -import MaterialIcons from 'react-native-vector-icons/MaterialIcons'; import {HomeHeader} from '../../sharedComponents/HomeHeader'; import {RootStack} from '../AppStack'; @@ -46,6 +51,10 @@ import { createNavigationOptions as createDeviceNameEditNavOptions, } from '../../screens/Settings/ProjectSettings/DeviceName/EditScreen'; import {useNavigationStore} from '../../hooks/useNavigationStore'; +import {TabBarLabel} from './TabBar/TabBarLabel'; +import {TabBarIcon} from './TabBar/TabBarIcon'; + +export type TabName = keyof HomeTabsList; export type HomeTabsList = { Map: undefined; @@ -128,45 +137,83 @@ export type AppList = { const Tab = createBottomTabNavigator(); const HomeTabs = () => { - const navigationStore = useNavigationStore(); + const {setCurrentTab, currentTab} = useNavigationStore(); + const navigation = useNavigation(); + const route = useRoute(); + + const handleTabPress = ({ + target, + preventDefault, + }: EventArg<'tabPress', true, undefined>) => { + if (target?.split('-')[0] === 'Tracking') { + preventDefault(); + const currentTab = getFocusedRouteNameFromRoute(route); + if (currentTab === 'Camera') { + navigation.navigate('Map'); + } + } + setCurrentTab((target?.split('-')[0] || 'Map') as unknown as TabName); + }; + return ( { - if (defaultPrevented) { - return; - } - navigationStore.setCurrentTab(target?.split('-')[0] || 'Map'); - }, + tabPress: handleTabPress, }} screenOptions={({route}) => ({ - tabBarIcon: ({color}) => { - const icons = { - Map: 'map', - Camera: 'photo-camera', - Tracking: 'nordic-walking', - }; - console.log(navigationStore.currentTab); - return ( - - ); - }, header: () => , headerTransparent: true, tabBarTestID: 'tabBarButton' + route.name, })} initialRouteName="Map" backBehavior="initialRoute"> - - + ( + + ), + tabBarLabel: params => ( + + ), + }} + /> + ( + + ), + tabBarLabel: params => ( + + ), + }} + /> ( + + ), + tabBarLabel: params => ( + + ), + }} children={() => <>} - listeners={({navigation}) => ({ - tabPress: e => { - navigation.navigate('Map'); - }, - })} /> ); diff --git a/src/frontend/Navigation/ScreenGroups/TabBar/TabBarIcon.tsx b/src/frontend/Navigation/ScreenGroups/TabBar/TabBarIcon.tsx new file mode 100644 index 000000000..25786f48d --- /dev/null +++ b/src/frontend/Navigation/ScreenGroups/TabBar/TabBarIcon.tsx @@ -0,0 +1,22 @@ +import * as React from 'react'; +import MaterialIcons from 'react-native-vector-icons/MaterialIcons'; +import {FC} from 'react'; + +export interface TabBarIcon { + size: number; + focused: boolean; + color: string; + isFocused: boolean; + iconName: string; +} +export const TabBarIcon: FC = ({size, isFocused, iconName}) => { + const color1 = 'rgb(0, 122, 255)'; + const color2 = '#8E8E8F'; + return ( + + ); +}; diff --git a/src/frontend/Navigation/ScreenGroups/TabBar/TabBarLabel.tsx b/src/frontend/Navigation/ScreenGroups/TabBar/TabBarLabel.tsx new file mode 100644 index 000000000..689c49e40 --- /dev/null +++ b/src/frontend/Navigation/ScreenGroups/TabBar/TabBarLabel.tsx @@ -0,0 +1,16 @@ +import * as React from 'react'; +import {Text} from 'react-native'; +import {FC} from 'react'; +import {LabelPosition} from '@react-navigation/bottom-tabs/lib/typescript/src/types'; + +export interface TabBarLabel { + isFocused: boolean; + color: string; + position: LabelPosition; + children: string; +} +export const TabBarLabel: FC = ({children, isFocused}) => { + const color1 = 'rgb(0, 122, 255)'; + const color2 = '#8E8E8F'; + return {children}; +}; diff --git a/src/frontend/hooks/useNavigationStore.ts b/src/frontend/hooks/useNavigationStore.ts index 8b6fce22e..840eb1d02 100644 --- a/src/frontend/hooks/useNavigationStore.ts +++ b/src/frontend/hooks/useNavigationStore.ts @@ -1,11 +1,14 @@ import {create} from 'zustand'; +import {HomeTabsList} from '../Navigation/ScreenGroups/AppScreens'; + +type TabName = keyof HomeTabsList; type NavigationStoreState = { - currentTab: string; - setCurrentTab: (tab: string) => void; + currentTab: TabName; + setCurrentTab: (tab: TabName) => void; }; export const useNavigationStore = create(set => ({ - currentTab: 'Map', - setCurrentTab: (tab: string) => set(() => ({currentTab: tab})), + currentTab: 'Map' as TabName, + setCurrentTab: (tab: TabName) => set(() => ({currentTab: tab})), })); From 1d8da9e61fccdd58a00ffefbbfc05a46aecf24f3 Mon Sep 17 00:00:00 2001 From: bohdanprog Date: Thu, 11 Apr 2024 17:12:40 +0200 Subject: [PATCH 009/123] add modal with different options --- .../Navigation/ScreenGroups/AppScreens.tsx | 16 +++++--- src/frontend/contexts/ExternalProviders.tsx | 9 +++-- src/frontend/contexts/GPSModalContext.tsx | 36 +++++++++++++++++ src/frontend/images/alert-icon.png | Bin 0 -> 5067 bytes .../screens/MapScreen/gps/GPSDisabled.tsx | 37 ++++++++++++++++++ .../screens/MapScreen/gps/GPSEnabled.tsx | 14 +++++++ .../screens/MapScreen/gps/GPSModal.tsx | 34 ++++++++++++++++ src/frontend/screens/MapScreen/index.tsx | 3 ++ 8 files changed, 141 insertions(+), 8 deletions(-) create mode 100644 src/frontend/contexts/GPSModalContext.tsx create mode 100644 src/frontend/images/alert-icon.png create mode 100644 src/frontend/screens/MapScreen/gps/GPSDisabled.tsx create mode 100644 src/frontend/screens/MapScreen/gps/GPSEnabled.tsx create mode 100644 src/frontend/screens/MapScreen/gps/GPSModal.tsx diff --git a/src/frontend/Navigation/ScreenGroups/AppScreens.tsx b/src/frontend/Navigation/ScreenGroups/AppScreens.tsx index 27d5a4d90..04dcd5552 100644 --- a/src/frontend/Navigation/ScreenGroups/AppScreens.tsx +++ b/src/frontend/Navigation/ScreenGroups/AppScreens.tsx @@ -53,6 +53,7 @@ import { import {useNavigationStore} from '../../hooks/useNavigationStore'; import {TabBarLabel} from './TabBar/TabBarLabel'; import {TabBarIcon} from './TabBar/TabBarIcon'; +import {useGPSModalContext} from '../../contexts/GPSModalContext'; export type TabName = keyof HomeTabsList; @@ -140,17 +141,22 @@ const HomeTabs = () => { const {setCurrentTab, currentTab} = useNavigationStore(); const navigation = useNavigation(); const route = useRoute(); + const {setDisplayModal} = useGPSModalContext(); const handleTabPress = ({ target, preventDefault, }: EventArg<'tabPress', true, undefined>) => { - if (target?.split('-')[0] === 'Tracking') { + const targetTab = target?.split('-')[0]; + if (targetTab === 'Tracking') { preventDefault(); - const currentTab = getFocusedRouteNameFromRoute(route); - if (currentTab === 'Camera') { - navigation.navigate('Map'); - } + setDisplayModal(true); + } else { + setDisplayModal(false); + } + const currentTab = getFocusedRouteNameFromRoute(route); + if (currentTab === 'Camera') { + navigation.navigate('Map'); } setCurrentTab((target?.split('-')[0] || 'Map') as unknown as TabName); }; diff --git a/src/frontend/contexts/ExternalProviders.tsx b/src/frontend/contexts/ExternalProviders.tsx index 61a09d03d..898afd1e9 100644 --- a/src/frontend/contexts/ExternalProviders.tsx +++ b/src/frontend/contexts/ExternalProviders.tsx @@ -11,6 +11,7 @@ import { // See https://github.com/gorhom/react-native-bottom-sheet/issues/1157 import {BottomSheetModalProvider} from '@gorhom/bottom-sheet'; import {AppStackList} from '../Navigation/AppStack'; +import {GPSModalContextProvider} from './GPSModalContext'; type ExternalProvidersProp = { children: React.ReactNode; @@ -26,9 +27,11 @@ export const ExternalProviders = ({ return ( - - {children} - + + + {children} + + ); diff --git a/src/frontend/contexts/GPSModalContext.tsx b/src/frontend/contexts/GPSModalContext.tsx new file mode 100644 index 000000000..892b0433e --- /dev/null +++ b/src/frontend/contexts/GPSModalContext.tsx @@ -0,0 +1,36 @@ +import React, { + createContext, + Dispatch, + SetStateAction, + useContext, + useState, +} from 'react'; + +interface GPSModalContext { + displayModal: boolean; + setDisplayModal: Dispatch>; +} + +const GPSModalContext = createContext(null); + +const GPSModalContextProvider = ({children}: {children: React.ReactNode}) => { + const [displayModal, setDisplayModal] = useState(false); + + return ( + + {children} + + ); +}; + +function useGPSModalContext() { + const context = useContext(GPSModalContext); + if (!context) { + throw new Error( + 'useBottomSheetContext must be used within a BottomSheetContextProvider', + ); + } + return context; +} + +export {GPSModalContextProvider, useGPSModalContext}; diff --git a/src/frontend/images/alert-icon.png b/src/frontend/images/alert-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..8bcb094e8376df7602eef6d3ccc866fe802ac7a8 GIT binary patch literal 5067 zcmV;+6Ey6JP)gzWt~R z?PPvQ-Fls>TjyKf-|zg+@BB^~a}gv60xxgB*YW6icdoB%FO*!cKc#|_pio71+&uHl zGv3orKiyM&*|KH5%IRF-$^ss1%9#r!B?T%3r-v2M%HMFq4c--3Tw(Vwx#SXW%a$!> z=FFL9-MV$2&Y6=ZPX_FB{P5w!L3oW_fAYyEJpefXdn%-tVM-t+0V)d6Yp=cLb1VwV z-o1OhBS(&SYzq*3eDvti-s#h)?e>*dUTN>s(a{l%9XmE?Zf-WL&zw0El$V$10Fdpu zbLYamUcf|{7b{?~0u`zi4{yP7tZ8X!@vEw;yrV~tdfnaKer08)mrkes_V#vf{P^)6 zYs#0EmD%;FQ>S|F+11q*j2bm65Rd`esZ=TlfGV2oW00*$L*{m-{cx`QMenO&3`vS$w<#K*9ne;k4JM9fr#;yehHZAadKTw$< zlgZ??o6~(@S5~*kiIKUhuf95`;yJw()}B}#Jn+B+yfF8SLEbYCT80HwUhCw+q}y-5 zon2p*^rcD(F~L83_H2Usju|s1p$AR?j6g{U5TE0KB!;mMgN*h8#ae!jX_Xb=Y-e>o z2SB<{7SX-kvFyHm`?8l^c3A*;0xNg!v4Z>t;KBoi@aKAa{)P=3ER*!Gu*nw~fRt2) z6H>c`)=9un-I8KRg8$aq0>o{!4dXn4bnd~lhD9vcrI1M$5P(yWtd3{IN`T6UrTU9Z zRxcx4QBeU%Sv^MpU}{l=u*Ds+Dh>(akUjQV?2z7|uZQ*Zp#G(oUTT4mx+TRbfTHFp zRXnLOsgO}gRX8bCsh&Q4`poL;>RMe>tqP9ko;nY3f$q_!<*;nhp)bDp;*eUvU4W!> z8G)8zt^1`_KC5yWolEO)G5`o@b#-;w0|yS|Xf?56QTsJmzy=Id#1=VU2wULk&l9o{ z{_*3-6RNNU1#o1rQ!1AN908W%__%T7#@5%@Pn|e%;$Xq^x!iI6!KAW69lbvZ!Io;~{; z*Is+=cclt5ObJCViIj}+_KcE(|XbGos z`5=A#@yAK0YK}p&6BR03p)xgh+;PXFdYg;P1r!*zZQHgLJ9qBfujlB}p-#Ox3z$^0 zTjl`UhCR!MUt`twR{kGk)^`<{@Y{$ZSUXt1IRm&qNu^_pw086!Y;iv8VsKAc)3CK+wR zJi1awbusErnQ7Cenf$I$zW4+AZ0eUnR&6+fuXfvQxBXo1$vEwz0p-+#mrl})jg&7~u;5{}d=Hwhh^R%L{NRHRE}`MU zjnRuPhgCB6n3IIMIj-ALFx*^_QTR-p18M2EjRJ^KDKW~r0#m0>om5j(^Y`W}CcM~L zvu53j$kO@tXbpl-o4vKA=L%3 z4knC(rg{VAV%b+tZ`AsQj1tLPxYxWVf>6%{ouia_*NDi;_cD?Uk^|YJ=9Juny(<6^)WzP>X*9n z&O862)NInrxnMvcYLswqaG=o|>dXL#k5R|jHj$t~FGg_^2UDF%BKHgsBwg56V{LOx z2}Li+4t64RS`~x03@fFmuHT}5kb%JtLfQIn52H>WFzQn|*WK=LlJ1JoC1y>hF=3qy z0i_ObNq~Yqid~t99(u^K2?403OP8AY^XHq#9(&A!SPDTyMEXPN0YGG z&ZmkFH(57cEMmN^Odczp-cdq%v@V*{(9rNR6QjTuz;GZC_E5jkC`4lSqYOhHee_Yg z#4Q?>{ps4ZYhM;1ZK`{_4s~`Dk5Ob)|Ho*L4es>}S7E&5h%x4m`dLR4IwI2DnI=X7 zq{knB+^zv=$&w{T4sOA6D_5>G3c^{oVZ$Pc9I=RA%uE6Ci*w_wK8*5+U!hfC;{LCf z>LH5N*b!ZUYA?`*zFLBM6QjT+Kv0j~tX{pkAV>)3dC=v{ms>yqEN(+DKqfo2qt4Dw zaV|9IR-7^SoFQjNs%M9}-3{XO#MiA`XVnT2UVQOIyDlcEq*FVXg$;{Kuv3yt7>s4) zn!3X#l1Mo3Tm%c08>2@+Qz{3OFz9yC#gk1;;tVzw9|%ZJ6hSp%7EA1`(&%zEst2Q} zH~A6xl3~mDWvj=4Al@h_f~HzKdlJ=?Dps4A1a)(EsV{ZwPt-B6>!^{BLsZABAO!V= zB!$XYplAZ+WLb}#>=RlZF5;Zx;KPm*01lNy?T{%`rkJ5fm;hq$<(4?qkRg5Y9-_xw z-j;I@oH4gMaw=^e65H~|F>GG`4Td5Bgg@leZZIkr6`1IR`?17juUtKyHX*#2FgEw4 zF;@&KXliOAH^~`v3z(QS>_jYp!M+TYNri;_u|&Zes)!V4RNtO`w2BNrwq2fr^`kK6 z1gvDEY=)Z(61O3IYT^*=5eupU3x;ZAoO25BFTM1ViA&^MlQU$K&_ooQxTbiVw}1bB z+u|ZU#+-G1eLZi8d?Zo;@knzYe)!>G6O+Iw7~pKyV8yj|Fey%v0k`@~eFa#cQDmd= zcP~xqQ9XVb<6^Cr&jLjf7rBxoL6{gM+S)mLHBgCyJCqi)KanOKlv|J`6_UDhjLbck z7mB!Xteyj;^+{(*EG5e=3(DHxV`38gApoP&0|m=BAs74oNTa=U`75A2g#A-qL7L?fp)~iB-odzmt71jz;O`-u6^pMr%apz2_S^l zHi=e)C^@Mj>K7<1-a{Zeq>g(aKZ-axK|2^mRFn7&vGSB2aJ0PFzcew)^y$;>+WA7x zzjDd0FbH69hFmRRP9Wvf;yE^*OJj5wV5^vAAj6i_2t`us3iW*|Maj`J>{X9E^2iDqyFWK2 z5Ew;76BfCsCZOQ(#ch+KB4CcJTD5Av>W&}PuDW*USf_wXlRqW$*mx9WEu&mkopcgI z76@gGT$V-5vKhEK_UpR3x{?5u?`HwhTW`H(N-bi~@2Q(wx~$9&IqD?jSys{FMRU`8 z`g$D1pp*SiY1xUK14>2Gn>TO%EiHukBNCaEmF8UbumdZ1{P=O(;*qu(E$bqjK?s(e z9>E+F65D0ypq|a;7NgWCo?Ihi|F@<@f{N*dn7AZ&`m{Ow!3U;8iz;J}C911Uc|(KA z)z>%f+O_Kdx#{GnD-Zx)Ik^cr*-o3+8O=e+pSeH*-1r>X|DvExde_C|Z+AB~Hol^O z|DutecK|>qSFJK9Uw+wc_aU>tSylC;qcdha|M}dxyU`xftT8<;EiJtaIda~(o}(Od zPqu)vQf+MuCNwr~{QC0c{{&Fgu*;o0_V`hVnvX6d?ab?XF!b>c02A&iuUdf=L=6imQMNFUK=S3mB?i zMtvdFuv0$Qa)J1JGXeya%ZO3^N!8-TM?PP;aGC5&pCM5pGp44q^B)Q&=NANuf+78K zw1D_kOzjeb7(wk~`h*U3yz|aG-yaF~s_i$wDOzUhuYUD?xdWe>;?p_)vCzSyS;6=e z>i5w9$(%ZMD$U#yrZ54P`h+bq?oVE9u<(_N$*Fz&PM`^E`z|q=$%!VV(mQcU|Fs#RBA2;y)TmKi-awElbbYY`ZZ57Q zryv`XpvhJvTuiLl#*Yv&?&vG8yz=i07cM-c0mTtBC&tOR?c47fw`0c_@_mX9=&H7@ z`Nsw{`PzYnBg#IbGwM7t!fGc55;n$pXY}IDH{bl3?Akg~tS?0NaB9K?(({3wYA4Y z_bH|B8@4w#7IhEwkRN)!ze|<5RJ#@Z6m6aQ>8DTR9~{Uuv>p%D`ByND+278%GXb6H zsaj*;&uY6}fVImHd!4}8!Gj0?L#*3sViw}vvYr3k+SazRSlP~+ng)zcpO-&UI&^?=-(ARZdS=s%f{7EmJenY!s`n-Qx*)Y;0R>aYn$L|8nI!NghGu3hU%u4u?*3OZ1QFZ*x8bwT{-&?A%HH%Z@=Nf=SA*Yy?SDKHv50NY*LX&-zS*wk%F3@oJpJ{)Ry}4RF z72K$st!-#(+AzQ>#{WgKXv-M=umt;ViWf|CUn83!hmkcO-^#xiAJpPFYeLiRt7Hs! z^CcfZA+L*+CEOOK(7DGjF-z<~MJ34l03f!E5#R9RHy#6=%)Mbe@2_%l=7AVE7Dhk(=OprJHalumN`Rsde^JG`8TrS< h%6p||%AX`=`2)JX-WLf{z;gfq002ovPDHLkV1jntx^Dmg literal 0 HcmV?d00001 diff --git a/src/frontend/screens/MapScreen/gps/GPSDisabled.tsx b/src/frontend/screens/MapScreen/gps/GPSDisabled.tsx new file mode 100644 index 000000000..72b292396 --- /dev/null +++ b/src/frontend/screens/MapScreen/gps/GPSDisabled.tsx @@ -0,0 +1,37 @@ +import * as React from 'react'; +import {View, Image} from 'react-native'; +import {Button} from '../../../sharedComponents/Button'; +import {Text} from '../../../sharedComponents/Text'; + +export const GPSDisabled = () => { + return ( + + + + + GPS Disabled + + + To create a Track CoMapeo needs access to your location and GPS. + + + + ); +}; diff --git a/src/frontend/screens/MapScreen/gps/GPSEnabled.tsx b/src/frontend/screens/MapScreen/gps/GPSEnabled.tsx new file mode 100644 index 000000000..571910c8e --- /dev/null +++ b/src/frontend/screens/MapScreen/gps/GPSEnabled.tsx @@ -0,0 +1,14 @@ +import * as React from 'react'; +import {View} from 'react-native'; +import {Button} from '../../../sharedComponents/Button'; +import {Text} from '../../../sharedComponents/Text'; + +export const GPSEnabled = () => { + return ( + + + + ); +}; diff --git a/src/frontend/screens/MapScreen/gps/GPSModal.tsx b/src/frontend/screens/MapScreen/gps/GPSModal.tsx new file mode 100644 index 000000000..46551484e --- /dev/null +++ b/src/frontend/screens/MapScreen/gps/GPSModal.tsx @@ -0,0 +1,34 @@ +import React, {FC} from 'react'; +import {View, StyleSheet} from 'react-native'; +import {GPSDisabled} from './GPSDisabled'; +import {GPSEnabled} from './GPSEnabled'; +import {useGPSModalContext} from '../../../contexts/GPSModalContext'; + +interface GPSModal { + locationServicesEnabled: boolean; +} +export const GPSModal: FC = ({locationServicesEnabled}) => { + const {displayModal} = useGPSModalContext(); + + return ( + <> + {displayModal && ( + + {locationServicesEnabled ? : } + + )} + + ); +}; + +const styles = StyleSheet.create({ + wrapper: { + position: 'absolute', + bottom: 0, + left: 0, + width: '100%', + borderTopLeftRadius: 10, + borderTopRightRadius: 10, + backgroundColor: '#fff', + }, +}); diff --git a/src/frontend/screens/MapScreen/index.tsx b/src/frontend/screens/MapScreen/index.tsx index 2ab24bf32..c84666778 100644 --- a/src/frontend/screens/MapScreen/index.tsx +++ b/src/frontend/screens/MapScreen/index.tsx @@ -17,6 +17,7 @@ import {getCoords, useLocation} from '../../hooks/useLocation'; import {useIsFullyFocused} from '../../hooks/useIsFullyFocused'; import {useLastKnownLocation} from '../../hooks/useLastSavedLocation'; import {useLocationProviderStatus} from '../../hooks/useLocationProviderStatus'; +import {GPSModal} from './gps/GPSModal'; // This is the default zoom used when the map first loads, and also the zoom // that the map will zoom to if the user clicks the "Locate" button and the @@ -123,6 +124,8 @@ export const MapScreen = () => { onPress={handleAddPress} isLoading={!isFinishedLoading} /> + + ); }; From 7245fe71bab27e5ddaf87f982921680ad867fb91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20Stefa=C5=84czyk?= Date: Fri, 12 Apr 2024 08:39:47 +0200 Subject: [PATCH 010/123] tracks poc --- android/app/src/main/AndroidManifest.xml | 4 ++ app.json | 12 +++- package-lock.json | 34 ++++++++++-- package.json | 4 +- src/frontend/hooks/tracks/useTracking.ts | 55 +++++++++++++++++++ src/frontend/hooks/tracks/useTracksStore.ts | 39 +++++++++++++ .../screens/MapScreen/gps/GPSEnabled.tsx | 15 ++++- src/frontend/screens/MapScreen/index.tsx | 41 +++++++++++++- 8 files changed, 195 insertions(+), 9 deletions(-) create mode 100644 src/frontend/hooks/tracks/useTracking.ts create mode 100644 src/frontend/hooks/tracks/useTracksStore.ts diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index b4f50a7ce..9816b7164 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,6 +1,10 @@ + + + + =6.9.0" } }, + "node_modules/geojson": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/geojson/-/geojson-0.5.0.tgz", + "integrity": "sha512-/Bx5lEn+qRF4TfQ5aLu6NH+UKtvIv7Lhc487y/c8BdludrCTpiWf9wyI0RTyqg49MFefIAvFDuEi5Dfd/zgNxQ==", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/geojson-rbush": { "version": "3.2.0", "license": "MIT", @@ -23334,6 +23355,11 @@ "node": ">=4" } }, + "node_modules/unimodules-app-loader": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/unimodules-app-loader/-/unimodules-app-loader-4.5.0.tgz", + "integrity": "sha512-q/Xug4K6/20876Xac+tjOLOOAeHEu2zF66LNN/5c8EV4WPEe/+RYZEljN/woQt17KPIB2eyel9dc+d6qUMjUOg==" + }, "node_modules/unique-filename": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", diff --git a/package.json b/package.json index 65e8126ed..42f66f943 100644 --- a/package.json +++ b/package.json @@ -46,9 +46,11 @@ "expo-camera": "~14.0.5", "expo-crypto": "~12.8.1", "expo-localization": "~14.8.3", - "expo-location": "~16.5.4", + "expo-location": "~16.5.5", "expo-secure-store": "~12.8.1", "expo-sensors": "~12.9.1", + "expo-task-manager": "~11.7.2", + "geojson": "^0.5.0", "lodash.isequal": "^4.5.0", "nanoid": "^5.0.1", "nodejs-mobile-react-native": "^18.17.7", diff --git a/src/frontend/hooks/tracks/useTracking.ts b/src/frontend/hooks/tracks/useTracking.ts new file mode 100644 index 000000000..b8b7aa2ca --- /dev/null +++ b/src/frontend/hooks/tracks/useTracking.ts @@ -0,0 +1,55 @@ +import * as Location from 'expo-location'; +import * as TaskManager from 'expo-task-manager'; +import {useCallback, useState} from 'react'; +import {FullLocationData, useTracksStore} from './useTracksStore'; + +export const LOCATION_TASK_NAME = 'background-location-task'; + +type LocationCallbackInfo = { + data: {locations: FullLocationData[]} | null; + error: TaskManager.TaskManagerError | null; +}; +export function useTracking() { + const tracksStore = useTracksStore(); + const isTracking = useTracksStore(state => state.isTracking); + const addNewTrackLocations = useCallback( + ({data, error}: LocationCallbackInfo) => { + if (error) { + console.error('Error while processing location update callback', error); + } + if (data?.locations) { + tracksStore.addNewLocations(data.locations); + } + }, + [], + ); + + const startTracking = useCallback(async () => { + TaskManager.defineTask(LOCATION_TASK_NAME, addNewTrackLocations); + + if (isTracking) { + console.warn('Start tracking attempt while tracking already enabled'); + return; + } + const requestForeground = Location.requestForegroundPermissionsAsync; + const requestBackground = Location.requestBackgroundPermissionsAsync; + + const foregroundRequest = await requestForeground(); + if (foregroundRequest.granted) { + const backgroundRequest = await requestBackground(); + if (backgroundRequest.granted) { + await Location.startLocationUpdatesAsync(LOCATION_TASK_NAME, { + accuracy: Location.Accuracy.Highest, + activityType: Location.LocationActivityType.Fitness, + }); + tracksStore.setTracking(true); + } + } + }, []); + + const cancelTracking = useCallback(async () => { + await TaskManager.unregisterTaskAsync(LOCATION_TASK_NAME); + tracksStore.setTracking(false); + }, []); + return {isTracking, startTracking, cancelTracking}; +} diff --git a/src/frontend/hooks/tracks/useTracksStore.ts b/src/frontend/hooks/tracks/useTracksStore.ts new file mode 100644 index 000000000..4bb2ae777 --- /dev/null +++ b/src/frontend/hooks/tracks/useTracksStore.ts @@ -0,0 +1,39 @@ +import {create} from 'zustand'; + +export type LocationData = { + coords: { + latitude: number; + accuracy: number; + longitude: number; + }; + timestamp: number; +}; +export type FullLocationData = { + coords: { + altitude: number; + altitudeAccuracy: number; + latitude: number; + accuracy: number; + longitude: number; + heading: number; + speed: number; + }; + timestamp: number; +}; +type TracksStoreState = { + isTracking: boolean; + locationHistory: FullLocationData[]; + addNewLocations: (locationData: FullLocationData[]) => void; + clearLocationHistory: () => void; + setTracking: (val: boolean) => void; +}; + +export const useTracksStore = create(set => ({ + isTracking: false, + locationHistory: [], + dupa: [], + addNewLocations: data => + set(state => ({locationHistory: [...state.locationHistory, ...data]})), + clearLocationHistory: () => set(() => ({locationHistory: []})), + setTracking: (val: boolean) => set(state => ({isTracking: val})), +})); diff --git a/src/frontend/screens/MapScreen/gps/GPSEnabled.tsx b/src/frontend/screens/MapScreen/gps/GPSEnabled.tsx index 571910c8e..620cbbfb2 100644 --- a/src/frontend/screens/MapScreen/gps/GPSEnabled.tsx +++ b/src/frontend/screens/MapScreen/gps/GPSEnabled.tsx @@ -2,12 +2,23 @@ import * as React from 'react'; import {View} from 'react-native'; import {Button} from '../../../sharedComponents/Button'; import {Text} from '../../../sharedComponents/Text'; +import {useTracking} from '../../../hooks/tracks/useTracking'; +import {useTracksStore} from '../../../hooks/tracks/useTracksStore'; export const GPSEnabled = () => { + const tracking = useTracking(); return ( - ); diff --git a/src/frontend/screens/MapScreen/index.tsx b/src/frontend/screens/MapScreen/index.tsx index c84666778..0035eedf4 100644 --- a/src/frontend/screens/MapScreen/index.tsx +++ b/src/frontend/screens/MapScreen/index.tsx @@ -1,11 +1,18 @@ import * as React from 'react'; -import Mapbox, {UserLocation} from '@rnmapbox/maps'; +import Mapbox, { + LineJoin, + LineLayer, + ShapeSource, + UserLocation, +} from '@rnmapbox/maps'; import config from '../../../config.json'; import {IconButton} from '../../sharedComponents/IconButton'; import { LocationFollowingIcon, LocationNoFollowIcon, } from '../../sharedComponents/icons'; +import {LineString} from 'geojson'; + import {View, StyleSheet} from 'react-native'; import {ObservationMapLayer} from './ObsevationMapLayer'; import {AddButton} from '../../sharedComponents/AddButton'; @@ -18,6 +25,10 @@ import {useIsFullyFocused} from '../../hooks/useIsFullyFocused'; import {useLastKnownLocation} from '../../hooks/useLastSavedLocation'; import {useLocationProviderStatus} from '../../hooks/useLocationProviderStatus'; import {GPSModal} from './gps/GPSModal'; +import { + FullLocationData, + useTracksStore, +} from '../../hooks/tracks/useTracksStore'; // This is the default zoom used when the map first loads, and also the zoom // that the map will zoom to if the user clicks the "Locate" button and the @@ -44,6 +55,8 @@ export const MapScreen = () => { const locationServicesEnabled = !!locationProviderStatus?.locationServicesEnabled; + const tracksStore = useTracksStore(); + const handleAddPress = () => { newDraft(); navigate('PresetChooser'); @@ -105,6 +118,23 @@ export const MapScreen = () => { minDisplacement={MIN_DISPLACEMENT} /> )} + {tracksStore.locationHistory.length > 1 && ( + <> + + + + + )} [ + location.coords.longitude, + location.coords.latitude, + ]), + }; +} From 8773faf70d169ebe047ca3874174a5be08eea3b3 Mon Sep 17 00:00:00 2001 From: bohdanprog Date: Fri, 12 Apr 2024 08:39:42 +0200 Subject: [PATCH 011/123] add check if permission granted, add function to request about permission --- .../screens/MapScreen/gps/GPSDisabled.tsx | 4 +++- src/frontend/screens/MapScreen/gps/GPSModal.tsx | 17 +++++++++++------ src/frontend/screens/MapScreen/index.tsx | 2 +- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/frontend/screens/MapScreen/gps/GPSDisabled.tsx b/src/frontend/screens/MapScreen/gps/GPSDisabled.tsx index 72b292396..08fc9aa0d 100644 --- a/src/frontend/screens/MapScreen/gps/GPSDisabled.tsx +++ b/src/frontend/screens/MapScreen/gps/GPSDisabled.tsx @@ -2,8 +2,10 @@ import * as React from 'react'; import {View, Image} from 'react-native'; import {Button} from '../../../sharedComponents/Button'; import {Text} from '../../../sharedComponents/Text'; +import * as Location from 'expo-location'; export const GPSDisabled = () => { + const [status, requestPermission] = Location.useBackgroundPermissions(); return ( { diff --git a/src/frontend/screens/MapScreen/gps/GPSModal.tsx b/src/frontend/screens/MapScreen/gps/GPSModal.tsx index 46551484e..25065c391 100644 --- a/src/frontend/screens/MapScreen/gps/GPSModal.tsx +++ b/src/frontend/screens/MapScreen/gps/GPSModal.tsx @@ -1,20 +1,25 @@ -import React, {FC} from 'react'; +import React from 'react'; import {View, StyleSheet} from 'react-native'; import {GPSDisabled} from './GPSDisabled'; import {GPSEnabled} from './GPSEnabled'; import {useGPSModalContext} from '../../../contexts/GPSModalContext'; +import {useForegroundPermissions} from 'expo-location'; -interface GPSModal { - locationServicesEnabled: boolean; -} -export const GPSModal: FC = ({locationServicesEnabled}) => { +export const GPSModal = () => { const {displayModal} = useGPSModalContext(); + const [permissions] = useForegroundPermissions(); + + console.log(permissions, 'permissions'); return ( <> {displayModal && ( - {locationServicesEnabled ? : } + {permissions && !!permissions.granted ? ( + + ) : ( + + )} )} diff --git a/src/frontend/screens/MapScreen/index.tsx b/src/frontend/screens/MapScreen/index.tsx index 0035eedf4..a083fb88c 100644 --- a/src/frontend/screens/MapScreen/index.tsx +++ b/src/frontend/screens/MapScreen/index.tsx @@ -155,7 +155,7 @@ export const MapScreen = () => { isLoading={!isFinishedLoading} /> - + ); }; From dfa17913e8bcdb04789aeecec081558830ae3172 Mon Sep 17 00:00:00 2001 From: bohdanprog Date: Fri, 12 Apr 2024 12:20:21 +0200 Subject: [PATCH 012/123] add animation, add function for check permission disable strart tracking button --- src/frontend/hooks/tracks/useTracking.ts | 6 +- .../screens/MapScreen/gps/GPSDisabled.tsx | 80 ++++++++++++++----- .../screens/MapScreen/gps/GPSEnabled.tsx | 45 +++++++---- .../screens/MapScreen/gps/GPSModal.tsx | 27 +++---- src/frontend/screens/MapScreen/index.tsx | 4 +- 5 files changed, 110 insertions(+), 52 deletions(-) diff --git a/src/frontend/hooks/tracks/useTracking.ts b/src/frontend/hooks/tracks/useTracking.ts index b8b7aa2ca..151d388d1 100644 --- a/src/frontend/hooks/tracks/useTracking.ts +++ b/src/frontend/hooks/tracks/useTracking.ts @@ -10,6 +10,7 @@ type LocationCallbackInfo = { error: TaskManager.TaskManagerError | null; }; export function useTracking() { + const [loading, setLoading] = useState(false); const tracksStore = useTracksStore(); const isTracking = useTracksStore(state => state.isTracking); const addNewTrackLocations = useCallback( @@ -25,10 +26,12 @@ export function useTracking() { ); const startTracking = useCallback(async () => { + setLoading(true); TaskManager.defineTask(LOCATION_TASK_NAME, addNewTrackLocations); if (isTracking) { console.warn('Start tracking attempt while tracking already enabled'); + setLoading(false); return; } const requestForeground = Location.requestForegroundPermissionsAsync; @@ -45,11 +48,12 @@ export function useTracking() { tracksStore.setTracking(true); } } + setLoading(false); }, []); const cancelTracking = useCallback(async () => { await TaskManager.unregisterTaskAsync(LOCATION_TASK_NAME); tracksStore.setTracking(false); }, []); - return {isTracking, startTracking, cancelTracking}; + return {isTracking, startTracking, cancelTracking, loading}; } diff --git a/src/frontend/screens/MapScreen/gps/GPSDisabled.tsx b/src/frontend/screens/MapScreen/gps/GPSDisabled.tsx index 08fc9aa0d..91214ca2b 100644 --- a/src/frontend/screens/MapScreen/gps/GPSDisabled.tsx +++ b/src/frontend/screens/MapScreen/gps/GPSDisabled.tsx @@ -1,39 +1,79 @@ import * as React from 'react'; -import {View, Image} from 'react-native'; +import {Image, Linking, StyleSheet} from 'react-native'; import {Button} from '../../../sharedComponents/Button'; import {Text} from '../../../sharedComponents/Text'; import * as Location from 'expo-location'; +import Animated, { + Easing, + FadeInDown, + FadeOutDown, +} from 'react-native-reanimated'; + +const handleOpenSettings = () => { + Linking.sendIntent('android.settings.LOCATION_SOURCE_SETTINGS'); +}; + +interface GPSDisabled { + setIsGranted: React.Dispatch>; +} +export const GPSDisabled: React.FC = ({setIsGranted}) => { + const requestForLocationPermissions = async () => { + const [foregroundPermission, backgroundPermission] = await Promise.all([ + Location.requestForegroundPermissionsAsync(), + Location.requestBackgroundPermissionsAsync(), + ]); + if (foregroundPermission.granted && backgroundPermission.granted) { + setIsGranted(true); + } else if ( + !foregroundPermission.canAskAgain || + !backgroundPermission.canAskAgain + ) { + handleOpenSettings(); + } + }; -export const GPSDisabled = () => { - const [status, requestPermission] = Location.useBackgroundPermissions(); return ( - + - - GPS Disabled - - + GPS Disabled + To create a Track CoMapeo needs access to your location and GPS. - + ); }; + +const styles = StyleSheet.create({ + wrapper: { + padding: 30, + zIndex: 11, + alignItems: 'center', + display: 'flex', + justifyContent: 'center', + }, + image: {marginBottom: 30}, + title: {fontSize: 24, fontWeight: 'bold', textAlign: 'center'}, + description: {fontSize: 20, textAlign: 'center', marginBottom: 30}, + button: {marginBottom: 20, marginVertical: 8.5}, + buttonText: {fontWeight: '500', color: '#fff'}, +}); diff --git a/src/frontend/screens/MapScreen/gps/GPSEnabled.tsx b/src/frontend/screens/MapScreen/gps/GPSEnabled.tsx index 620cbbfb2..a20a48552 100644 --- a/src/frontend/screens/MapScreen/gps/GPSEnabled.tsx +++ b/src/frontend/screens/MapScreen/gps/GPSEnabled.tsx @@ -1,25 +1,42 @@ import * as React from 'react'; -import {View} from 'react-native'; +import {StyleSheet, View} from 'react-native'; import {Button} from '../../../sharedComponents/Button'; import {Text} from '../../../sharedComponents/Text'; import {useTracking} from '../../../hooks/tracks/useTracking'; -import {useTracksStore} from '../../../hooks/tracks/useTracksStore'; +import Animated, { + Easing, + FadeInDown, + FadeOutDown, +} from 'react-native-reanimated'; export const GPSEnabled = () => { - const tracking = useTracking(); + const {isTracking, cancelTracking, startTracking, loading} = useTracking(); + + const handleTracking = () => { + isTracking ? cancelTracking() : startTracking(); + }; + + const getButtonTitle = () => { + if (loading) return 'Loading...'; + if (isTracking) return 'Stop Tracks'; + return 'Start Tracks'; + }; + return ( - - - + ); }; + +const styles = StyleSheet.create({ + wrapper: {paddingHorizontal: 20, paddingVertical: 30}, + buttonText: {fontWeight: '500', color: '#fff'}, +}); diff --git a/src/frontend/screens/MapScreen/gps/GPSModal.tsx b/src/frontend/screens/MapScreen/gps/GPSModal.tsx index 25065c391..08af061a2 100644 --- a/src/frontend/screens/MapScreen/gps/GPSModal.tsx +++ b/src/frontend/screens/MapScreen/gps/GPSModal.tsx @@ -1,28 +1,23 @@ -import React from 'react'; -import {View, StyleSheet} from 'react-native'; +import React, {useEffect, useState} from 'react'; +import {StyleSheet, View} from 'react-native'; import {GPSDisabled} from './GPSDisabled'; import {GPSEnabled} from './GPSEnabled'; -import {useGPSModalContext} from '../../../contexts/GPSModalContext'; import {useForegroundPermissions} from 'expo-location'; export const GPSModal = () => { - const {displayModal} = useGPSModalContext(); const [permissions] = useForegroundPermissions(); + const [isGranted, setIsGranted] = useState(null); - console.log(permissions, 'permissions'); + useEffect(() => { + if (permissions && isGranted === null) { + setIsGranted(permissions!.granted); + } + }, [permissions]); return ( - <> - {displayModal && ( - - {permissions && !!permissions.granted ? ( - - ) : ( - - )} - - )} - + + {isGranted ? : } + ); }; diff --git a/src/frontend/screens/MapScreen/index.tsx b/src/frontend/screens/MapScreen/index.tsx index a083fb88c..4fd35a24b 100644 --- a/src/frontend/screens/MapScreen/index.tsx +++ b/src/frontend/screens/MapScreen/index.tsx @@ -29,6 +29,7 @@ import { FullLocationData, useTracksStore, } from '../../hooks/tracks/useTracksStore'; +import {useGPSModalContext} from '../../contexts/GPSModalContext'; // This is the default zoom used when the map first loads, and also the zoom // that the map will zoom to if the user clicks the "Locate" button and the @@ -41,6 +42,7 @@ const MIN_DISPLACEMENT = 15; export const MAP_STYLE = Mapbox.StyleURL.Outdoors; export const MapScreen = () => { + const {displayModal} = useGPSModalContext(); const [zoom, setZoom] = React.useState(DEFAULT_ZOOM); const isFocused = useIsFullyFocused(); @@ -155,7 +157,7 @@ export const MapScreen = () => { isLoading={!isFinishedLoading} /> - + {displayModal && } ); }; From dee33fdb49b9168f02f56703e36b05d8c717eeec Mon Sep 17 00:00:00 2001 From: bohdanprog Date: Fri, 12 Apr 2024 13:58:26 +0200 Subject: [PATCH 013/123] fixed problem with custom modal --- .../Navigation/ScreenGroups/AppScreens.tsx | 6 +-- src/frontend/contexts/GPSModalContext.tsx | 17 +++---- .../screens/MapScreen/gps/GPSDisabled.tsx | 19 ++------ .../screens/MapScreen/gps/GPSEnabled.tsx | 12 +---- .../screens/MapScreen/gps/GPSModal.tsx | 44 +++++++++++-------- src/frontend/screens/MapScreen/index.tsx | 5 +-- 6 files changed, 41 insertions(+), 62 deletions(-) diff --git a/src/frontend/Navigation/ScreenGroups/AppScreens.tsx b/src/frontend/Navigation/ScreenGroups/AppScreens.tsx index 04dcd5552..a4812e4c2 100644 --- a/src/frontend/Navigation/ScreenGroups/AppScreens.tsx +++ b/src/frontend/Navigation/ScreenGroups/AppScreens.tsx @@ -141,7 +141,7 @@ const HomeTabs = () => { const {setCurrentTab, currentTab} = useNavigationStore(); const navigation = useNavigation(); const route = useRoute(); - const {setDisplayModal} = useGPSModalContext(); + const {bottomSheetRef} = useGPSModalContext(); const handleTabPress = ({ target, @@ -150,9 +150,9 @@ const HomeTabs = () => { const targetTab = target?.split('-')[0]; if (targetTab === 'Tracking') { preventDefault(); - setDisplayModal(true); + bottomSheetRef.current?.present(); } else { - setDisplayModal(false); + bottomSheetRef.current?.close(); } const currentTab = getFocusedRouteNameFromRoute(route); if (currentTab === 'Camera') { diff --git a/src/frontend/contexts/GPSModalContext.tsx b/src/frontend/contexts/GPSModalContext.tsx index 892b0433e..7cdc6dfad 100644 --- a/src/frontend/contexts/GPSModalContext.tsx +++ b/src/frontend/contexts/GPSModalContext.tsx @@ -1,23 +1,18 @@ -import React, { - createContext, - Dispatch, - SetStateAction, - useContext, - useState, -} from 'react'; +import {BottomSheetModal} from '@gorhom/bottom-sheet'; +import {BottomSheetModalMethods} from '@gorhom/bottom-sheet/lib/typescript/types'; +import React, {createContext, useContext, useRef} from 'react'; interface GPSModalContext { - displayModal: boolean; - setDisplayModal: Dispatch>; + bottomSheetRef: React.RefObject; } const GPSModalContext = createContext(null); const GPSModalContextProvider = ({children}: {children: React.ReactNode}) => { - const [displayModal, setDisplayModal] = useState(false); + const bottomSheetRef = useRef(null); return ( - + {children} ); diff --git a/src/frontend/screens/MapScreen/gps/GPSDisabled.tsx b/src/frontend/screens/MapScreen/gps/GPSDisabled.tsx index 91214ca2b..848b1d1ac 100644 --- a/src/frontend/screens/MapScreen/gps/GPSDisabled.tsx +++ b/src/frontend/screens/MapScreen/gps/GPSDisabled.tsx @@ -1,13 +1,8 @@ import * as React from 'react'; -import {Image, Linking, StyleSheet} from 'react-native'; +import {Image, Linking, StyleSheet, View} from 'react-native'; import {Button} from '../../../sharedComponents/Button'; import {Text} from '../../../sharedComponents/Text'; import * as Location from 'expo-location'; -import Animated, { - Easing, - FadeInDown, - FadeOutDown, -} from 'react-native-reanimated'; const handleOpenSettings = () => { Linking.sendIntent('android.settings.LOCATION_SOURCE_SETTINGS'); @@ -33,15 +28,7 @@ export const GPSDisabled: React.FC = ({setIsGranted}) => { }; return ( - + = ({setIsGranted}) => { style={styles.button}> Enable - + ); }; diff --git a/src/frontend/screens/MapScreen/gps/GPSEnabled.tsx b/src/frontend/screens/MapScreen/gps/GPSEnabled.tsx index a20a48552..9d05de054 100644 --- a/src/frontend/screens/MapScreen/gps/GPSEnabled.tsx +++ b/src/frontend/screens/MapScreen/gps/GPSEnabled.tsx @@ -3,11 +3,6 @@ import {StyleSheet, View} from 'react-native'; import {Button} from '../../../sharedComponents/Button'; import {Text} from '../../../sharedComponents/Text'; import {useTracking} from '../../../hooks/tracks/useTracking'; -import Animated, { - Easing, - FadeInDown, - FadeOutDown, -} from 'react-native-reanimated'; export const GPSEnabled = () => { const {isTracking, cancelTracking, startTracking, loading} = useTracking(); @@ -23,16 +18,13 @@ export const GPSEnabled = () => { }; return ( - + - + ); }; diff --git a/src/frontend/screens/MapScreen/gps/GPSModal.tsx b/src/frontend/screens/MapScreen/gps/GPSModal.tsx index 08af061a2..09f7ebaa6 100644 --- a/src/frontend/screens/MapScreen/gps/GPSModal.tsx +++ b/src/frontend/screens/MapScreen/gps/GPSModal.tsx @@ -1,34 +1,42 @@ -import React, {useEffect, useState} from 'react'; -import {StyleSheet, View} from 'react-native'; +import React, {useEffect, useRef, useState} from 'react'; import {GPSDisabled} from './GPSDisabled'; import {GPSEnabled} from './GPSEnabled'; import {useForegroundPermissions} from 'expo-location'; +import { + BottomSheetBackdrop, + BottomSheetModal, + BottomSheetView, +} from '@gorhom/bottom-sheet'; +import {useGPSModalContext} from '../../../contexts/GPSModalContext'; export const GPSModal = () => { const [permissions] = useForegroundPermissions(); const [isGranted, setIsGranted] = useState(null); + const {bottomSheetRef} = useGPSModalContext(); useEffect(() => { if (permissions && isGranted === null) { setIsGranted(permissions!.granted); } }, [permissions]); - + const onBottomSheetDismiss = () => bottomSheetRef.current?.close(); return ( - - {isGranted ? : } - + null} + backdropComponent={BottomSheetBackdrop}> + + {isGranted ? ( + + ) : ( + + )} + + ); }; - -const styles = StyleSheet.create({ - wrapper: { - position: 'absolute', - bottom: 0, - left: 0, - width: '100%', - borderTopLeftRadius: 10, - borderTopRightRadius: 10, - backgroundColor: '#fff', - }, -}); diff --git a/src/frontend/screens/MapScreen/index.tsx b/src/frontend/screens/MapScreen/index.tsx index 4fd35a24b..8163af0c6 100644 --- a/src/frontend/screens/MapScreen/index.tsx +++ b/src/frontend/screens/MapScreen/index.tsx @@ -29,7 +29,6 @@ import { FullLocationData, useTracksStore, } from '../../hooks/tracks/useTracksStore'; -import {useGPSModalContext} from '../../contexts/GPSModalContext'; // This is the default zoom used when the map first loads, and also the zoom // that the map will zoom to if the user clicks the "Locate" button and the @@ -42,7 +41,6 @@ const MIN_DISPLACEMENT = 15; export const MAP_STYLE = Mapbox.StyleURL.Outdoors; export const MapScreen = () => { - const {displayModal} = useGPSModalContext(); const [zoom, setZoom] = React.useState(DEFAULT_ZOOM); const isFocused = useIsFullyFocused(); @@ -156,8 +154,7 @@ export const MapScreen = () => { onPress={handleAddPress} isLoading={!isFinishedLoading} /> - - {displayModal && } + ); }; From 5e338f704dac4af0b60a2dcf10fcaaa3469798a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20Stefa=C5=84czyk?= Date: Fri, 12 Apr 2024 14:03:13 +0200 Subject: [PATCH 014/123] fix hooks dependencies, user location flick --- src/frontend/hooks/tracks/useTracking.ts | 28 ++++++++----------- src/frontend/hooks/tracks/useTracksStore.ts | 2 +- .../screens/MapScreen/gps/GPSEnabled.tsx | 2 +- src/frontend/screens/MapScreen/index.tsx | 8 ++---- 4 files changed, 16 insertions(+), 24 deletions(-) diff --git a/src/frontend/hooks/tracks/useTracking.ts b/src/frontend/hooks/tracks/useTracking.ts index 151d388d1..1a5604037 100644 --- a/src/frontend/hooks/tracks/useTracking.ts +++ b/src/frontend/hooks/tracks/useTracking.ts @@ -22,7 +22,7 @@ export function useTracking() { tracksStore.addNewLocations(data.locations); } }, - [], + [tracksStore], ); const startTracking = useCallback(async () => { @@ -34,26 +34,20 @@ export function useTracking() { setLoading(false); return; } - const requestForeground = Location.requestForegroundPermissionsAsync; - const requestBackground = Location.requestBackgroundPermissionsAsync; - - const foregroundRequest = await requestForeground(); - if (foregroundRequest.granted) { - const backgroundRequest = await requestBackground(); - if (backgroundRequest.granted) { - await Location.startLocationUpdatesAsync(LOCATION_TASK_NAME, { - accuracy: Location.Accuracy.Highest, - activityType: Location.LocationActivityType.Fitness, - }); - tracksStore.setTracking(true); - } - } + + await Location.startLocationUpdatesAsync(LOCATION_TASK_NAME, { + accuracy: Location.Accuracy.Highest, + activityType: Location.LocationActivityType.Fitness, + }); + + tracksStore.setTracking(true); setLoading(false); - }, []); + }, [addNewTrackLocations, tracksStore, isTracking]); const cancelTracking = useCallback(async () => { await TaskManager.unregisterTaskAsync(LOCATION_TASK_NAME); tracksStore.setTracking(false); - }, []); + }, [tracksStore]); + return {isTracking, startTracking, cancelTracking, loading}; } diff --git a/src/frontend/hooks/tracks/useTracksStore.ts b/src/frontend/hooks/tracks/useTracksStore.ts index 4bb2ae777..51d580e1e 100644 --- a/src/frontend/hooks/tracks/useTracksStore.ts +++ b/src/frontend/hooks/tracks/useTracksStore.ts @@ -35,5 +35,5 @@ export const useTracksStore = create(set => ({ addNewLocations: data => set(state => ({locationHistory: [...state.locationHistory, ...data]})), clearLocationHistory: () => set(() => ({locationHistory: []})), - setTracking: (val: boolean) => set(state => ({isTracking: val})), + setTracking: (val: boolean) => set(() => ({isTracking: val})), })); diff --git a/src/frontend/screens/MapScreen/gps/GPSEnabled.tsx b/src/frontend/screens/MapScreen/gps/GPSEnabled.tsx index 9d05de054..c5932bb9b 100644 --- a/src/frontend/screens/MapScreen/gps/GPSEnabled.tsx +++ b/src/frontend/screens/MapScreen/gps/GPSEnabled.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import {StyleSheet, View} from 'react-native'; +import {StyleSheet} from 'react-native'; import {Button} from '../../../sharedComponents/Button'; import {Text} from '../../../sharedComponents/Text'; import {useTracking} from '../../../hooks/tracks/useTracking'; diff --git a/src/frontend/screens/MapScreen/index.tsx b/src/frontend/screens/MapScreen/index.tsx index 8163af0c6..427553ab1 100644 --- a/src/frontend/screens/MapScreen/index.tsx +++ b/src/frontend/screens/MapScreen/index.tsx @@ -55,7 +55,7 @@ export const MapScreen = () => { const locationServicesEnabled = !!locationProviderStatus?.locationServicesEnabled; - const tracksStore = useTracksStore(); + const locationHistory = useTracksStore(state => state.locationHistory); const handleAddPress = () => { newDraft(); @@ -118,11 +118,9 @@ export const MapScreen = () => { minDisplacement={MIN_DISPLACEMENT} /> )} - {tracksStore.locationHistory.length > 1 && ( + {locationHistory.length > 1 && ( <> - + Date: Fri, 12 Apr 2024 15:14:56 +0200 Subject: [PATCH 015/123] cleanup --- .../screens/MapScreen/gps/GPSEnabled.tsx | 2 +- src/frontend/screens/TrackingScreen.tsx | 33 ------------------- 2 files changed, 1 insertion(+), 34 deletions(-) delete mode 100644 src/frontend/screens/TrackingScreen.tsx diff --git a/src/frontend/screens/MapScreen/gps/GPSEnabled.tsx b/src/frontend/screens/MapScreen/gps/GPSEnabled.tsx index c5932bb9b..9d05de054 100644 --- a/src/frontend/screens/MapScreen/gps/GPSEnabled.tsx +++ b/src/frontend/screens/MapScreen/gps/GPSEnabled.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import {StyleSheet} from 'react-native'; +import {StyleSheet, View} from 'react-native'; import {Button} from '../../../sharedComponents/Button'; import {Text} from '../../../sharedComponents/Text'; import {useTracking} from '../../../hooks/tracks/useTracking'; diff --git a/src/frontend/screens/TrackingScreen.tsx b/src/frontend/screens/TrackingScreen.tsx deleted file mode 100644 index b2f67c0e7..000000000 --- a/src/frontend/screens/TrackingScreen.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import * as React from 'react'; -import {View, StyleSheet} from 'react-native'; -import {useIsFocused} from '@react-navigation/native'; - -import {CameraView} from '../sharedComponents/CameraView'; -import {NativeHomeTabsNavigationProps} from '../sharedTypes'; -import {CapturedPictureMM} from '../contexts/PhotoPromiseContext/types'; -import {useDraftObservation} from '../hooks/useDraftObservation'; - -export const TrackingScreen = ({ - navigation, -}: NativeHomeTabsNavigationProps<'Tracking'>) => { - const isFocused = useIsFocused(); - const {newDraft} = useDraftObservation(); - - function handleAddPress(photoPromise: Promise) { - newDraft(photoPromise); - navigation.navigate('PresetChooser'); - } - - return ( - - {isFocused ? : null} - - ); -}; - -const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: 'black', - }, -}); From 5cfeb7559015ddaacbec648f8e9e04bbfc5c2ac2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20Stefa=C5=84czyk?= Date: Fri, 12 Apr 2024 16:45:17 +0200 Subject: [PATCH 016/123] place observations exactly at track line --- .../screens/MapScreen/ObsevationMapLayer.tsx | 6 +++++- src/frontend/screens/MapScreen/index.tsx | 4 ++-- .../screens/ObservationEdit/SaveButton.tsx | 20 ++++++++++++++++++- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/frontend/screens/MapScreen/ObsevationMapLayer.tsx b/src/frontend/screens/MapScreen/ObsevationMapLayer.tsx index d287706b5..f1e795e53 100644 --- a/src/frontend/screens/MapScreen/ObsevationMapLayer.tsx +++ b/src/frontend/screens/MapScreen/ObsevationMapLayer.tsx @@ -36,7 +36,11 @@ export const ObservationMapLayer = () => { onPress={handlePressEvent} id="observations-source" shape={featureCollection}> - + ); }; diff --git a/src/frontend/screens/MapScreen/index.tsx b/src/frontend/screens/MapScreen/index.tsx index 427553ab1..c76deaa18 100644 --- a/src/frontend/screens/MapScreen/index.tsx +++ b/src/frontend/screens/MapScreen/index.tsx @@ -111,7 +111,6 @@ export const MapScreen = () => { followUserLocation={false} /> - {isFinishedLoading && } {coords !== undefined && locationServicesEnabled && ( { { )} + {isFinishedLoading && } state.addNewLocations); function createObservation() { if (!value) throw new Error('no observation saved in persisted state '); @@ -151,6 +152,23 @@ export const SaveButton = ({ onSuccess: () => { clearDraft(); navigation.pop(); + if (value.lat && value.lon) { + addNewTrackLocation([ + { + timestamp: new Date().getTime(), + coords: { + accuracy: 0, + altitude: 0, + latitude: value.lat || 0, + longitude: value.lon || 0, + altitudeAccuracy: 0, + heading: 0, + speed: 0, + }, + }, + ]); + console.log(observationId); + } }, }, ); From 8d398c5eab2276b79408eb0466fb24dbd8275ddd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20Stefa=C5=84czyk?= Date: Fri, 12 Apr 2024 17:09:36 +0200 Subject: [PATCH 017/123] store observations found on track --- src/frontend/hooks/tracks/useTracksStore.ts | 6 ++++- .../screens/ObservationEdit/SaveButton.tsx | 27 +++++++++++++++++-- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/frontend/hooks/tracks/useTracksStore.ts b/src/frontend/hooks/tracks/useTracksStore.ts index 51d580e1e..1f7e9b5c1 100644 --- a/src/frontend/hooks/tracks/useTracksStore.ts +++ b/src/frontend/hooks/tracks/useTracksStore.ts @@ -23,6 +23,8 @@ export type FullLocationData = { type TracksStoreState = { isTracking: boolean; locationHistory: FullLocationData[]; + observations: string[]; + addNewObservation: (observationId: string) => void; addNewLocations: (locationData: FullLocationData[]) => void; clearLocationHistory: () => void; setTracking: (val: boolean) => void; @@ -31,7 +33,9 @@ type TracksStoreState = { export const useTracksStore = create(set => ({ isTracking: false, locationHistory: [], - dupa: [], + observations: [], + addNewObservation: (id: string) => + set(state => ({observations: [...state.observations, id]})), addNewLocations: data => set(state => ({locationHistory: [...state.locationHistory, ...data]})), clearLocationHistory: () => set(() => ({locationHistory: []})), diff --git a/src/frontend/screens/ObservationEdit/SaveButton.tsx b/src/frontend/screens/ObservationEdit/SaveButton.tsx index acd7df0f8..a63e10c7b 100644 --- a/src/frontend/screens/ObservationEdit/SaveButton.tsx +++ b/src/frontend/screens/ObservationEdit/SaveButton.tsx @@ -75,6 +75,10 @@ export const SaveButton = ({ const editObservationMutation = useEditObservation(); const createBlobMutation = useCreateBlobMutation(); const addNewTrackLocation = useTracksStore(state => state.addNewLocations); + const addNewTrackObservation = useTracksStore( + state => state.addNewObservation, + ); + function createObservation() { if (!value) throw new Error('no observation saved in persisted state '); @@ -128,9 +132,28 @@ export const SaveButton = ({ onError: () => { if (openErrorModal) openErrorModal(); }, - onSuccess: () => { + onSuccess: data => { clearDraft(); navigation.navigate('Home', {screen: 'Map'}); + if (value.lat && value.lon) { + addNewTrackLocation([ + { + timestamp: new Date().getTime(), + coords: { + accuracy: 0, + altitude: 0, + latitude: value.lat || 0, + longitude: value.lon || 0, + altitudeAccuracy: 0, + heading: 0, + speed: 0, + }, + }, + ]); + } + if (data.docId) { + addNewTrackObservation(data.docId); + } }, }, ); @@ -167,8 +190,8 @@ export const SaveButton = ({ }, }, ]); - console.log(observationId); } + addNewTrackObservation(observationId); }, }, ); From 82b5c5c0881a28d670cc2000e9a980d39b03f126 Mon Sep 17 00:00:00 2001 From: bohdanprog Date: Mon, 15 Apr 2024 12:22:07 +0200 Subject: [PATCH 018/123] add check if foreground permission is granted, add gps indicator --- android/app/src/main/AndroidManifest.xml | 4 +- .../Navigation/ScreenGroups/AppScreens.tsx | 2 +- src/frontend/images/ActiveGPSSignal.svg | 9 ++ src/frontend/images/NoGPSSignal.svg | 3 + .../screens/MapScreen/gps/GPSIndicator.tsx | 41 +++++++++ .../screens/MapScreen/gps/GPSModal.tsx | 83 ++++++++++++------- src/frontend/screens/MapScreen/index.tsx | 4 +- 7 files changed, 114 insertions(+), 32 deletions(-) create mode 100644 src/frontend/images/ActiveGPSSignal.svg create mode 100644 src/frontend/images/NoGPSSignal.svg create mode 100644 src/frontend/screens/MapScreen/gps/GPSIndicator.tsx diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 9816b7164..67ddcd2aa 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -4,7 +4,9 @@ - + + + { } const currentTab = getFocusedRouteNameFromRoute(route); if (currentTab === 'Camera') { - navigation.navigate('Map'); + navigation.navigate('Map' as never); } setCurrentTab((target?.split('-')[0] || 'Map') as unknown as TabName); }; diff --git a/src/frontend/images/ActiveGPSSignal.svg b/src/frontend/images/ActiveGPSSignal.svg new file mode 100644 index 000000000..940d69e45 --- /dev/null +++ b/src/frontend/images/ActiveGPSSignal.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/frontend/images/NoGPSSignal.svg b/src/frontend/images/NoGPSSignal.svg new file mode 100644 index 000000000..13bbf016e --- /dev/null +++ b/src/frontend/images/NoGPSSignal.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/frontend/screens/MapScreen/gps/GPSIndicator.tsx b/src/frontend/screens/MapScreen/gps/GPSIndicator.tsx new file mode 100644 index 000000000..0db0cf3ef --- /dev/null +++ b/src/frontend/screens/MapScreen/gps/GPSIndicator.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import {StyleSheet, View} from 'react-native'; +import {Text} from '../../../sharedComponents/Text'; +import NoGPSSignalImage from '../../../images/NoGPSSignal.svg'; +import ActiveGPSSignalImage from '../../../images/ActiveGPSSignal.svg'; +import * as Location from 'expo-location'; + +export const GPSIndicator = () => { + const [backgroundStatus] = Location.useBackgroundPermissions(); + const [foregroundStatus] = Location.useForegroundPermissions(); + + return ( + + + {backgroundStatus?.granted && foregroundStatus?.granted ? ( + <> + + GPS + + ) : ( + <> + + No GPS + + )} + + + ); +}; + +const styles = StyleSheet.create({ + indicatorWrapper: { + backgroundColor: '#333333', + borderRadius: 20, + position: 'absolute', + padding: 14.5, + top: 20, + }, + wrapper: {flexDirection: 'row', alignItems: 'center'}, + text: {marginLeft: 5, color: '#fff', fontSize: 15}, +}); diff --git a/src/frontend/screens/MapScreen/gps/GPSModal.tsx b/src/frontend/screens/MapScreen/gps/GPSModal.tsx index 09f7ebaa6..00b9dec49 100644 --- a/src/frontend/screens/MapScreen/gps/GPSModal.tsx +++ b/src/frontend/screens/MapScreen/gps/GPSModal.tsx @@ -1,42 +1,67 @@ -import React, {useEffect, useRef, useState} from 'react'; +import React, {useEffect, useState} from 'react'; import {GPSDisabled} from './GPSDisabled'; import {GPSEnabled} from './GPSEnabled'; -import {useForegroundPermissions} from 'expo-location'; -import { - BottomSheetBackdrop, - BottomSheetModal, - BottomSheetView, -} from '@gorhom/bottom-sheet'; +import * as Location from 'expo-location'; +import {BottomSheetModal, BottomSheetView} from '@gorhom/bottom-sheet'; import {useGPSModalContext} from '../../../contexts/GPSModalContext'; +import {useNavigationStore} from '../../../hooks/useNavigationStore'; +import {TouchableWithoutFeedback, View, StyleSheet} from 'react-native'; export const GPSModal = () => { - const [permissions] = useForegroundPermissions(); + const {setCurrentTab} = useNavigationStore(); + const [backgroundStatus] = Location.useBackgroundPermissions(); + const [foregroundStatus] = Location.useForegroundPermissions(); + + const [currentIndex, setCurrentIndex] = useState(-1); const [isGranted, setIsGranted] = useState(null); const {bottomSheetRef} = useGPSModalContext(); useEffect(() => { - if (permissions && isGranted === null) { - setIsGranted(permissions!.granted); + if (backgroundStatus && foregroundStatus && isGranted === null) { + setIsGranted(backgroundStatus.granted && foregroundStatus.granted); } - }, [permissions]); - const onBottomSheetDismiss = () => bottomSheetRef.current?.close(); + }, [backgroundStatus, foregroundStatus]); + + const onBottomSheetDismiss = () => { + setCurrentTab('Map'); + bottomSheetRef.current?.close(); + }; + return ( - null} - backdropComponent={BottomSheetBackdrop}> - - {isGranted ? ( - - ) : ( - - )} - - + <> + + + + null}> + + {isGranted ? ( + + ) : ( + + )} + + + ); }; + +const styles = StyleSheet.create({ + wrapper: { + position: 'absolute', + height: '100%', + width: '100%', + backgroundColor: 'transparent', + }, +}); diff --git a/src/frontend/screens/MapScreen/index.tsx b/src/frontend/screens/MapScreen/index.tsx index 8163af0c6..544f24ab4 100644 --- a/src/frontend/screens/MapScreen/index.tsx +++ b/src/frontend/screens/MapScreen/index.tsx @@ -29,6 +29,7 @@ import { FullLocationData, useTracksStore, } from '../../hooks/tracks/useTracksStore'; +import {GPSIndicator} from './gps/GPSIndicator'; // This is the default zoom used when the map first loads, and also the zoom // that the map will zoom to if the user clicks the "Locate" button and the @@ -136,7 +137,7 @@ export const MapScreen = () => { )} - + Date: Mon, 15 Apr 2024 12:52:22 +0200 Subject: [PATCH 019/123] move gps indicator to map header --- src/frontend/screens/MapScreen/gps/GPSIndicator.tsx | 8 +++++--- src/frontend/screens/MapScreen/gps/GPSModal.tsx | 3 ++- src/frontend/screens/MapScreen/index.tsx | 2 -- src/frontend/sharedComponents/HomeHeader.tsx | 12 ++++-------- 4 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/frontend/screens/MapScreen/gps/GPSIndicator.tsx b/src/frontend/screens/MapScreen/gps/GPSIndicator.tsx index 0db0cf3ef..672bf8063 100644 --- a/src/frontend/screens/MapScreen/gps/GPSIndicator.tsx +++ b/src/frontend/screens/MapScreen/gps/GPSIndicator.tsx @@ -4,8 +4,10 @@ import {Text} from '../../../sharedComponents/Text'; import NoGPSSignalImage from '../../../images/NoGPSSignal.svg'; import ActiveGPSSignalImage from '../../../images/ActiveGPSSignal.svg'; import * as Location from 'expo-location'; +import {useLocation} from '../../../hooks/useLocation'; export const GPSIndicator = () => { + const {location} = useLocation({maxDistanceInterval: 15}); const [backgroundStatus] = Location.useBackgroundPermissions(); const [foregroundStatus] = Location.useForegroundPermissions(); @@ -15,7 +17,9 @@ export const GPSIndicator = () => { {backgroundStatus?.granted && foregroundStatus?.granted ? ( <> - GPS + + GPS ± {Math.floor(location?.coords.accuracy || 0)} + ) : ( <> @@ -32,9 +36,7 @@ const styles = StyleSheet.create({ indicatorWrapper: { backgroundColor: '#333333', borderRadius: 20, - position: 'absolute', padding: 14.5, - top: 20, }, wrapper: {flexDirection: 'row', alignItems: 'center'}, text: {marginLeft: 5, color: '#fff', fontSize: 15}, diff --git a/src/frontend/screens/MapScreen/gps/GPSModal.tsx b/src/frontend/screens/MapScreen/gps/GPSModal.tsx index 00b9dec49..514fd9f10 100644 --- a/src/frontend/screens/MapScreen/gps/GPSModal.tsx +++ b/src/frontend/screens/MapScreen/gps/GPSModal.tsx @@ -37,7 +37,7 @@ export const GPSModal = () => { { )} - { const navigation = useNavigationFromHomeTabs(); @@ -16,9 +16,10 @@ export const HomeHeader = () => { colors={['#0006', '#0000']} /> {/* Stand in for styling, */} - "" + + + { navigation.navigate('ObservationList'); }} @@ -36,11 +37,6 @@ const styles = StyleSheet.create({ alignItems: 'center', backgroundColor: 'transparent', }, - rightButton: {}, - leftButton: { - width: 60, - height: 60, - }, linearGradient: { height: 60, position: 'absolute', From 9d125b0afebc7ea7b2f2cb5c89523f3bac9be74b Mon Sep 17 00:00:00 2001 From: bohdanprog Date: Mon, 15 Apr 2024 13:00:11 +0200 Subject: [PATCH 020/123] create custom bottom sheet component with clicalbe backdrop --- .../screens/MapScreen/gps/GPSModal.tsx | 46 +++------------- .../CustomBottomSheetModal.tsx | 53 +++++++++++++++++++ 2 files changed, 61 insertions(+), 38 deletions(-) create mode 100644 src/frontend/sharedComponents/BottomSheetModal/CustomBottomSheetModal.tsx diff --git a/src/frontend/screens/MapScreen/gps/GPSModal.tsx b/src/frontend/screens/MapScreen/gps/GPSModal.tsx index 514fd9f10..9cdb9b1d7 100644 --- a/src/frontend/screens/MapScreen/gps/GPSModal.tsx +++ b/src/frontend/screens/MapScreen/gps/GPSModal.tsx @@ -2,10 +2,9 @@ import React, {useEffect, useState} from 'react'; import {GPSDisabled} from './GPSDisabled'; import {GPSEnabled} from './GPSEnabled'; import * as Location from 'expo-location'; -import {BottomSheetModal, BottomSheetView} from '@gorhom/bottom-sheet'; import {useGPSModalContext} from '../../../contexts/GPSModalContext'; import {useNavigationStore} from '../../../hooks/useNavigationStore'; -import {TouchableWithoutFeedback, View, StyleSheet} from 'react-native'; +import {CustomBottomSheetModal} from '../../../sharedComponents/BottomSheetModal/CustomBottomSheetModal'; export const GPSModal = () => { const {setCurrentTab} = useNavigationStore(); @@ -28,41 +27,12 @@ export const GPSModal = () => { }; return ( - <> - - - - null}> - - {isGranted ? ( - - ) : ( - - )} - - - + + {isGranted ? : } + ); }; - -const styles = StyleSheet.create({ - wrapper: { - position: 'absolute', - height: '100%', - width: '100%', - backgroundColor: 'transparent', - }, - modal: {borderBottomLeftRadius: 0, borderBottomRightRadius: 0}, -}); diff --git a/src/frontend/sharedComponents/BottomSheetModal/CustomBottomSheetModal.tsx b/src/frontend/sharedComponents/BottomSheetModal/CustomBottomSheetModal.tsx new file mode 100644 index 000000000..35f2a53bc --- /dev/null +++ b/src/frontend/sharedComponents/BottomSheetModal/CustomBottomSheetModal.tsx @@ -0,0 +1,53 @@ +import React, {FC} from 'react'; +import {StyleSheet, TouchableWithoutFeedback, View} from 'react-native'; +import {BottomSheetModal, BottomSheetView} from '@gorhom/bottom-sheet'; +import {BottomSheetModalMethods} from '@gorhom/bottom-sheet/lib/typescript/types'; + +interface CustomBottomSheetModal { + dismiss: () => void; + bottomSheetRef: React.RefObject; + currentIndex: number; + setCurrentIndex: React.Dispatch>; + children: React.ReactNode; +} + +export const CustomBottomSheetModal: FC = ({ + dismiss, + bottomSheetRef, + currentIndex, + setCurrentIndex, + children, +}) => { + return ( + <> + + + + null}> + {children} + + + ); +}; + +const styles = StyleSheet.create({ + wrapper: { + position: 'absolute', + height: '100%', + width: '100%', + backgroundColor: 'transparent', + }, + modal: {borderBottomLeftRadius: 0, borderBottomRightRadius: 0}, +}); From f1b7ede96163858820bb9f0cb3323d2ab97d6337 Mon Sep 17 00:00:00 2001 From: bohdanprog Date: Mon, 15 Apr 2024 15:54:46 +0200 Subject: [PATCH 021/123] add icon to start tracking and stop tracking button --- .../Navigation/ScreenGroups/AppScreens.tsx | 3 +- src/frontend/hooks/tracks/useTracking.ts | 6 +-- src/frontend/images/StartTracking.svg | 3 ++ src/frontend/images/StopTracking.svg | 3 ++ .../screens/MapScreen/gps/GPSEnabled.tsx | 40 ++++++++++++++----- 5 files changed, 41 insertions(+), 14 deletions(-) create mode 100644 src/frontend/images/StartTracking.svg create mode 100644 src/frontend/images/StopTracking.svg diff --git a/src/frontend/Navigation/ScreenGroups/AppScreens.tsx b/src/frontend/Navigation/ScreenGroups/AppScreens.tsx index 6bfeeb7c3..2104c19ca 100644 --- a/src/frontend/Navigation/ScreenGroups/AppScreens.tsx +++ b/src/frontend/Navigation/ScreenGroups/AppScreens.tsx @@ -167,7 +167,7 @@ const HomeTabs = () => { tabPress: handleTabPress, }} screenOptions={({route}) => ({ - header: () => , + header: () => <>, headerTransparent: true, tabBarTestID: 'tabBarButton' + route.name, })} @@ -177,6 +177,7 @@ const HomeTabs = () => { name="Map" component={MapScreen} options={{ + header: () => , tabBarIcon: params => ( { @@ -49,11 +49,11 @@ export function useTracking() { } } setLoading(false); - }, []); + }, [addNewTrackLocations, isTracking, tracksStore]); const cancelTracking = useCallback(async () => { await TaskManager.unregisterTaskAsync(LOCATION_TASK_NAME); tracksStore.setTracking(false); - }, []); + }, [tracksStore]); return {isTracking, startTracking, cancelTracking, loading}; } diff --git a/src/frontend/images/StartTracking.svg b/src/frontend/images/StartTracking.svg new file mode 100644 index 000000000..0f52c9c71 --- /dev/null +++ b/src/frontend/images/StartTracking.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/frontend/images/StopTracking.svg b/src/frontend/images/StopTracking.svg new file mode 100644 index 000000000..2e5139a24 --- /dev/null +++ b/src/frontend/images/StopTracking.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/frontend/screens/MapScreen/gps/GPSEnabled.tsx b/src/frontend/screens/MapScreen/gps/GPSEnabled.tsx index 9d05de054..5bbc1d476 100644 --- a/src/frontend/screens/MapScreen/gps/GPSEnabled.tsx +++ b/src/frontend/screens/MapScreen/gps/GPSEnabled.tsx @@ -1,15 +1,17 @@ -import * as React from 'react'; +import React, {useCallback} from 'react'; import {StyleSheet, View} from 'react-native'; import {Button} from '../../../sharedComponents/Button'; import {Text} from '../../../sharedComponents/Text'; import {useTracking} from '../../../hooks/tracks/useTracking'; +import StartTrackingIcon from '../../../images/StartTracking.svg'; +import StopTrackingIcon from '../../../images/StopTracking.svg'; export const GPSEnabled = () => { const {isTracking, cancelTracking, startTracking, loading} = useTracking(); - const handleTracking = () => { + const handleTracking = useCallback(() => { isTracking ? cancelTracking() : startTracking(); - }; + }, [cancelTracking, isTracking, startTracking]); const getButtonTitle = () => { if (loading) return 'Loading...'; @@ -18,17 +20,35 @@ export const GPSEnabled = () => { }; return ( - - ); }; const styles = StyleSheet.create({ - wrapper: {paddingHorizontal: 20, paddingVertical: 30}, - buttonText: {fontWeight: '500', color: '#fff'}, + container: {paddingHorizontal: 20, paddingVertical: 30}, + buttonWrapper: { + flexDirection: 'row', + display: 'flex', + alignItems: 'center', + width: '100%', + }, + buttonText: { + fontWeight: '500', + color: '#fff', + width: '100%', + flex: 1, + textAlign: 'center', + }, }); From 804f42ba01571acc6fbbec7913d8674998941577 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20Stefa=C5=84czyk?= Date: Fri, 12 Apr 2024 14:03:13 +0200 Subject: [PATCH 022/123] fix hooks dependencies, user location flick --- src/frontend/hooks/tracks/useTracking.ts | 22 ++++++++------------- src/frontend/hooks/tracks/useTracksStore.ts | 2 +- src/frontend/screens/MapScreen/index.tsx | 8 +++----- 3 files changed, 12 insertions(+), 20 deletions(-) diff --git a/src/frontend/hooks/tracks/useTracking.ts b/src/frontend/hooks/tracks/useTracking.ts index 256d02572..77074a407 100644 --- a/src/frontend/hooks/tracks/useTracking.ts +++ b/src/frontend/hooks/tracks/useTracking.ts @@ -34,20 +34,13 @@ export function useTracking() { setLoading(false); return; } - const requestForeground = Location.requestForegroundPermissionsAsync; - const requestBackground = Location.requestBackgroundPermissionsAsync; - - const foregroundRequest = await requestForeground(); - if (foregroundRequest.granted) { - const backgroundRequest = await requestBackground(); - if (backgroundRequest.granted) { - await Location.startLocationUpdatesAsync(LOCATION_TASK_NAME, { - accuracy: Location.Accuracy.Highest, - activityType: Location.LocationActivityType.Fitness, - }); - tracksStore.setTracking(true); - } - } + + await Location.startLocationUpdatesAsync(LOCATION_TASK_NAME, { + accuracy: Location.Accuracy.Highest, + activityType: Location.LocationActivityType.Fitness, + }); + + tracksStore.setTracking(true); setLoading(false); }, [addNewTrackLocations, isTracking, tracksStore]); @@ -55,5 +48,6 @@ export function useTracking() { await TaskManager.unregisterTaskAsync(LOCATION_TASK_NAME); tracksStore.setTracking(false); }, [tracksStore]); + return {isTracking, startTracking, cancelTracking, loading}; } diff --git a/src/frontend/hooks/tracks/useTracksStore.ts b/src/frontend/hooks/tracks/useTracksStore.ts index 4bb2ae777..51d580e1e 100644 --- a/src/frontend/hooks/tracks/useTracksStore.ts +++ b/src/frontend/hooks/tracks/useTracksStore.ts @@ -35,5 +35,5 @@ export const useTracksStore = create(set => ({ addNewLocations: data => set(state => ({locationHistory: [...state.locationHistory, ...data]})), clearLocationHistory: () => set(() => ({locationHistory: []})), - setTracking: (val: boolean) => set(state => ({isTracking: val})), + setTracking: (val: boolean) => set(() => ({isTracking: val})), })); diff --git a/src/frontend/screens/MapScreen/index.tsx b/src/frontend/screens/MapScreen/index.tsx index 746f03639..c08a7f016 100644 --- a/src/frontend/screens/MapScreen/index.tsx +++ b/src/frontend/screens/MapScreen/index.tsx @@ -55,7 +55,7 @@ export const MapScreen = () => { const locationServicesEnabled = !!locationProviderStatus?.locationServicesEnabled; - const tracksStore = useTracksStore(); + const locationHistory = useTracksStore(state => state.locationHistory); const handleAddPress = () => { newDraft(); @@ -118,11 +118,9 @@ export const MapScreen = () => { minDisplacement={MIN_DISPLACEMENT} /> )} - {tracksStore.locationHistory.length > 1 && ( + {locationHistory.length > 1 && ( <> - + Date: Fri, 12 Apr 2024 15:14:56 +0200 Subject: [PATCH 023/123] cleanup --- src/frontend/screens/TrackingScreen.tsx | 33 ------------------------- 1 file changed, 33 deletions(-) delete mode 100644 src/frontend/screens/TrackingScreen.tsx diff --git a/src/frontend/screens/TrackingScreen.tsx b/src/frontend/screens/TrackingScreen.tsx deleted file mode 100644 index b2f67c0e7..000000000 --- a/src/frontend/screens/TrackingScreen.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import * as React from 'react'; -import {View, StyleSheet} from 'react-native'; -import {useIsFocused} from '@react-navigation/native'; - -import {CameraView} from '../sharedComponents/CameraView'; -import {NativeHomeTabsNavigationProps} from '../sharedTypes'; -import {CapturedPictureMM} from '../contexts/PhotoPromiseContext/types'; -import {useDraftObservation} from '../hooks/useDraftObservation'; - -export const TrackingScreen = ({ - navigation, -}: NativeHomeTabsNavigationProps<'Tracking'>) => { - const isFocused = useIsFocused(); - const {newDraft} = useDraftObservation(); - - function handleAddPress(photoPromise: Promise) { - newDraft(photoPromise); - navigation.navigate('PresetChooser'); - } - - return ( - - {isFocused ? : null} - - ); -}; - -const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: 'black', - }, -}); From 02bab4bd34aeda31772208154c35edcf4e794a83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20Stefa=C5=84czyk?= Date: Fri, 12 Apr 2024 16:45:17 +0200 Subject: [PATCH 024/123] place observations exactly at track line --- .../screens/MapScreen/ObsevationMapLayer.tsx | 6 +++++- src/frontend/screens/MapScreen/index.tsx | 4 ++-- .../screens/ObservationEdit/SaveButton.tsx | 20 ++++++++++++++++++- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/frontend/screens/MapScreen/ObsevationMapLayer.tsx b/src/frontend/screens/MapScreen/ObsevationMapLayer.tsx index d287706b5..f1e795e53 100644 --- a/src/frontend/screens/MapScreen/ObsevationMapLayer.tsx +++ b/src/frontend/screens/MapScreen/ObsevationMapLayer.tsx @@ -36,7 +36,11 @@ export const ObservationMapLayer = () => { onPress={handlePressEvent} id="observations-source" shape={featureCollection}> - + ); }; diff --git a/src/frontend/screens/MapScreen/index.tsx b/src/frontend/screens/MapScreen/index.tsx index c08a7f016..8fd94059f 100644 --- a/src/frontend/screens/MapScreen/index.tsx +++ b/src/frontend/screens/MapScreen/index.tsx @@ -111,7 +111,6 @@ export const MapScreen = () => { followUserLocation={false} /> - {isFinishedLoading && } {coords !== undefined && locationServicesEnabled && ( { { )} + {isFinishedLoading && } state.addNewLocations); function createObservation() { if (!value) throw new Error('no observation saved in persisted state '); @@ -151,6 +152,23 @@ export const SaveButton = ({ onSuccess: () => { clearDraft(); navigation.pop(); + if (value.lat && value.lon) { + addNewTrackLocation([ + { + timestamp: new Date().getTime(), + coords: { + accuracy: 0, + altitude: 0, + latitude: value.lat || 0, + longitude: value.lon || 0, + altitudeAccuracy: 0, + heading: 0, + speed: 0, + }, + }, + ]); + console.log(observationId); + } }, }, ); From f8b2a3a8d8be73620bd839d9074f206376e233d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20Stefa=C5=84czyk?= Date: Fri, 12 Apr 2024 17:09:36 +0200 Subject: [PATCH 025/123] store observations found on track --- src/frontend/hooks/tracks/useTracksStore.ts | 6 ++++- .../screens/ObservationEdit/SaveButton.tsx | 27 +++++++++++++++++-- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/frontend/hooks/tracks/useTracksStore.ts b/src/frontend/hooks/tracks/useTracksStore.ts index 51d580e1e..1f7e9b5c1 100644 --- a/src/frontend/hooks/tracks/useTracksStore.ts +++ b/src/frontend/hooks/tracks/useTracksStore.ts @@ -23,6 +23,8 @@ export type FullLocationData = { type TracksStoreState = { isTracking: boolean; locationHistory: FullLocationData[]; + observations: string[]; + addNewObservation: (observationId: string) => void; addNewLocations: (locationData: FullLocationData[]) => void; clearLocationHistory: () => void; setTracking: (val: boolean) => void; @@ -31,7 +33,9 @@ type TracksStoreState = { export const useTracksStore = create(set => ({ isTracking: false, locationHistory: [], - dupa: [], + observations: [], + addNewObservation: (id: string) => + set(state => ({observations: [...state.observations, id]})), addNewLocations: data => set(state => ({locationHistory: [...state.locationHistory, ...data]})), clearLocationHistory: () => set(() => ({locationHistory: []})), diff --git a/src/frontend/screens/ObservationEdit/SaveButton.tsx b/src/frontend/screens/ObservationEdit/SaveButton.tsx index acd7df0f8..a63e10c7b 100644 --- a/src/frontend/screens/ObservationEdit/SaveButton.tsx +++ b/src/frontend/screens/ObservationEdit/SaveButton.tsx @@ -75,6 +75,10 @@ export const SaveButton = ({ const editObservationMutation = useEditObservation(); const createBlobMutation = useCreateBlobMutation(); const addNewTrackLocation = useTracksStore(state => state.addNewLocations); + const addNewTrackObservation = useTracksStore( + state => state.addNewObservation, + ); + function createObservation() { if (!value) throw new Error('no observation saved in persisted state '); @@ -128,9 +132,28 @@ export const SaveButton = ({ onError: () => { if (openErrorModal) openErrorModal(); }, - onSuccess: () => { + onSuccess: data => { clearDraft(); navigation.navigate('Home', {screen: 'Map'}); + if (value.lat && value.lon) { + addNewTrackLocation([ + { + timestamp: new Date().getTime(), + coords: { + accuracy: 0, + altitude: 0, + latitude: value.lat || 0, + longitude: value.lon || 0, + altitudeAccuracy: 0, + heading: 0, + speed: 0, + }, + }, + ]); + } + if (data.docId) { + addNewTrackObservation(data.docId); + } }, }, ); @@ -167,8 +190,8 @@ export const SaveButton = ({ }, }, ]); - console.log(observationId); } + addNewTrackObservation(observationId); }, }, ); From b35b1ecf2e35f5972578f70228f7ad1d3b8cffea Mon Sep 17 00:00:00 2001 From: bohdanprog Date: Mon, 15 Apr 2024 12:22:07 +0200 Subject: [PATCH 026/123] add check if foreground permission is granted, add gps indicator --- src/frontend/screens/MapScreen/gps/GPSModal.tsx | 11 ++++++++++- src/frontend/screens/MapScreen/index.tsx | 2 ++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/frontend/screens/MapScreen/gps/GPSModal.tsx b/src/frontend/screens/MapScreen/gps/GPSModal.tsx index 9cdb9b1d7..06c486ac9 100644 --- a/src/frontend/screens/MapScreen/gps/GPSModal.tsx +++ b/src/frontend/screens/MapScreen/gps/GPSModal.tsx @@ -19,7 +19,7 @@ export const GPSModal = () => { if (backgroundStatus && foregroundStatus && isGranted === null) { setIsGranted(backgroundStatus.granted && foregroundStatus.granted); } - }, [backgroundStatus, foregroundStatus]); + }, [backgroundStatus, foregroundStatus, isGranted]); const onBottomSheetDismiss = () => { setCurrentTab('Map'); @@ -36,3 +36,12 @@ export const GPSModal = () => { ); }; + +const styles = StyleSheet.create({ + wrapper: { + position: 'absolute', + height: '100%', + width: '100%', + backgroundColor: 'transparent', + }, +}); diff --git a/src/frontend/screens/MapScreen/index.tsx b/src/frontend/screens/MapScreen/index.tsx index 8fd94059f..ddb574d66 100644 --- a/src/frontend/screens/MapScreen/index.tsx +++ b/src/frontend/screens/MapScreen/index.tsx @@ -29,6 +29,7 @@ import { FullLocationData, useTracksStore, } from '../../hooks/tracks/useTracksStore'; +import {GPSIndicator} from './gps/GPSIndicator'; // This is the default zoom used when the map first loads, and also the zoom // that the map will zoom to if the user clicks the "Locate" button and the @@ -134,6 +135,7 @@ export const MapScreen = () => { )} {isFinishedLoading && } + Date: Mon, 15 Apr 2024 12:52:22 +0200 Subject: [PATCH 027/123] move gps indicator to map header --- src/frontend/screens/MapScreen/gps/GPSModal.tsx | 9 --------- src/frontend/screens/MapScreen/index.tsx | 2 -- 2 files changed, 11 deletions(-) diff --git a/src/frontend/screens/MapScreen/gps/GPSModal.tsx b/src/frontend/screens/MapScreen/gps/GPSModal.tsx index 06c486ac9..636177cfe 100644 --- a/src/frontend/screens/MapScreen/gps/GPSModal.tsx +++ b/src/frontend/screens/MapScreen/gps/GPSModal.tsx @@ -36,12 +36,3 @@ export const GPSModal = () => { ); }; - -const styles = StyleSheet.create({ - wrapper: { - position: 'absolute', - height: '100%', - width: '100%', - backgroundColor: 'transparent', - }, -}); diff --git a/src/frontend/screens/MapScreen/index.tsx b/src/frontend/screens/MapScreen/index.tsx index ddb574d66..8fd94059f 100644 --- a/src/frontend/screens/MapScreen/index.tsx +++ b/src/frontend/screens/MapScreen/index.tsx @@ -29,7 +29,6 @@ import { FullLocationData, useTracksStore, } from '../../hooks/tracks/useTracksStore'; -import {GPSIndicator} from './gps/GPSIndicator'; // This is the default zoom used when the map first loads, and also the zoom // that the map will zoom to if the user clicks the "Locate" button and the @@ -135,7 +134,6 @@ export const MapScreen = () => { )} {isFinishedLoading && } - Date: Fri, 12 Apr 2024 18:16:53 +0200 Subject: [PATCH 028/123] fix bug where observations don't display when there's no tracking line --- src/frontend/screens/MapScreen/ObsevationMapLayer.tsx | 6 +----- src/frontend/screens/MapScreen/index.tsx | 1 + 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/frontend/screens/MapScreen/ObsevationMapLayer.tsx b/src/frontend/screens/MapScreen/ObsevationMapLayer.tsx index f1e795e53..d287706b5 100644 --- a/src/frontend/screens/MapScreen/ObsevationMapLayer.tsx +++ b/src/frontend/screens/MapScreen/ObsevationMapLayer.tsx @@ -36,11 +36,7 @@ export const ObservationMapLayer = () => { onPress={handlePressEvent} id="observations-source" shape={featureCollection}> - + ); }; diff --git a/src/frontend/screens/MapScreen/index.tsx b/src/frontend/screens/MapScreen/index.tsx index 8fd94059f..25ae0d5ae 100644 --- a/src/frontend/screens/MapScreen/index.tsx +++ b/src/frontend/screens/MapScreen/index.tsx @@ -122,6 +122,7 @@ export const MapScreen = () => { Date: Mon, 15 Apr 2024 13:00:31 +0200 Subject: [PATCH 029/123] cleanup track store naming --- .../{useTracksStore.ts => useCurrentTrackStore.ts} | 2 +- src/frontend/hooks/tracks/useTracking.ts | 6 +++--- src/frontend/screens/MapScreen/index.tsx | 11 +++++++---- src/frontend/screens/ObservationEdit/SaveButton.tsx | 8 +++++--- 4 files changed, 16 insertions(+), 11 deletions(-) rename src/frontend/hooks/tracks/{useTracksStore.ts => useCurrentTrackStore.ts} (94%) diff --git a/src/frontend/hooks/tracks/useTracksStore.ts b/src/frontend/hooks/tracks/useCurrentTrackStore.ts similarity index 94% rename from src/frontend/hooks/tracks/useTracksStore.ts rename to src/frontend/hooks/tracks/useCurrentTrackStore.ts index 1f7e9b5c1..c27f4fa91 100644 --- a/src/frontend/hooks/tracks/useTracksStore.ts +++ b/src/frontend/hooks/tracks/useCurrentTrackStore.ts @@ -30,7 +30,7 @@ type TracksStoreState = { setTracking: (val: boolean) => void; }; -export const useTracksStore = create(set => ({ +export const useCurrentTrackStore = create(set => ({ isTracking: false, locationHistory: [], observations: [], diff --git a/src/frontend/hooks/tracks/useTracking.ts b/src/frontend/hooks/tracks/useTracking.ts index 77074a407..d9ce90818 100644 --- a/src/frontend/hooks/tracks/useTracking.ts +++ b/src/frontend/hooks/tracks/useTracking.ts @@ -1,7 +1,7 @@ import * as Location from 'expo-location'; import * as TaskManager from 'expo-task-manager'; import {useCallback, useState} from 'react'; -import {FullLocationData, useTracksStore} from './useTracksStore'; +import {FullLocationData, useCurrentTrackStore} from './useCurrentTrackStore'; export const LOCATION_TASK_NAME = 'background-location-task'; @@ -11,8 +11,8 @@ type LocationCallbackInfo = { }; export function useTracking() { const [loading, setLoading] = useState(false); - const tracksStore = useTracksStore(); - const isTracking = useTracksStore(state => state.isTracking); + const tracksStore = useCurrentTrackStore(); + const isTracking = useCurrentTrackStore(state => state.isTracking); const addNewTrackLocations = useCallback( ({data, error}: LocationCallbackInfo) => { if (error) { diff --git a/src/frontend/screens/MapScreen/index.tsx b/src/frontend/screens/MapScreen/index.tsx index 25ae0d5ae..e74709bf1 100644 --- a/src/frontend/screens/MapScreen/index.tsx +++ b/src/frontend/screens/MapScreen/index.tsx @@ -27,8 +27,8 @@ import {useLocationProviderStatus} from '../../hooks/useLocationProviderStatus'; import {GPSModal} from './gps/GPSModal'; import { FullLocationData, - useTracksStore, -} from '../../hooks/tracks/useTracksStore'; + useCurrentTrackStore, +} from '../../hooks/tracks/useCurrentTrackStore'; // This is the default zoom used when the map first loads, and also the zoom // that the map will zoom to if the user clicks the "Locate" button and the @@ -55,7 +55,7 @@ export const MapScreen = () => { const locationServicesEnabled = !!locationProviderStatus?.locationServicesEnabled; - const locationHistory = useTracksStore(state => state.locationHistory); + const locationHistory = useCurrentTrackStore(state => state.locationHistory); const handleAddPress = () => { newDraft(); @@ -119,7 +119,10 @@ export const MapScreen = () => { )} {locationHistory.length > 1 && ( <> - + console.log('display bottom sheet')} + id="routeSource" + shape={toRoute(locationHistory)}> state.addNewLocations); - const addNewTrackObservation = useTracksStore( + const addNewTrackLocation = useCurrentTrackStore( + state => state.addNewLocations, + ); + const addNewTrackObservation = useCurrentTrackStore( state => state.addNewObservation, ); From 41c3dd5a7e344df7161df9dc0ce1101707559e00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20Stefa=C5=84czyk?= Date: Mon, 15 Apr 2024 13:30:38 +0200 Subject: [PATCH 030/123] extract track layer to separate component --- .../screens/MapScreen/TrackPathLayer.tsx | 45 +++++++++++++++++++ src/frontend/screens/MapScreen/index.tsx | 45 ++----------------- 2 files changed, 48 insertions(+), 42 deletions(-) create mode 100644 src/frontend/screens/MapScreen/TrackPathLayer.tsx diff --git a/src/frontend/screens/MapScreen/TrackPathLayer.tsx b/src/frontend/screens/MapScreen/TrackPathLayer.tsx new file mode 100644 index 000000000..13b067b0a --- /dev/null +++ b/src/frontend/screens/MapScreen/TrackPathLayer.tsx @@ -0,0 +1,45 @@ +import {LineJoin, LineLayer, ShapeSource} from '@rnmapbox/maps'; +import { + FullLocationData, + useCurrentTrackStore, +} from '../../hooks/tracks/useCurrentTrackStore'; +import * as React from 'react'; +import {StyleSheet} from 'react-native'; +import {LineString} from 'geojson'; +export const TrackPathLayer = () => { + const locationHistory = useCurrentTrackStore(state => state.locationHistory); + + return ( + locationHistory.length > 1 && ( + console.log('display bottom sheet')} + id="routeSource" + shape={toRoute(locationHistory)}> + + + ) + ); +}; + +const toRoute = (locations: FullLocationData[]): LineString => { + return { + type: 'LineString', + coordinates: locations.map(location => [ + location.coords.longitude, + location.coords.latitude, + ]), + }; +}; + +const styles = StyleSheet.create({ + lineLayer: { + lineColor: '#000000', + lineWidth: 5, + lineCap: LineJoin.Round, + lineOpacity: 1.84, + }, +} as any); diff --git a/src/frontend/screens/MapScreen/index.tsx b/src/frontend/screens/MapScreen/index.tsx index e74709bf1..d7dbf8e6c 100644 --- a/src/frontend/screens/MapScreen/index.tsx +++ b/src/frontend/screens/MapScreen/index.tsx @@ -1,17 +1,11 @@ import * as React from 'react'; -import Mapbox, { - LineJoin, - LineLayer, - ShapeSource, - UserLocation, -} from '@rnmapbox/maps'; +import Mapbox, {UserLocation} from '@rnmapbox/maps'; import config from '../../../config.json'; import {IconButton} from '../../sharedComponents/IconButton'; import { LocationFollowingIcon, LocationNoFollowIcon, } from '../../sharedComponents/icons'; -import {LineString} from 'geojson'; import {View, StyleSheet} from 'react-native'; import {ObservationMapLayer} from './ObsevationMapLayer'; @@ -25,10 +19,7 @@ import {useIsFullyFocused} from '../../hooks/useIsFullyFocused'; import {useLastKnownLocation} from '../../hooks/useLastSavedLocation'; import {useLocationProviderStatus} from '../../hooks/useLocationProviderStatus'; import {GPSModal} from './gps/GPSModal'; -import { - FullLocationData, - useCurrentTrackStore, -} from '../../hooks/tracks/useCurrentTrackStore'; +import {TrackPathLayer} from './TrackPathLayer'; // This is the default zoom used when the map first loads, and also the zoom // that the map will zoom to if the user clicks the "Locate" button and the @@ -55,8 +46,6 @@ export const MapScreen = () => { const locationServicesEnabled = !!locationProviderStatus?.locationServicesEnabled; - const locationHistory = useCurrentTrackStore(state => state.locationHistory); - const handleAddPress = () => { newDraft(); navigate('PresetChooser'); @@ -117,26 +106,8 @@ export const MapScreen = () => { minDisplacement={MIN_DISPLACEMENT} /> )} - {locationHistory.length > 1 && ( - <> - console.log('display bottom sheet')} - id="routeSource" - shape={toRoute(locationHistory)}> - - - - )} {isFinishedLoading && } + {isFinishedLoading && } [ - location.coords.longitude, - location.coords.latitude, - ]), - }; -} From ce07f72948bc5b0b47340e9e56b9d8f48c3a50c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20Stefa=C5=84czyk?= Date: Mon, 15 Apr 2024 13:35:39 +0200 Subject: [PATCH 031/123] correct imports, remove unused ones --- src/frontend/screens/MapScreen/ObsevationMapLayer.tsx | 4 ++-- src/frontend/screens/ObservationsList/ObservationListItem.tsx | 3 --- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/frontend/screens/MapScreen/ObsevationMapLayer.tsx b/src/frontend/screens/MapScreen/ObsevationMapLayer.tsx index d287706b5..1af24008f 100644 --- a/src/frontend/screens/MapScreen/ObsevationMapLayer.tsx +++ b/src/frontend/screens/MapScreen/ObsevationMapLayer.tsx @@ -2,8 +2,8 @@ import {Observation} from '@mapeo/schema'; import React from 'react'; import MapboxGL from '@rnmapbox/maps'; import {useAllObservations} from '../../hooks/useAllObservations'; -import {OnPressEvent} from '@rnmapbox/maps/lib/typescript/types/OnPressEvent'; import {useNavigationFromHomeTabs} from '../../hooks/useNavigationWithTypes'; +import {OnPressEvent} from '@rnmapbox/maps/lib/typescript/src/types/OnPressEvent'; const DEFAULT_MARKER_COLOR = '#F29D4B'; @@ -24,7 +24,7 @@ export const ObservationMapLayer = () => { }; function handlePressEvent(event: OnPressEvent) { - const properties = event.features[0].properties; + const properties = event.features[0]?.properties; if (!properties) return; if (!('id' in properties)) return; diff --git a/src/frontend/screens/ObservationsList/ObservationListItem.tsx b/src/frontend/screens/ObservationsList/ObservationListItem.tsx index 81a4fa4e9..26eee8c50 100644 --- a/src/frontend/screens/ObservationsList/ObservationListItem.tsx +++ b/src/frontend/screens/ObservationsList/ObservationListItem.tsx @@ -4,10 +4,7 @@ import {Text} from '../../sharedComponents/Text'; import {TouchableHighlight} from '../../sharedComponents/Touchables'; import {CategoryCircleIcon} from '../../sharedComponents/icons/CategoryIcon'; -//import PhotoView from "../../sharedComponents/PhotoView"; -// import useDeviceId from "../../hooks/useDeviceId"; import {Attachment, ViewStyleProp} from '../../sharedTypes'; -import {filterPhotosFromAttachments} from '../../hooks/persistedState/usePersistedDraftObservation/photosMethods'; import {BLACK} from '../../lib/styles'; import {Observation} from '@mapeo/schema'; import { From 07aab304faebcd81e94463a0e2f796525159f580 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20Stefa=C5=84czyk?= Date: Mon, 15 Apr 2024 19:55:06 +0200 Subject: [PATCH 032/123] implement user location tooltip --- .../hooks/tracks/useCurrentTrackStore.ts | 36 ++++++++- src/frontend/hooks/useFormattedTimeSince.ts | 33 ++++++++ .../screens/MapScreen/TrackPathLayer.tsx | 8 +- src/frontend/screens/MapScreen/index.tsx | 17 ++-- .../MapScreen/track/UserTooltipMarker.tsx | 81 +++++++++++++++++++ src/frontend/utils/distance.ts | 36 +++++++++ 6 files changed, 202 insertions(+), 9 deletions(-) create mode 100644 src/frontend/hooks/useFormattedTimeSince.ts create mode 100644 src/frontend/screens/MapScreen/track/UserTooltipMarker.tsx create mode 100644 src/frontend/utils/distance.ts diff --git a/src/frontend/hooks/tracks/useCurrentTrackStore.ts b/src/frontend/hooks/tracks/useCurrentTrackStore.ts index c27f4fa91..cd24fa43f 100644 --- a/src/frontend/hooks/tracks/useCurrentTrackStore.ts +++ b/src/frontend/hooks/tracks/useCurrentTrackStore.ts @@ -1,4 +1,5 @@ import {create} from 'zustand'; +import {calculateTotalDistance} from '../../utils/distance'; export type LocationData = { coords: { @@ -24,6 +25,8 @@ type TracksStoreState = { isTracking: boolean; locationHistory: FullLocationData[]; observations: string[]; + distance: number; + trackingSince: Date; addNewObservation: (observationId: string) => void; addNewLocations: (locationData: FullLocationData[]) => void; clearLocationHistory: () => void; @@ -34,10 +37,39 @@ export const useCurrentTrackStore = create(set => ({ isTracking: false, locationHistory: [], observations: [], + distance: 0, + trackingSince: new Date(0), addNewObservation: (id: string) => set(state => ({observations: [...state.observations, id]})), addNewLocations: data => - set(state => ({locationHistory: [...state.locationHistory, ...data]})), + set(state => { + const {locationHistory} = state; + + if (data.length > 1) { + return { + locationHistory: [...locationHistory, ...data], + distance: state.distance + calculateTotalDistance(data), + }; + } + if (locationHistory.length < 1) { + return { + locationHistory: [...locationHistory, ...data], + }; + } + const lastLocation = locationHistory[locationHistory.length - 1]; + if (!lastLocation) { + throw Error('No lastLocation for state.locationHistory.length > 1'); + } + return { + locationHistory: [...state.locationHistory, ...data], + distance: + state.distance + calculateTotalDistance([lastLocation, ...data]), + }; + }), clearLocationHistory: () => set(() => ({locationHistory: []})), - setTracking: (val: boolean) => set(() => ({isTracking: val})), + setTracking: (val: boolean) => + set(() => ({ + isTracking: val, + trackingSince: val ? new Date() : new Date(0), + })), })); diff --git a/src/frontend/hooks/useFormattedTimeSince.ts b/src/frontend/hooks/useFormattedTimeSince.ts new file mode 100644 index 000000000..ec898343c --- /dev/null +++ b/src/frontend/hooks/useFormattedTimeSince.ts @@ -0,0 +1,33 @@ +import {useEffect, useState} from 'react'; + +export const useFormattedTimeSince = (start: Date, interval: number) => { + const [currentTime, setCurrentTime] = useState(new Date()); + + useEffect(() => { + const timer = setInterval(() => { + setCurrentTime(new Date()); + }, interval); + return () => clearInterval(timer); + }, [interval]); + + return secondsToHMS( + Math.floor((currentTime.getTime() - start.getTime()) / 1000), + ); +}; + +const secondsToHMS = (secs: number) => { + function z(n: number) { + return (n < 10 ? '0' : '') + n; + } + var sign = secs < 0 ? '-' : ''; + secs = Math.abs(secs); + /* eslint-disable no-bitwise */ + return ( + sign + + z((secs / 3600) | 0) + + ':' + + z(((secs % 3600) / 60) | 0) + + ':' + + z(secs % 60) + ); +}; diff --git a/src/frontend/screens/MapScreen/TrackPathLayer.tsx b/src/frontend/screens/MapScreen/TrackPathLayer.tsx index 13b067b0a..c49fcaeb6 100644 --- a/src/frontend/screens/MapScreen/TrackPathLayer.tsx +++ b/src/frontend/screens/MapScreen/TrackPathLayer.tsx @@ -6,15 +6,19 @@ import { import * as React from 'react'; import {StyleSheet} from 'react-native'; import {LineString} from 'geojson'; +import {useLocation} from '../../hooks/useLocation'; export const TrackPathLayer = () => { const locationHistory = useCurrentTrackStore(state => state.locationHistory); - + const {location} = useLocation({maxDistanceInterval: 3}); + const finalLocationHistory = location?.coords + ? [...locationHistory, location as any] + : locationHistory; return ( locationHistory.length > 1 && ( console.log('display bottom sheet')} id="routeSource" - shape={toRoute(locationHistory)}> + shape={toRoute(finalLocationHistory)}> { const locationServicesEnabled = !!locationProviderStatus?.locationServicesEnabled; + const {isTracking} = useTracking(); + const handleAddPress = () => { newDraft(); navigate('PresetChooser'); @@ -101,10 +105,13 @@ export const MapScreen = () => { /> {coords !== undefined && locationServicesEnabled && ( - + <> + + {isTracking && } + )} {isFinishedLoading && } {isFinishedLoading && } diff --git a/src/frontend/screens/MapScreen/track/UserTooltipMarker.tsx b/src/frontend/screens/MapScreen/track/UserTooltipMarker.tsx new file mode 100644 index 000000000..d56fa0106 --- /dev/null +++ b/src/frontend/screens/MapScreen/track/UserTooltipMarker.tsx @@ -0,0 +1,81 @@ +import {MarkerView} from '@rnmapbox/maps'; +import {StyleSheet, Text, View} from 'react-native'; +import {useLocation} from '../../../hooks/useLocation'; +import React from 'react'; +import {useCurrentTrackStore} from '../../../hooks/tracks/useCurrentTrackStore'; +import {useFormattedTimeSince} from '../../../hooks/useFormattedTimeSince'; + +export const UserTooltipMarker = () => { + const {location} = useLocation({maxDistanceInterval: 2}); + const totalDistance = useCurrentTrackStore(state => state.distance); + const trackingSince = useCurrentTrackStore(state => state.trackingSince); + const timer = useFormattedTimeSince(trackingSince, 1000); + return ( + location?.coords && ( + + + + + {totalDistance.toFixed(2)}km + + + + {timer} + + + + + + + ) + ); +}; + +const styles = StyleSheet.create({ + container: { + alignItems: 'center', + justifyContent: 'center', + marginBottom: 13, + }, + wrapper: { + backgroundColor: '#FFF', + padding: 10, + borderRadius: 5, + alignItems: 'center', + justifyContent: 'center', + color: 'black', + display: 'flex', + flexDirection: 'row', + }, + text: { + color: '#333333', + }, + separator: { + marginLeft: 10, + marginRight: 10, + height: 12, + borderColor: '#CCCCD6', + borderLeftWidth: 1, + color: '#CCCCD6', + }, + indicator: { + marginLeft: 5, + height: 10, + width: 10, + borderRadius: 99, + backgroundColor: '#59A553', + }, + arrow: { + alignItems: 'center', + justifyContent: 'center', + borderTopWidth: 15, + borderLeftWidth: 10, + borderRightWidth: 10, + borderTopColor: '#FFF', + borderLeftColor: 'transparent', + borderRightColor: 'transparent', + }, +}); diff --git a/src/frontend/utils/distance.ts b/src/frontend/utils/distance.ts new file mode 100644 index 000000000..709fd6ec7 --- /dev/null +++ b/src/frontend/utils/distance.ts @@ -0,0 +1,36 @@ +import {FullLocationData} from '../hooks/tracks/useCurrentTrackStore'; + +const EARTH_RADIUS = 6371; // Radius of the earth in km + +const degreesToRadians = (degrees: number): number => degrees * (Math.PI / 180); + +export const calculateTotalDistance = (points: FullLocationData[]): number => + points.reduce((previousValue, currentValue, i, arr) => { + if (i === 0) { + return previousValue; + } + + const pointA = arr[i - 1]; + if (!pointA) { + throw Error('No point A for i=' + i); + } + + const pointB = currentValue; + + const dLat = degreesToRadians( + pointB.coords.latitude - pointA.coords.latitude, + ); + const dLon = degreesToRadians( + pointB.coords.longitude - pointA.coords.longitude, + ); + + const a = + Math.sin(dLat / 2) * Math.sin(dLat / 2) + + Math.cos(degreesToRadians(pointA.coords.latitude)) * + Math.cos(degreesToRadians(pointB.coords.latitude)) * + Math.sin(dLon / 2) * + Math.sin(dLon / 2); + const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + + return previousValue + EARTH_RADIUS * c; + }, 0); From 39abc5885498b228f84cdb7497fea5b30870de82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20Stefa=C5=84czyk?= Date: Tue, 16 Apr 2024 08:59:12 +0200 Subject: [PATCH 033/123] path drawing fixes --- src/frontend/hooks/tracks/useTracking.ts | 2 +- src/frontend/screens/MapScreen/ObsevationMapLayer.tsx | 6 +----- src/frontend/screens/MapScreen/TrackPathLayer.tsx | 7 +++++-- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/frontend/hooks/tracks/useTracking.ts b/src/frontend/hooks/tracks/useTracking.ts index d9ce90818..8f8a46cbf 100644 --- a/src/frontend/hooks/tracks/useTracking.ts +++ b/src/frontend/hooks/tracks/useTracking.ts @@ -45,7 +45,7 @@ export function useTracking() { }, [addNewTrackLocations, isTracking, tracksStore]); const cancelTracking = useCallback(async () => { - await TaskManager.unregisterTaskAsync(LOCATION_TASK_NAME); + await Location.stopLocationUpdatesAsync(LOCATION_TASK_NAME); tracksStore.setTracking(false); }, [tracksStore]); diff --git a/src/frontend/screens/MapScreen/ObsevationMapLayer.tsx b/src/frontend/screens/MapScreen/ObsevationMapLayer.tsx index f068be6b1..1af24008f 100644 --- a/src/frontend/screens/MapScreen/ObsevationMapLayer.tsx +++ b/src/frontend/screens/MapScreen/ObsevationMapLayer.tsx @@ -36,11 +36,7 @@ export const ObservationMapLayer = () => { onPress={handlePressEvent} id="observations-source" shape={featureCollection}> - + ); }; diff --git a/src/frontend/screens/MapScreen/TrackPathLayer.tsx b/src/frontend/screens/MapScreen/TrackPathLayer.tsx index c49fcaeb6..138f1bca8 100644 --- a/src/frontend/screens/MapScreen/TrackPathLayer.tsx +++ b/src/frontend/screens/MapScreen/TrackPathLayer.tsx @@ -9,19 +9,22 @@ import {LineString} from 'geojson'; import {useLocation} from '../../hooks/useLocation'; export const TrackPathLayer = () => { const locationHistory = useCurrentTrackStore(state => state.locationHistory); + const isTracking = useCurrentTrackStore(state => state.isTracking); const {location} = useLocation({maxDistanceInterval: 3}); const finalLocationHistory = location?.coords ? [...locationHistory, location as any] : locationHistory; + return ( - locationHistory.length > 1 && ( + locationHistory.length > 1 && + isTracking && ( console.log('display bottom sheet')} id="routeSource" shape={toRoute(finalLocationHistory)}> From a2119fbef021c923836d69870039018a17d939de Mon Sep 17 00:00:00 2001 From: bohdanprog Date: Tue, 16 Apr 2024 09:08:38 +0200 Subject: [PATCH 034/123] add timer indicator to stop tracking modal --- .../screens/MapScreen/gps/GPSEnabled.tsx | 73 ++++++++++++++----- 1 file changed, 53 insertions(+), 20 deletions(-) diff --git a/src/frontend/screens/MapScreen/gps/GPSEnabled.tsx b/src/frontend/screens/MapScreen/gps/GPSEnabled.tsx index 5bbc1d476..818ada4c2 100644 --- a/src/frontend/screens/MapScreen/gps/GPSEnabled.tsx +++ b/src/frontend/screens/MapScreen/gps/GPSEnabled.tsx @@ -5,9 +5,14 @@ import {Text} from '../../../sharedComponents/Text'; import {useTracking} from '../../../hooks/tracks/useTracking'; import StartTrackingIcon from '../../../images/StartTracking.svg'; import StopTrackingIcon from '../../../images/StopTracking.svg'; +import {useFormattedTimeSince} from '../../../hooks/useFormattedTimeSince'; +import {useCurrentTrackStore} from '../../../hooks/tracks/useCurrentTrackStore'; export const GPSEnabled = () => { const {isTracking, cancelTracking, startTracking, loading} = useTracking(); + const trackingSince = useCurrentTrackStore(state => state.trackingSince); + const timer = useFormattedTimeSince(trackingSince, 1000); + const styles = getStyles(isTracking); const handleTracking = useCallback(() => { isTracking ? cancelTracking() : startTracking(); @@ -23,32 +28,60 @@ export const GPSEnabled = () => { + {isTracking && ( + + + You’ve been recording for + {timer} + + )} ); }; -const styles = StyleSheet.create({ - container: {paddingHorizontal: 20, paddingVertical: 30}, - buttonWrapper: { - flexDirection: 'row', - display: 'flex', - alignItems: 'center', - width: '100%', - }, - buttonText: { - fontWeight: '500', - color: '#fff', - width: '100%', - flex: 1, - textAlign: 'center', - }, -}); +const getStyles = (isTracking: boolean) => { + return StyleSheet.create({ + button: {backgroundColor: isTracking ? '#D92222' : '#0066FF'}, + container: {paddingHorizontal: 20, paddingVertical: 30}, + buttonWrapper: { + flexDirection: 'row', + display: 'flex', + alignItems: 'center', + width: '100%', + }, + buttonText: { + fontWeight: '500', + color: '#fff', + width: '100%', + flex: 1, + textAlign: 'center', + }, + runtimeWrapper: { + paddingTop: 20, + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + }, + indicator: { + marginRight: 5, + height: 10, + width: 10, + borderRadius: 99, + backgroundColor: '#59A553', + }, + text: {fontSize: 16}, + timer: { + marginLeft: 5, + fontWeight: 'bold', + fontSize: 16, + }, + }); +}; From f8061529a6e7cd2f5c0489a316273cac8882b5d7 Mon Sep 17 00:00:00 2001 From: bohdanprog Date: Tue, 16 Apr 2024 11:15:31 +0200 Subject: [PATCH 035/123] add show timer indicator in bottom tab bar --- .eslintrc.js | 5 ++ .../Navigation/ScreenGroups/AppScreens.tsx | 69 ++++--------------- .../ScreenGroups/TabBar/TabBarIcon.tsx | 19 ++--- .../ScreenGroups/TabBar/TabBarLabel.tsx | 24 ++++--- .../TabBar/TabBarTrackingLabel.tsx | 9 +++ .../TabBar/TrackingTabBarIcon.tsx | 45 ++++++++++++ src/frontend/Navigation/types.ts | 17 +++++ src/frontend/hooks/useCurrentTab.ts | 36 ++++++++++ src/frontend/sharedComponents/HomeHeader.tsx | 6 +- 9 files changed, 153 insertions(+), 77 deletions(-) create mode 100644 src/frontend/Navigation/ScreenGroups/TabBar/TabBarTrackingLabel.tsx create mode 100644 src/frontend/Navigation/ScreenGroups/TabBar/TrackingTabBarIcon.tsx create mode 100644 src/frontend/Navigation/types.ts create mode 100644 src/frontend/hooks/useCurrentTab.ts diff --git a/.eslintrc.js b/.eslintrc.js index e2064424b..06ac355c3 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -17,6 +17,11 @@ module.exports = { 'no-shadow': 'off', 'no-undef': 'off', 'react-native/no-inline-styles': 'off', + 'react/no-unstable-nested-components': [ + { + allowAsProps: true, + }, + ], }, }, ], diff --git a/src/frontend/Navigation/ScreenGroups/AppScreens.tsx b/src/frontend/Navigation/ScreenGroups/AppScreens.tsx index 723b1728a..8c5852c07 100644 --- a/src/frontend/Navigation/ScreenGroups/AppScreens.tsx +++ b/src/frontend/Navigation/ScreenGroups/AppScreens.tsx @@ -1,11 +1,5 @@ import {createBottomTabNavigator} from '@react-navigation/bottom-tabs'; -import { - EventArg, - getFocusedRouteNameFromRoute, - NavigatorScreenParams, - useNavigation, - useRoute, -} from '@react-navigation/native'; +import {NavigatorScreenParams} from '@react-navigation/native'; import * as React from 'react'; import {HomeHeader} from '../../sharedComponents/HomeHeader'; import {RootStack} from '../AppStack'; @@ -49,10 +43,8 @@ import { EditScreen as DeviceNameEditScreen, createNavigationOptions as createDeviceNameEditNavOptions, } from '../../screens/Settings/ProjectSettings/DeviceName/EditScreen'; -import {useNavigationStore} from '../../hooks/useNavigationStore'; import {TabBarLabel} from './TabBar/TabBarLabel'; import {TabBarIcon} from './TabBar/TabBarIcon'; -import {useGPSModalContext} from '../../contexts/GPSModalContext'; import {useLocation} from '../../hooks/useLocation'; import {useForegroundPermissions} from 'expo-location'; import {useLocationProviderStatus} from '../../hooks/useLocationProviderStatus'; @@ -61,8 +53,9 @@ import { GpsModal, createNavigationOptions as createGpsModalNavigationOptions, } from '../../screens/GpsModal'; - -export type TabName = keyof HomeTabsList; +import {useCurrentTab} from '../../hooks/useCurrentTab'; +import {TrackingTabBarIcon} from './TabBar/TrackingTabBarIcon'; +import {TrackingLabel} from './TabBar/TabBarTrackingLabel'; export type HomeTabsList = { Map: undefined; @@ -146,10 +139,7 @@ export type AppList = { const Tab = createBottomTabNavigator(); const HomeTabs = () => { - const {setCurrentTab, currentTab} = useNavigationStore(); - const navigation = useNavigation(); - const route = useRoute(); - const {bottomSheetRef} = useGPSModalContext(); + const {handleTabPress} = useCurrentTab(); const locationState = useLocation({maxDistanceInterval: 0}); const [permissions] = useForegroundPermissions(); const locationProviderStatus = useLocationProviderStatus(); @@ -164,28 +154,9 @@ const HomeTabs = () => { providerStatus: locationProviderStatus, }); - const handleTabPress = ({ - target, - preventDefault, - }: EventArg<'tabPress', true, undefined>) => { - const targetTab = target?.split('-')[0]; - if (targetTab === 'Tracking') { - preventDefault(); - bottomSheetRef.current?.present(); - } else { - bottomSheetRef.current?.close(); - } - const currentTab = getFocusedRouteNameFromRoute(route); - if (currentTab === 'Camera') { - navigation.navigate('Map' as never); - } - setCurrentTab((target?.split('-')[0] || 'Map') as unknown as TabName); - }; return ( ({ header: () => ( { component={MapScreen} options={{ tabBarIcon: params => ( - - ), - tabBarLabel: params => ( - + ), + tabBarLabel: params => , }} /> { tabBarIcon: params => ( ), - tabBarLabel: params => ( - - ), + tabBarLabel: params => , }} /> ( - - ), - tabBarLabel: params => ( - - ), + tabBarIcon: TrackingTabBarIcon, + tabBarLabel: TrackingLabel, }} children={() => <>} /> diff --git a/src/frontend/Navigation/ScreenGroups/TabBar/TabBarIcon.tsx b/src/frontend/Navigation/ScreenGroups/TabBar/TabBarIcon.tsx index 25786f48d..31ee597e2 100644 --- a/src/frontend/Navigation/ScreenGroups/TabBar/TabBarIcon.tsx +++ b/src/frontend/Navigation/ScreenGroups/TabBar/TabBarIcon.tsx @@ -1,22 +1,23 @@ -import * as React from 'react'; +import React, {FC} from 'react'; import MaterialIcons from 'react-native-vector-icons/MaterialIcons'; -import {FC} from 'react'; +import {useNavigationStore} from '../../../hooks/useNavigationStore'; +import {TabBarIconProps, TabName} from '../../types'; -export interface TabBarIcon { - size: number; - focused: boolean; - color: string; - isFocused: boolean; +export interface TabBarIcon extends TabBarIconProps { + tabName: TabName; iconName: string; } -export const TabBarIcon: FC = ({size, isFocused, iconName}) => { + +export const TabBarIcon: FC = ({size, tabName, iconName}) => { + const {currentTab} = useNavigationStore(); + const color1 = 'rgb(0, 122, 255)'; const color2 = '#8E8E8F'; return ( ); }; diff --git a/src/frontend/Navigation/ScreenGroups/TabBar/TabBarLabel.tsx b/src/frontend/Navigation/ScreenGroups/TabBar/TabBarLabel.tsx index 689c49e40..5dff3b1af 100644 --- a/src/frontend/Navigation/ScreenGroups/TabBar/TabBarLabel.tsx +++ b/src/frontend/Navigation/ScreenGroups/TabBar/TabBarLabel.tsx @@ -1,16 +1,20 @@ -import * as React from 'react'; +import React, {FC} from 'react'; import {Text} from 'react-native'; -import {FC} from 'react'; -import {LabelPosition} from '@react-navigation/bottom-tabs/lib/typescript/src/types'; +import {useNavigationStore} from '../../../hooks/useNavigationStore'; +import {TabBarLabelParams} from '../../types'; -export interface TabBarLabel { - isFocused: boolean; - color: string; - position: LabelPosition; - children: string; +export interface TabBarLabel extends TabBarLabelParams { + tabName: string; } -export const TabBarLabel: FC = ({children, isFocused}) => { + +export const TabBarLabel: FC = ({children, tabName}) => { + const {currentTab} = useNavigationStore(); + const color1 = 'rgb(0, 122, 255)'; const color2 = '#8E8E8F'; - return {children}; + return ( + + {children} + + ); }; diff --git a/src/frontend/Navigation/ScreenGroups/TabBar/TabBarTrackingLabel.tsx b/src/frontend/Navigation/ScreenGroups/TabBar/TabBarTrackingLabel.tsx new file mode 100644 index 000000000..3e11288d0 --- /dev/null +++ b/src/frontend/Navigation/ScreenGroups/TabBar/TabBarTrackingLabel.tsx @@ -0,0 +1,9 @@ +import React, {FC} from 'react'; +import {TabBarLabel} from './TabBarLabel'; +import {useTracking} from '../../../hooks/tracks/useTracking'; +import {TabBarLabelParams} from '../../types'; + +export const TrackingLabel: FC = props => { + const {isTracking} = useTracking(); + return !isTracking && ; +}; diff --git a/src/frontend/Navigation/ScreenGroups/TabBar/TrackingTabBarIcon.tsx b/src/frontend/Navigation/ScreenGroups/TabBar/TrackingTabBarIcon.tsx new file mode 100644 index 000000000..ad2d6aed6 --- /dev/null +++ b/src/frontend/Navigation/ScreenGroups/TabBar/TrackingTabBarIcon.tsx @@ -0,0 +1,45 @@ +import React, {FC} from 'react'; +import {StyleSheet, View} from 'react-native'; +import {TabBarIcon} from './TabBarIcon'; +import {useTracking} from '../../../hooks/tracks/useTracking'; +import {useCurrentTrackStore} from '../../../hooks/tracks/useCurrentTrackStore'; +import {useFormattedTimeSince} from '../../../hooks/useFormattedTimeSince'; +import {Text} from '../../../sharedComponents/Text'; +import {TabBarIconProps} from '../../types'; + +export const TrackingTabBarIcon: FC = props => { + const {isTracking} = useTracking(); + const trackingSince = useCurrentTrackStore(state => state.trackingSince); + const timer = useFormattedTimeSince(trackingSince, 1000); + + return ( + <> + {isTracking && ( + + + {timer} + + )} + + + ); +}; + +const styles = StyleSheet.create({ + runtimeWrapper: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + }, + indicator: { + marginRight: 5, + height: 10, + width: 10, + borderRadius: 99, + backgroundColor: '#59A553', + }, + timer: { + marginLeft: 5, + fontSize: 12, + }, +}); diff --git a/src/frontend/Navigation/types.ts b/src/frontend/Navigation/types.ts new file mode 100644 index 000000000..9042f631c --- /dev/null +++ b/src/frontend/Navigation/types.ts @@ -0,0 +1,17 @@ +import {LabelPosition} from '@react-navigation/bottom-tabs/lib/typescript/src/types'; +import {HomeTabsList} from './ScreenGroups/AppScreens'; + +export interface TabBarIconProps { + size: number; + focused: boolean; + color: string; +} + +export interface TabBarLabelParams { + focused: boolean; + color: string; + position: LabelPosition; + children: string; +} + +export type TabName = keyof HomeTabsList; diff --git a/src/frontend/hooks/useCurrentTab.ts b/src/frontend/hooks/useCurrentTab.ts new file mode 100644 index 000000000..385ae53cd --- /dev/null +++ b/src/frontend/hooks/useCurrentTab.ts @@ -0,0 +1,36 @@ +import { + useNavigation, + useRoute, + EventArg, + getFocusedRouteNameFromRoute, +} from '@react-navigation/native'; +import {useGPSModalContext} from '../contexts/GPSModalContext'; +import {TabName} from '../Navigation/ScreenGroups/AppScreens'; +import {useNavigationStore} from './useNavigationStore'; + +export const useCurrentTab = () => { + const {setCurrentTab} = useNavigationStore(); + const navigation = useNavigation(); + const route = useRoute(); + const {bottomSheetRef} = useGPSModalContext(); + + const handleTabPress = ({ + target, + preventDefault, + }: EventArg<'tabPress', true, undefined>) => { + const targetTab = target?.split('-')[0]; + if (targetTab === 'Tracking') { + preventDefault(); + bottomSheetRef.current?.present(); + } else { + bottomSheetRef.current?.close(); + } + const currentTab = getFocusedRouteNameFromRoute(route); + if (currentTab === 'Camera') { + navigation.navigate('Map' as never); + } + setCurrentTab((target?.split('-')[0] || 'Map') as unknown as TabName); + }; + + return {handleTabPress}; +}; diff --git a/src/frontend/sharedComponents/HomeHeader.tsx b/src/frontend/sharedComponents/HomeHeader.tsx index 6d682d05c..9cb57f217 100644 --- a/src/frontend/sharedComponents/HomeHeader.tsx +++ b/src/frontend/sharedComponents/HomeHeader.tsx @@ -21,7 +21,7 @@ export const HomeHeader = ({locationStatus, precision}: Props) => { style={styles.linearGradient} colors={['#0006', '#0000']} /> - {/* Placeholder for left button */} + {/* Placeholder for left button */} Date: Tue, 16 Apr 2024 11:47:29 +0200 Subject: [PATCH 036/123] remove tab bar label, changed tabbar height --- .../Navigation/ScreenGroups/AppScreens.tsx | 15 +++++++------- .../ScreenGroups/TabBar/TabBarLabel.tsx | 20 ------------------- .../TabBar/TabBarTrackingLabel.tsx | 9 --------- src/frontend/Navigation/types.ts | 8 -------- .../screens/MapScreen/TrackPathLayer.tsx | 1 + .../MapScreen/track/UserTooltipMarker.tsx | 2 +- .../CustomBottomSheetModal.tsx | 3 ++- 7 files changed, 11 insertions(+), 47 deletions(-) delete mode 100644 src/frontend/Navigation/ScreenGroups/TabBar/TabBarLabel.tsx delete mode 100644 src/frontend/Navigation/ScreenGroups/TabBar/TabBarTrackingLabel.tsx diff --git a/src/frontend/Navigation/ScreenGroups/AppScreens.tsx b/src/frontend/Navigation/ScreenGroups/AppScreens.tsx index 8c5852c07..5acd67d82 100644 --- a/src/frontend/Navigation/ScreenGroups/AppScreens.tsx +++ b/src/frontend/Navigation/ScreenGroups/AppScreens.tsx @@ -43,7 +43,6 @@ import { EditScreen as DeviceNameEditScreen, createNavigationOptions as createDeviceNameEditNavOptions, } from '../../screens/Settings/ProjectSettings/DeviceName/EditScreen'; -import {TabBarLabel} from './TabBar/TabBarLabel'; import {TabBarIcon} from './TabBar/TabBarIcon'; import {useLocation} from '../../hooks/useLocation'; import {useForegroundPermissions} from 'expo-location'; @@ -55,7 +54,8 @@ import { } from '../../screens/GpsModal'; import {useCurrentTab} from '../../hooks/useCurrentTab'; import {TrackingTabBarIcon} from './TabBar/TrackingTabBarIcon'; -import {TrackingLabel} from './TabBar/TabBarTrackingLabel'; + +export const TAB_BAR_HEIGHT = 70; export type HomeTabsList = { Map: undefined; @@ -158,6 +158,10 @@ const HomeTabs = () => { ({ + tabBarStyle: { + height: TAB_BAR_HEIGHT, + }, + tabBarShowLabel: false, header: () => ( { tabBarIcon: params => ( ), - tabBarLabel: params => , }} /> { iconName="photo-camera" /> ), - tabBarLabel: params => , }} /> <>} /> diff --git a/src/frontend/Navigation/ScreenGroups/TabBar/TabBarLabel.tsx b/src/frontend/Navigation/ScreenGroups/TabBar/TabBarLabel.tsx deleted file mode 100644 index 5dff3b1af..000000000 --- a/src/frontend/Navigation/ScreenGroups/TabBar/TabBarLabel.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import React, {FC} from 'react'; -import {Text} from 'react-native'; -import {useNavigationStore} from '../../../hooks/useNavigationStore'; -import {TabBarLabelParams} from '../../types'; - -export interface TabBarLabel extends TabBarLabelParams { - tabName: string; -} - -export const TabBarLabel: FC = ({children, tabName}) => { - const {currentTab} = useNavigationStore(); - - const color1 = 'rgb(0, 122, 255)'; - const color2 = '#8E8E8F'; - return ( - - {children} - - ); -}; diff --git a/src/frontend/Navigation/ScreenGroups/TabBar/TabBarTrackingLabel.tsx b/src/frontend/Navigation/ScreenGroups/TabBar/TabBarTrackingLabel.tsx deleted file mode 100644 index 3e11288d0..000000000 --- a/src/frontend/Navigation/ScreenGroups/TabBar/TabBarTrackingLabel.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import React, {FC} from 'react'; -import {TabBarLabel} from './TabBarLabel'; -import {useTracking} from '../../../hooks/tracks/useTracking'; -import {TabBarLabelParams} from '../../types'; - -export const TrackingLabel: FC = props => { - const {isTracking} = useTracking(); - return !isTracking && ; -}; diff --git a/src/frontend/Navigation/types.ts b/src/frontend/Navigation/types.ts index 9042f631c..5df57c839 100644 --- a/src/frontend/Navigation/types.ts +++ b/src/frontend/Navigation/types.ts @@ -1,4 +1,3 @@ -import {LabelPosition} from '@react-navigation/bottom-tabs/lib/typescript/src/types'; import {HomeTabsList} from './ScreenGroups/AppScreens'; export interface TabBarIconProps { @@ -7,11 +6,4 @@ export interface TabBarIconProps { color: string; } -export interface TabBarLabelParams { - focused: boolean; - color: string; - position: LabelPosition; - children: string; -} - export type TabName = keyof HomeTabsList; diff --git a/src/frontend/screens/MapScreen/TrackPathLayer.tsx b/src/frontend/screens/MapScreen/TrackPathLayer.tsx index 138f1bca8..2469355a8 100644 --- a/src/frontend/screens/MapScreen/TrackPathLayer.tsx +++ b/src/frontend/screens/MapScreen/TrackPathLayer.tsx @@ -7,6 +7,7 @@ import * as React from 'react'; import {StyleSheet} from 'react-native'; import {LineString} from 'geojson'; import {useLocation} from '../../hooks/useLocation'; + export const TrackPathLayer = () => { const locationHistory = useCurrentTrackStore(state => state.locationHistory); const isTracking = useCurrentTrackStore(state => state.isTracking); diff --git a/src/frontend/screens/MapScreen/track/UserTooltipMarker.tsx b/src/frontend/screens/MapScreen/track/UserTooltipMarker.tsx index d56fa0106..2b5649e36 100644 --- a/src/frontend/screens/MapScreen/track/UserTooltipMarker.tsx +++ b/src/frontend/screens/MapScreen/track/UserTooltipMarker.tsx @@ -6,7 +6,7 @@ import {useCurrentTrackStore} from '../../../hooks/tracks/useCurrentTrackStore'; import {useFormattedTimeSince} from '../../../hooks/useFormattedTimeSince'; export const UserTooltipMarker = () => { - const {location} = useLocation({maxDistanceInterval: 2}); + const {location} = useLocation({maxDistanceInterval: 0}); const totalDistance = useCurrentTrackStore(state => state.distance); const trackingSince = useCurrentTrackStore(state => state.trackingSince); const timer = useFormattedTimeSince(trackingSince, 1000); diff --git a/src/frontend/sharedComponents/BottomSheetModal/CustomBottomSheetModal.tsx b/src/frontend/sharedComponents/BottomSheetModal/CustomBottomSheetModal.tsx index 35f2a53bc..2cdb2ca83 100644 --- a/src/frontend/sharedComponents/BottomSheetModal/CustomBottomSheetModal.tsx +++ b/src/frontend/sharedComponents/BottomSheetModal/CustomBottomSheetModal.tsx @@ -2,6 +2,7 @@ import React, {FC} from 'react'; import {StyleSheet, TouchableWithoutFeedback, View} from 'react-native'; import {BottomSheetModal, BottomSheetView} from '@gorhom/bottom-sheet'; import {BottomSheetModalMethods} from '@gorhom/bottom-sheet/lib/typescript/types'; +import {TAB_BAR_HEIGHT} from '../../Navigation/ScreenGroups/AppScreens'; interface CustomBottomSheetModal { dismiss: () => void; @@ -27,7 +28,7 @@ export const CustomBottomSheetModal: FC = ({ /> Date: Tue, 16 Apr 2024 11:50:16 +0200 Subject: [PATCH 037/123] save only required data for track path drawing --- .../hooks/tracks/useCurrentTrackStore.ts | 14 ++++++++++-- src/frontend/hooks/tracks/useTracking.ts | 8 ++++++- .../screens/MapScreen/TrackPathLayer.tsx | 16 ++++++++++---- .../screens/ObservationEdit/SaveButton.tsx | 22 ++++--------------- src/frontend/utils/distance.ts | 16 +++++--------- 5 files changed, 41 insertions(+), 35 deletions(-) diff --git a/src/frontend/hooks/tracks/useCurrentTrackStore.ts b/src/frontend/hooks/tracks/useCurrentTrackStore.ts index cd24fa43f..0eb1383f6 100644 --- a/src/frontend/hooks/tracks/useCurrentTrackStore.ts +++ b/src/frontend/hooks/tracks/useCurrentTrackStore.ts @@ -21,14 +21,24 @@ export type FullLocationData = { }; timestamp: number; }; + +export type LocationHistoryPoint = { + timestamp: number; +} & LonLatData; + +export type LonLatData = { + longitude: number; + latitude: number; +}; + type TracksStoreState = { isTracking: boolean; - locationHistory: FullLocationData[]; + locationHistory: LocationHistoryPoint[]; observations: string[]; distance: number; trackingSince: Date; addNewObservation: (observationId: string) => void; - addNewLocations: (locationData: FullLocationData[]) => void; + addNewLocations: (locationData: LocationHistoryPoint[]) => void; clearLocationHistory: () => void; setTracking: (val: boolean) => void; }; diff --git a/src/frontend/hooks/tracks/useTracking.ts b/src/frontend/hooks/tracks/useTracking.ts index 8f8a46cbf..f2fae98f1 100644 --- a/src/frontend/hooks/tracks/useTracking.ts +++ b/src/frontend/hooks/tracks/useTracking.ts @@ -19,7 +19,13 @@ export function useTracking() { console.error('Error while processing location update callback', error); } if (data?.locations) { - tracksStore.addNewLocations(data.locations); + tracksStore.addNewLocations( + data.locations.map(loc => ({ + latitude: loc.coords.latitude, + longitude: loc.coords.longitude, + timestamp: loc.timestamp, + })), + ); } }, [tracksStore], diff --git a/src/frontend/screens/MapScreen/TrackPathLayer.tsx b/src/frontend/screens/MapScreen/TrackPathLayer.tsx index 2469355a8..b8ca58dd9 100644 --- a/src/frontend/screens/MapScreen/TrackPathLayer.tsx +++ b/src/frontend/screens/MapScreen/TrackPathLayer.tsx @@ -1,6 +1,7 @@ import {LineJoin, LineLayer, ShapeSource} from '@rnmapbox/maps'; import { FullLocationData, + LocationHistoryPoint, useCurrentTrackStore, } from '../../hooks/tracks/useCurrentTrackStore'; import * as React from 'react'; @@ -13,7 +14,14 @@ export const TrackPathLayer = () => { const isTracking = useCurrentTrackStore(state => state.isTracking); const {location} = useLocation({maxDistanceInterval: 3}); const finalLocationHistory = location?.coords - ? [...locationHistory, location as any] + ? [ + ...locationHistory, + { + latitude: location.coords.latitude, + longitude: location.coords.longitude, + timestamp: new Date().getTime(), + }, + ] : locationHistory; return ( @@ -33,12 +41,12 @@ export const TrackPathLayer = () => { ); }; -const toRoute = (locations: FullLocationData[]): LineString => { +const toRoute = (locations: LocationHistoryPoint[]): LineString => { return { type: 'LineString', coordinates: locations.map(location => [ - location.coords.longitude, - location.coords.latitude, + location.longitude, + location.latitude, ]), }; }; diff --git a/src/frontend/screens/ObservationEdit/SaveButton.tsx b/src/frontend/screens/ObservationEdit/SaveButton.tsx index 0eaf6d0b5..5b4a2f724 100644 --- a/src/frontend/screens/ObservationEdit/SaveButton.tsx +++ b/src/frontend/screens/ObservationEdit/SaveButton.tsx @@ -141,15 +141,8 @@ export const SaveButton = ({ addNewTrackLocation([ { timestamp: new Date().getTime(), - coords: { - accuracy: 0, - altitude: 0, - latitude: value.lat || 0, - longitude: value.lon || 0, - altitudeAccuracy: 0, - heading: 0, - speed: 0, - }, + latitude: value.lat, + longitude: value.lon, }, ]); } @@ -181,15 +174,8 @@ export const SaveButton = ({ addNewTrackLocation([ { timestamp: new Date().getTime(), - coords: { - accuracy: 0, - altitude: 0, - latitude: value.lat || 0, - longitude: value.lon || 0, - altitudeAccuracy: 0, - heading: 0, - speed: 0, - }, + latitude: value.lat, + longitude: value.lon, }, ]); } diff --git a/src/frontend/utils/distance.ts b/src/frontend/utils/distance.ts index 709fd6ec7..b103f681a 100644 --- a/src/frontend/utils/distance.ts +++ b/src/frontend/utils/distance.ts @@ -1,10 +1,10 @@ -import {FullLocationData} from '../hooks/tracks/useCurrentTrackStore'; +import {LonLatData} from '../hooks/tracks/useCurrentTrackStore'; const EARTH_RADIUS = 6371; // Radius of the earth in km const degreesToRadians = (degrees: number): number => degrees * (Math.PI / 180); -export const calculateTotalDistance = (points: FullLocationData[]): number => +export const calculateTotalDistance = (points: LonLatData[]): number => points.reduce((previousValue, currentValue, i, arr) => { if (i === 0) { return previousValue; @@ -17,17 +17,13 @@ export const calculateTotalDistance = (points: FullLocationData[]): number => const pointB = currentValue; - const dLat = degreesToRadians( - pointB.coords.latitude - pointA.coords.latitude, - ); - const dLon = degreesToRadians( - pointB.coords.longitude - pointA.coords.longitude, - ); + const dLat = degreesToRadians(pointB.latitude - pointA.latitude); + const dLon = degreesToRadians(pointB.longitude - pointA.longitude); const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + - Math.cos(degreesToRadians(pointA.coords.latitude)) * - Math.cos(degreesToRadians(pointB.coords.latitude)) * + Math.cos(degreesToRadians(pointA.latitude)) * + Math.cos(degreesToRadians(pointB.latitude)) * Math.sin(dLon / 2) * Math.sin(dLon / 2); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); From e8172db17bfad20b80250f56afcaa0d1926a1d60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20Stefa=C5=84czyk?= Date: Tue, 16 Apr 2024 11:51:46 +0200 Subject: [PATCH 038/123] move TrackPathLayer to track folder --- src/frontend/screens/MapScreen/index.tsx | 2 +- src/frontend/screens/MapScreen/{ => track}/TrackPathLayer.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename src/frontend/screens/MapScreen/{ => track}/TrackPathLayer.tsx (93%) diff --git a/src/frontend/screens/MapScreen/index.tsx b/src/frontend/screens/MapScreen/index.tsx index 8b0ceca59..b36736c4c 100644 --- a/src/frontend/screens/MapScreen/index.tsx +++ b/src/frontend/screens/MapScreen/index.tsx @@ -19,7 +19,7 @@ import {useIsFullyFocused} from '../../hooks/useIsFullyFocused'; import {useLastKnownLocation} from '../../hooks/useLastSavedLocation'; import {useLocationProviderStatus} from '../../hooks/useLocationProviderStatus'; import {GPSModal} from './gps/GPSModal'; -import {TrackPathLayer} from './TrackPathLayer'; +import {TrackPathLayer} from './track/TrackPathLayer'; import {useTracking} from '../../hooks/tracks/useTracking'; import {UserTooltipMarker} from './track/UserTooltipMarker'; diff --git a/src/frontend/screens/MapScreen/TrackPathLayer.tsx b/src/frontend/screens/MapScreen/track/TrackPathLayer.tsx similarity index 93% rename from src/frontend/screens/MapScreen/TrackPathLayer.tsx rename to src/frontend/screens/MapScreen/track/TrackPathLayer.tsx index b8ca58dd9..44268e533 100644 --- a/src/frontend/screens/MapScreen/TrackPathLayer.tsx +++ b/src/frontend/screens/MapScreen/track/TrackPathLayer.tsx @@ -3,11 +3,11 @@ import { FullLocationData, LocationHistoryPoint, useCurrentTrackStore, -} from '../../hooks/tracks/useCurrentTrackStore'; +} from '../../../hooks/tracks/useCurrentTrackStore'; import * as React from 'react'; import {StyleSheet} from 'react-native'; import {LineString} from 'geojson'; -import {useLocation} from '../../hooks/useLocation'; +import {useLocation} from '../../../hooks/useLocation'; export const TrackPathLayer = () => { const locationHistory = useCurrentTrackStore(state => state.locationHistory); From d8327d66cb4324e6ecf7bd9e67ebd868dcc31051 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20Stefa=C5=84czyk?= Date: Tue, 16 Apr 2024 12:01:06 +0200 Subject: [PATCH 039/123] move task definition to different place --- src/frontend/hooks/tracks/useTracking.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/frontend/hooks/tracks/useTracking.ts b/src/frontend/hooks/tracks/useTracking.ts index f2fae98f1..8efba7d0c 100644 --- a/src/frontend/hooks/tracks/useTracking.ts +++ b/src/frontend/hooks/tracks/useTracking.ts @@ -2,6 +2,7 @@ import * as Location from 'expo-location'; import * as TaskManager from 'expo-task-manager'; import {useCallback, useState} from 'react'; import {FullLocationData, useCurrentTrackStore} from './useCurrentTrackStore'; +import React from 'react'; export const LOCATION_TASK_NAME = 'background-location-task'; @@ -13,6 +14,11 @@ export function useTracking() { const [loading, setLoading] = useState(false); const tracksStore = useCurrentTrackStore(); const isTracking = useCurrentTrackStore(state => state.isTracking); + + React.useEffect(() => { + TaskManager.defineTask(LOCATION_TASK_NAME, addNewTrackLocations); + }, []); + const addNewTrackLocations = useCallback( ({data, error}: LocationCallbackInfo) => { if (error) { @@ -33,7 +39,6 @@ export function useTracking() { const startTracking = useCallback(async () => { setLoading(true); - TaskManager.defineTask(LOCATION_TASK_NAME, addNewTrackLocations); if (isTracking) { console.warn('Start tracking attempt while tracking already enabled'); From 7d6b48ddd16ac9256925a96160e355324a040616 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20Stefa=C5=84czyk?= Date: Tue, 16 Apr 2024 12:14:37 +0200 Subject: [PATCH 040/123] fix layers order --- src/frontend/screens/MapScreen/ObsevationMapLayer.tsx | 10 ++++++++-- .../screens/MapScreen/track/TrackPathLayer.tsx | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/frontend/screens/MapScreen/ObsevationMapLayer.tsx b/src/frontend/screens/MapScreen/ObsevationMapLayer.tsx index 1af24008f..dc6ae5c78 100644 --- a/src/frontend/screens/MapScreen/ObsevationMapLayer.tsx +++ b/src/frontend/screens/MapScreen/ObsevationMapLayer.tsx @@ -4,6 +4,8 @@ import MapboxGL from '@rnmapbox/maps'; import {useAllObservations} from '../../hooks/useAllObservations'; import {useNavigationFromHomeTabs} from '../../hooks/useNavigationWithTypes'; import {OnPressEvent} from '@rnmapbox/maps/lib/typescript/src/types/OnPressEvent'; +import {useTracking} from '../../hooks/tracks/useTracking'; +import {useCurrentTrackStore} from '../../hooks/tracks/useCurrentTrackStore'; const DEFAULT_MARKER_COLOR = '#F29D4B'; @@ -17,7 +19,7 @@ const layerStyles = { export const ObservationMapLayer = () => { const observations = useAllObservations(); const {navigate} = useNavigationFromHomeTabs(); - + const isTracking = useCurrentTrackStore(state => state.isTracking); const featureCollection: GeoJSON.FeatureCollection = { type: 'FeatureCollection', features: mapObservationsToFeatures(observations), @@ -36,7 +38,11 @@ export const ObservationMapLayer = () => { onPress={handlePressEvent} id="observations-source" shape={featureCollection}> - + ); }; diff --git a/src/frontend/screens/MapScreen/track/TrackPathLayer.tsx b/src/frontend/screens/MapScreen/track/TrackPathLayer.tsx index 44268e533..88edfa489 100644 --- a/src/frontend/screens/MapScreen/track/TrackPathLayer.tsx +++ b/src/frontend/screens/MapScreen/track/TrackPathLayer.tsx @@ -33,7 +33,7 @@ export const TrackPathLayer = () => { shape={toRoute(finalLocationHistory)}> From 86ceb3ac2413651cee656d37b23c4a23c933eaac Mon Sep 17 00:00:00 2001 From: bohdanprog Date: Tue, 16 Apr 2024 12:14:57 +0200 Subject: [PATCH 041/123] create context where we keep timer data --- .../TabBar/TrackingTabBarIcon.tsx | 6 ++-- src/frontend/contexts/ExternalProviders.tsx | 9 ++++-- src/frontend/contexts/GPSModalContext.tsx | 2 +- src/frontend/contexts/TrackTimerContext.tsx | 32 +++++++++++++++++++ .../screens/MapScreen/gps/GPSEnabled.tsx | 7 ++-- .../MapScreen/track/UserTooltipMarker.tsx | 6 ++-- 6 files changed, 47 insertions(+), 15 deletions(-) create mode 100644 src/frontend/contexts/TrackTimerContext.tsx diff --git a/src/frontend/Navigation/ScreenGroups/TabBar/TrackingTabBarIcon.tsx b/src/frontend/Navigation/ScreenGroups/TabBar/TrackingTabBarIcon.tsx index ad2d6aed6..3bd53af26 100644 --- a/src/frontend/Navigation/ScreenGroups/TabBar/TrackingTabBarIcon.tsx +++ b/src/frontend/Navigation/ScreenGroups/TabBar/TrackingTabBarIcon.tsx @@ -2,15 +2,13 @@ import React, {FC} from 'react'; import {StyleSheet, View} from 'react-native'; import {TabBarIcon} from './TabBarIcon'; import {useTracking} from '../../../hooks/tracks/useTracking'; -import {useCurrentTrackStore} from '../../../hooks/tracks/useCurrentTrackStore'; -import {useFormattedTimeSince} from '../../../hooks/useFormattedTimeSince'; import {Text} from '../../../sharedComponents/Text'; import {TabBarIconProps} from '../../types'; +import {useTrackTimerContext} from '../../../contexts/TrackTimerContext'; export const TrackingTabBarIcon: FC = props => { const {isTracking} = useTracking(); - const trackingSince = useCurrentTrackStore(state => state.trackingSince); - const timer = useFormattedTimeSince(trackingSince, 1000); + const {timer} = useTrackTimerContext(); return ( <> diff --git a/src/frontend/contexts/ExternalProviders.tsx b/src/frontend/contexts/ExternalProviders.tsx index 898afd1e9..f4830bc8a 100644 --- a/src/frontend/contexts/ExternalProviders.tsx +++ b/src/frontend/contexts/ExternalProviders.tsx @@ -12,6 +12,7 @@ import { import {BottomSheetModalProvider} from '@gorhom/bottom-sheet'; import {AppStackList} from '../Navigation/AppStack'; import {GPSModalContextProvider} from './GPSModalContext'; +import {TrackTimerContextProvider} from './TrackTimerContext'; type ExternalProvidersProp = { children: React.ReactNode; @@ -28,9 +29,11 @@ export const ExternalProviders = ({ - - {children} - + + + {children} + + diff --git a/src/frontend/contexts/GPSModalContext.tsx b/src/frontend/contexts/GPSModalContext.tsx index 7cdc6dfad..cdbdaa338 100644 --- a/src/frontend/contexts/GPSModalContext.tsx +++ b/src/frontend/contexts/GPSModalContext.tsx @@ -22,7 +22,7 @@ function useGPSModalContext() { const context = useContext(GPSModalContext); if (!context) { throw new Error( - 'useBottomSheetContext must be used within a BottomSheetContextProvider', + 'useGPSModalContext must be used within a GPSModalContextProvider', ); } return context; diff --git a/src/frontend/contexts/TrackTimerContext.tsx b/src/frontend/contexts/TrackTimerContext.tsx new file mode 100644 index 000000000..a95048160 --- /dev/null +++ b/src/frontend/contexts/TrackTimerContext.tsx @@ -0,0 +1,32 @@ +import React, {createContext, useContext} from 'react'; +import {useCurrentTrackStore} from '../hooks/tracks/useCurrentTrackStore'; +import {useFormattedTimeSince} from '../hooks/useFormattedTimeSince'; + +interface TrackTimerContext { + timer: string; +} + +const TrackTimerContext = createContext(null); + +const TrackTimerContextProvider = ({children}: {children: React.ReactNode}) => { + const trackingSince = useCurrentTrackStore(state => state.trackingSince); + const timer = useFormattedTimeSince(trackingSince, 1000); + + return ( + + {children} + + ); +}; + +function useTrackTimerContext() { + const context = useContext(TrackTimerContext); + if (!context) { + throw new Error( + 'useTrackTimerContext must be used within a TrackTimerContextProvider', + ); + } + return context; +} + +export {TrackTimerContextProvider, useTrackTimerContext}; diff --git a/src/frontend/screens/MapScreen/gps/GPSEnabled.tsx b/src/frontend/screens/MapScreen/gps/GPSEnabled.tsx index 818ada4c2..d55c052fc 100644 --- a/src/frontend/screens/MapScreen/gps/GPSEnabled.tsx +++ b/src/frontend/screens/MapScreen/gps/GPSEnabled.tsx @@ -5,13 +5,12 @@ import {Text} from '../../../sharedComponents/Text'; import {useTracking} from '../../../hooks/tracks/useTracking'; import StartTrackingIcon from '../../../images/StartTracking.svg'; import StopTrackingIcon from '../../../images/StopTracking.svg'; -import {useFormattedTimeSince} from '../../../hooks/useFormattedTimeSince'; -import {useCurrentTrackStore} from '../../../hooks/tracks/useCurrentTrackStore'; +import {useTrackTimerContext} from '../../../contexts/TrackTimerContext'; export const GPSEnabled = () => { const {isTracking, cancelTracking, startTracking, loading} = useTracking(); - const trackingSince = useCurrentTrackStore(state => state.trackingSince); - const timer = useFormattedTimeSince(trackingSince, 1000); + const {timer} = useTrackTimerContext(); + const styles = getStyles(isTracking); const handleTracking = useCallback(() => { diff --git a/src/frontend/screens/MapScreen/track/UserTooltipMarker.tsx b/src/frontend/screens/MapScreen/track/UserTooltipMarker.tsx index 2b5649e36..2eb2ce462 100644 --- a/src/frontend/screens/MapScreen/track/UserTooltipMarker.tsx +++ b/src/frontend/screens/MapScreen/track/UserTooltipMarker.tsx @@ -3,13 +3,13 @@ import {StyleSheet, Text, View} from 'react-native'; import {useLocation} from '../../../hooks/useLocation'; import React from 'react'; import {useCurrentTrackStore} from '../../../hooks/tracks/useCurrentTrackStore'; -import {useFormattedTimeSince} from '../../../hooks/useFormattedTimeSince'; +import {useTrackTimerContext} from '../../../contexts/TrackTimerContext'; export const UserTooltipMarker = () => { + const {timer} = useTrackTimerContext(); const {location} = useLocation({maxDistanceInterval: 0}); const totalDistance = useCurrentTrackStore(state => state.distance); - const trackingSince = useCurrentTrackStore(state => state.trackingSince); - const timer = useFormattedTimeSince(trackingSince, 1000); + return ( location?.coords && ( Date: Tue, 16 Apr 2024 12:30:10 +0200 Subject: [PATCH 042/123] extract types from store to common types --- .../hooks/tracks/useCurrentTrackStore.ts | 31 +------------------ src/frontend/hooks/tracks/useTracking.ts | 4 ++- src/frontend/sharedTypes/location.ts | 21 +++++++++++++ 3 files changed, 25 insertions(+), 31 deletions(-) create mode 100644 src/frontend/sharedTypes/location.ts diff --git a/src/frontend/hooks/tracks/useCurrentTrackStore.ts b/src/frontend/hooks/tracks/useCurrentTrackStore.ts index 0eb1383f6..7d8d7dd35 100644 --- a/src/frontend/hooks/tracks/useCurrentTrackStore.ts +++ b/src/frontend/hooks/tracks/useCurrentTrackStore.ts @@ -1,35 +1,6 @@ import {create} from 'zustand'; import {calculateTotalDistance} from '../../utils/distance'; - -export type LocationData = { - coords: { - latitude: number; - accuracy: number; - longitude: number; - }; - timestamp: number; -}; -export type FullLocationData = { - coords: { - altitude: number; - altitudeAccuracy: number; - latitude: number; - accuracy: number; - longitude: number; - heading: number; - speed: number; - }; - timestamp: number; -}; - -export type LocationHistoryPoint = { - timestamp: number; -} & LonLatData; - -export type LonLatData = { - longitude: number; - latitude: number; -}; +import {LocationHistoryPoint} from '../../sharedTypes/location'; type TracksStoreState = { isTracking: boolean; diff --git a/src/frontend/hooks/tracks/useTracking.ts b/src/frontend/hooks/tracks/useTracking.ts index 8efba7d0c..46e3a7f4a 100644 --- a/src/frontend/hooks/tracks/useTracking.ts +++ b/src/frontend/hooks/tracks/useTracking.ts @@ -1,8 +1,9 @@ import * as Location from 'expo-location'; import * as TaskManager from 'expo-task-manager'; import {useCallback, useState} from 'react'; -import {FullLocationData, useCurrentTrackStore} from './useCurrentTrackStore'; +import {useCurrentTrackStore} from './useCurrentTrackStore'; import React from 'react'; +import {FullLocationData} from '../../sharedTypes/location'; export const LOCATION_TASK_NAME = 'background-location-task'; @@ -10,6 +11,7 @@ type LocationCallbackInfo = { data: {locations: FullLocationData[]} | null; error: TaskManager.TaskManagerError | null; }; + export function useTracking() { const [loading, setLoading] = useState(false); const tracksStore = useCurrentTrackStore(); diff --git a/src/frontend/sharedTypes/location.ts b/src/frontend/sharedTypes/location.ts new file mode 100644 index 000000000..b3d800ff2 --- /dev/null +++ b/src/frontend/sharedTypes/location.ts @@ -0,0 +1,21 @@ +export type FullLocationData = { + coords: { + altitude: number; + altitudeAccuracy: number; + latitude: number; + accuracy: number; + longitude: number; + heading: number; + speed: number; + }; + timestamp: number; +}; + +export type LocationHistoryPoint = { + timestamp: number; +} & LonLatData; + +export type LonLatData = { + longitude: number; + latitude: number; +}; From 5593ed27bdb9b0567e7302c511417234e7ef02fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20Stefa=C5=84czyk?= Date: Tue, 16 Apr 2024 12:56:51 +0200 Subject: [PATCH 043/123] pre-review cleanups --- .../ScreenGroups/TabBar/TabBarIcon.tsx | 5 +-- .../hooks/tracks/useCurrentTrackStore.ts | 14 +++--- src/frontend/hooks/tracks/useTracking.ts | 15 ++++--- .../screens/MapScreen/UserLocation.tsx | 22 +++++----- .../screens/MapScreen/gps/GPSIndicator.tsx | 43 ------------------- src/frontend/screens/MapScreen/index.tsx | 21 +++------ 6 files changed, 34 insertions(+), 86 deletions(-) delete mode 100644 src/frontend/screens/MapScreen/gps/GPSIndicator.tsx diff --git a/src/frontend/Navigation/ScreenGroups/TabBar/TabBarIcon.tsx b/src/frontend/Navigation/ScreenGroups/TabBar/TabBarIcon.tsx index 31ee597e2..14dd65986 100644 --- a/src/frontend/Navigation/ScreenGroups/TabBar/TabBarIcon.tsx +++ b/src/frontend/Navigation/ScreenGroups/TabBar/TabBarIcon.tsx @@ -2,6 +2,7 @@ import React, {FC} from 'react'; import MaterialIcons from 'react-native-vector-icons/MaterialIcons'; import {useNavigationStore} from '../../../hooks/useNavigationStore'; import {TabBarIconProps, TabName} from '../../types'; +import {COMAPEO_BLUE, MEDIUM_GREY} from '../../../lib/styles'; export interface TabBarIcon extends TabBarIconProps { tabName: TabName; @@ -11,13 +12,11 @@ export interface TabBarIcon extends TabBarIconProps { export const TabBarIcon: FC = ({size, tabName, iconName}) => { const {currentTab} = useNavigationStore(); - const color1 = 'rgb(0, 122, 255)'; - const color2 = '#8E8E8F'; return ( ); }; diff --git a/src/frontend/hooks/tracks/useCurrentTrackStore.ts b/src/frontend/hooks/tracks/useCurrentTrackStore.ts index 7d8d7dd35..1a125abd3 100644 --- a/src/frontend/hooks/tracks/useCurrentTrackStore.ts +++ b/src/frontend/hooks/tracks/useCurrentTrackStore.ts @@ -23,28 +23,28 @@ export const useCurrentTrackStore = create(set => ({ addNewObservation: (id: string) => set(state => ({observations: [...state.observations, id]})), addNewLocations: data => - set(state => { - const {locationHistory} = state; - + set(({locationHistory, distance}) => { if (data.length > 1) { return { locationHistory: [...locationHistory, ...data], - distance: state.distance + calculateTotalDistance(data), + distance: distance + calculateTotalDistance(data), }; } + if (locationHistory.length < 1) { return { locationHistory: [...locationHistory, ...data], }; } + const lastLocation = locationHistory[locationHistory.length - 1]; if (!lastLocation) { throw Error('No lastLocation for state.locationHistory.length > 1'); } + return { - locationHistory: [...state.locationHistory, ...data], - distance: - state.distance + calculateTotalDistance([lastLocation, ...data]), + locationHistory: [...locationHistory, ...data], + distance: distance + calculateTotalDistance([lastLocation, ...data]), }; }), clearLocationHistory: () => set(() => ({locationHistory: []})), diff --git a/src/frontend/hooks/tracks/useTracking.ts b/src/frontend/hooks/tracks/useTracking.ts index 46e3a7f4a..3fde724b0 100644 --- a/src/frontend/hooks/tracks/useTracking.ts +++ b/src/frontend/hooks/tracks/useTracking.ts @@ -14,7 +14,8 @@ type LocationCallbackInfo = { export function useTracking() { const [loading, setLoading] = useState(false); - const tracksStore = useCurrentTrackStore(); + const addNewLocations = useCurrentTrackStore(state => state.addNewLocations); + const setTracking = useCurrentTrackStore(state => state.setTracking); const isTracking = useCurrentTrackStore(state => state.isTracking); React.useEffect(() => { @@ -27,7 +28,7 @@ export function useTracking() { console.error('Error while processing location update callback', error); } if (data?.locations) { - tracksStore.addNewLocations( + addNewLocations( data.locations.map(loc => ({ latitude: loc.coords.latitude, longitude: loc.coords.longitude, @@ -36,7 +37,7 @@ export function useTracking() { ); } }, - [tracksStore], + [addNewLocations], ); const startTracking = useCallback(async () => { @@ -53,14 +54,14 @@ export function useTracking() { activityType: Location.LocationActivityType.Fitness, }); - tracksStore.setTracking(true); + setTracking(true); setLoading(false); - }, [addNewTrackLocations, isTracking, tracksStore]); + }, [addNewTrackLocations, isTracking, setTracking]); const cancelTracking = useCallback(async () => { await Location.stopLocationUpdatesAsync(LOCATION_TASK_NAME); - tracksStore.setTracking(false); - }, [tracksStore]); + setTracking(false); + }, [setTracking]); return {isTracking, startTracking, cancelTracking, loading}; } diff --git a/src/frontend/screens/MapScreen/UserLocation.tsx b/src/frontend/screens/MapScreen/UserLocation.tsx index a5596b073..60607db30 100644 --- a/src/frontend/screens/MapScreen/UserLocation.tsx +++ b/src/frontend/screens/MapScreen/UserLocation.tsx @@ -1,19 +1,21 @@ -import MapboxGL from '@rnmapbox/maps'; +import {UserLocation as MBUserLocation} from '@rnmapbox/maps'; import * as React from 'react'; -// import {useExperiments} from '../../hooks/useExperiments'; +import {useCurrentTrackStore} from '../../hooks/tracks/useCurrentTrackStore'; +import {useIsFullyFocused} from '../../hooks/useIsFullyFocused'; +import {UserTooltipMarker} from './track/UserTooltipMarker'; + interface UserLocationProps { - visible: boolean; minDisplacement: number; } -export const UserLocation = ({visible, minDisplacement}: UserLocationProps) => { - // const [{directionalArrow}] = useExperiments(); +export const UserLocation = ({minDisplacement}: UserLocationProps) => { + const isTracking = useCurrentTrackStore(state => state.isTracking); + const isFocused = useIsFullyFocused(); return ( - + <> + + {isTracking && } + ); }; diff --git a/src/frontend/screens/MapScreen/gps/GPSIndicator.tsx b/src/frontend/screens/MapScreen/gps/GPSIndicator.tsx deleted file mode 100644 index 672bf8063..000000000 --- a/src/frontend/screens/MapScreen/gps/GPSIndicator.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import React from 'react'; -import {StyleSheet, View} from 'react-native'; -import {Text} from '../../../sharedComponents/Text'; -import NoGPSSignalImage from '../../../images/NoGPSSignal.svg'; -import ActiveGPSSignalImage from '../../../images/ActiveGPSSignal.svg'; -import * as Location from 'expo-location'; -import {useLocation} from '../../../hooks/useLocation'; - -export const GPSIndicator = () => { - const {location} = useLocation({maxDistanceInterval: 15}); - const [backgroundStatus] = Location.useBackgroundPermissions(); - const [foregroundStatus] = Location.useForegroundPermissions(); - - return ( - - - {backgroundStatus?.granted && foregroundStatus?.granted ? ( - <> - - - GPS ± {Math.floor(location?.coords.accuracy || 0)} - - - ) : ( - <> - - No GPS - - )} - - - ); -}; - -const styles = StyleSheet.create({ - indicatorWrapper: { - backgroundColor: '#333333', - borderRadius: 20, - padding: 14.5, - }, - wrapper: {flexDirection: 'row', alignItems: 'center'}, - text: {marginLeft: 5, color: '#fff', fontSize: 15}, -}); diff --git a/src/frontend/screens/MapScreen/index.tsx b/src/frontend/screens/MapScreen/index.tsx index b36736c4c..e6e8b956a 100644 --- a/src/frontend/screens/MapScreen/index.tsx +++ b/src/frontend/screens/MapScreen/index.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import Mapbox, {UserLocation} from '@rnmapbox/maps'; +import Mapbox from '@rnmapbox/maps'; import config from '../../../config.json'; import {IconButton} from '../../sharedComponents/IconButton'; import { @@ -15,13 +15,11 @@ import {useDraftObservation} from '../../hooks/useDraftObservation'; // @ts-ignore import ScaleBar from 'react-native-scale-bar'; import {getCoords, useLocation} from '../../hooks/useLocation'; -import {useIsFullyFocused} from '../../hooks/useIsFullyFocused'; import {useLastKnownLocation} from '../../hooks/useLastSavedLocation'; import {useLocationProviderStatus} from '../../hooks/useLocationProviderStatus'; import {GPSModal} from './gps/GPSModal'; import {TrackPathLayer} from './track/TrackPathLayer'; -import {useTracking} from '../../hooks/tracks/useTracking'; -import {UserTooltipMarker} from './track/UserTooltipMarker'; +import {UserLocation} from './UserLocation'; // This is the default zoom used when the map first loads, and also the zoom // that the map will zoom to if the user clicks the "Locate" button and the @@ -36,7 +34,6 @@ export const MAP_STYLE = Mapbox.StyleURL.Outdoors; export const MapScreen = () => { const [zoom, setZoom] = React.useState(DEFAULT_ZOOM); - const isFocused = useIsFullyFocused(); const [isFinishedLoading, setIsFinishedLoading] = React.useState(false); const [following, setFollowing] = React.useState(true); const {newDraft} = useDraftObservation(); @@ -48,8 +45,6 @@ export const MapScreen = () => { const locationServicesEnabled = !!locationProviderStatus?.locationServicesEnabled; - const {isTracking} = useTracking(); - const handleAddPress = () => { newDraft(); navigate('PresetChooser'); @@ -104,14 +99,8 @@ export const MapScreen = () => { followUserLocation={false} /> - {coords !== undefined && locationServicesEnabled && ( - <> - - {isTracking && } - + {coords && locationServicesEnabled && ( + )} {isFinishedLoading && } {isFinishedLoading && } @@ -121,7 +110,7 @@ export const MapScreen = () => { latitude={coords ? coords[1] : undefined} bottom={20} /> - {coords !== undefined && locationServicesEnabled && ( + {coords && locationServicesEnabled && ( {following ? : } From 915f28ec43623712c389ab05085d1cb810761213 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20Stefa=C5=84czyk?= Date: Tue, 16 Apr 2024 13:35:25 +0200 Subject: [PATCH 044/123] add comment explaining distance calc algo --- src/frontend/utils/distance.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/frontend/utils/distance.ts b/src/frontend/utils/distance.ts index b103f681a..fc8bda85c 100644 --- a/src/frontend/utils/distance.ts +++ b/src/frontend/utils/distance.ts @@ -1,20 +1,17 @@ -import {LonLatData} from '../hooks/tracks/useCurrentTrackStore'; +import {LonLatData} from '../sharedTypes/location'; const EARTH_RADIUS = 6371; // Radius of the earth in km const degreesToRadians = (degrees: number): number => degrees * (Math.PI / 180); +// Based on https://en.wikipedia.org/wiki/Haversine_formula export const calculateTotalDistance = (points: LonLatData[]): number => points.reduce((previousValue, currentValue, i, arr) => { if (i === 0) { return previousValue; } - const pointA = arr[i - 1]; - if (!pointA) { - throw Error('No point A for i=' + i); - } - + const pointA = arr[i - 1]!!; const pointB = currentValue; const dLat = degreesToRadians(pointB.latitude - pointA.latitude); From 56a0d182002df4384cc51c5e7a3742a64c68533d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20Stefa=C5=84czyk?= Date: Tue, 16 Apr 2024 14:03:34 +0200 Subject: [PATCH 045/123] correct import --- src/frontend/screens/MapScreen/track/TrackPathLayer.tsx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/frontend/screens/MapScreen/track/TrackPathLayer.tsx b/src/frontend/screens/MapScreen/track/TrackPathLayer.tsx index 88edfa489..da9650783 100644 --- a/src/frontend/screens/MapScreen/track/TrackPathLayer.tsx +++ b/src/frontend/screens/MapScreen/track/TrackPathLayer.tsx @@ -1,13 +1,10 @@ import {LineJoin, LineLayer, ShapeSource} from '@rnmapbox/maps'; -import { - FullLocationData, - LocationHistoryPoint, - useCurrentTrackStore, -} from '../../../hooks/tracks/useCurrentTrackStore'; +import {useCurrentTrackStore} from '../../../hooks/tracks/useCurrentTrackStore'; import * as React from 'react'; import {StyleSheet} from 'react-native'; import {LineString} from 'geojson'; import {useLocation} from '../../../hooks/useLocation'; +import {LocationHistoryPoint} from '../../../sharedTypes/location'; export const TrackPathLayer = () => { const locationHistory = useCurrentTrackStore(state => state.locationHistory); From 465ea18ee3ff68e04edd75cb2757abda76b9bc4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20Stefa=C5=84czyk?= Date: Tue, 16 Apr 2024 14:32:55 +0200 Subject: [PATCH 046/123] use luxon for duration formatting --- package-lock.json | 15 ++++++++++++++ package.json | 2 ++ src/frontend/hooks/useFormattedTimeSince.ts | 23 +++------------------ 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/package-lock.json b/package-lock.json index 62e10c68f..71d584c8f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ "@react-navigation/native-stack": "^6.9.13", "@rnmapbox/maps": "^10.1.16", "@tanstack/react-query": "^5.12.2", + "@types/luxon": "^3.4.2", "assert": "^2.0.0", "buffer": "^6.0.3", "cheap-ruler": "^3.0.2", @@ -39,6 +40,7 @@ "expo-task-manager": "~11.7.2", "geojson": "^0.5.0", "lodash.isequal": "^4.5.0", + "luxon": "^3.4.4", "nanoid": "^5.0.1", "nodejs-mobile-react-native": "^18.17.7", "react": "18.2.0", @@ -8815,6 +8817,11 @@ "@types/lodash": "*" } }, + "node_modules/@types/luxon": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.4.2.tgz", + "integrity": "sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==" + }, "node_modules/@types/ms": { "version": "0.7.31", "dev": true, @@ -17304,6 +17311,14 @@ "version": "2.3.9", "license": "MIT" }, + "node_modules/luxon": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz", + "integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==", + "engines": { + "node": ">=12" + } + }, "node_modules/magic-bytes.js": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.10.0.tgz", diff --git a/package.json b/package.json index 53479c7f7..d1aeb1b87 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "@react-navigation/native-stack": "^6.9.13", "@rnmapbox/maps": "^10.1.16", "@tanstack/react-query": "^5.12.2", + "@types/luxon": "^3.4.2", "assert": "^2.0.0", "buffer": "^6.0.3", "cheap-ruler": "^3.0.2", @@ -52,6 +53,7 @@ "expo-task-manager": "~11.7.2", "geojson": "^0.5.0", "lodash.isequal": "^4.5.0", + "luxon": "^3.4.4", "nanoid": "^5.0.1", "nodejs-mobile-react-native": "^18.17.7", "react": "18.2.0", diff --git a/src/frontend/hooks/useFormattedTimeSince.ts b/src/frontend/hooks/useFormattedTimeSince.ts index ec898343c..8777c608c 100644 --- a/src/frontend/hooks/useFormattedTimeSince.ts +++ b/src/frontend/hooks/useFormattedTimeSince.ts @@ -1,4 +1,5 @@ import {useEffect, useState} from 'react'; +import {Duration} from 'luxon'; export const useFormattedTimeSince = (start: Date, interval: number) => { const [currentTime, setCurrentTime] = useState(new Date()); @@ -10,24 +11,6 @@ export const useFormattedTimeSince = (start: Date, interval: number) => { return () => clearInterval(timer); }, [interval]); - return secondsToHMS( - Math.floor((currentTime.getTime() - start.getTime()) / 1000), - ); -}; - -const secondsToHMS = (secs: number) => { - function z(n: number) { - return (n < 10 ? '0' : '') + n; - } - var sign = secs < 0 ? '-' : ''; - secs = Math.abs(secs); - /* eslint-disable no-bitwise */ - return ( - sign + - z((secs / 3600) | 0) + - ':' + - z(((secs % 3600) / 60) | 0) + - ':' + - z(secs % 60) - ); + const millisPassed = currentTime.getTime() - start.getTime(); + return Duration.fromMillis(millisPassed).toFormat('hh:mm:ss'); }; From eb40ca002cd6bf5358640b3d9ad32cf7382df276 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20Stefa=C5=84czyk?= Date: Tue, 16 Apr 2024 14:35:54 +0200 Subject: [PATCH 047/123] fix eslint rule --- .eslintrc.js | 1 + 1 file changed, 1 insertion(+) diff --git a/.eslintrc.js b/.eslintrc.js index 06ac355c3..dd3753ed7 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -18,6 +18,7 @@ module.exports = { 'no-undef': 'off', 'react-native/no-inline-styles': 'off', 'react/no-unstable-nested-components': [ + 'warn', { allowAsProps: true, }, From d15f71937c35efbd9b03ab09004dbc9da2a86583 Mon Sep 17 00:00:00 2001 From: bohdanprog Date: Tue, 16 Apr 2024 14:59:05 +0200 Subject: [PATCH 048/123] add translations to gps modal --- .../Navigation/ScreenGroups/AppScreens.tsx | 8 ++--- src/frontend/hooks/useCurrentTab.ts | 2 +- .../screens/MapScreen/gps/GPSDisabled.tsx | 26 ++++++++++++-- .../screens/MapScreen/gps/GPSEnabled.tsx | 35 ++++++++++++++++--- 4 files changed, 56 insertions(+), 15 deletions(-) diff --git a/src/frontend/Navigation/ScreenGroups/AppScreens.tsx b/src/frontend/Navigation/ScreenGroups/AppScreens.tsx index 5acd67d82..6c8f93a40 100644 --- a/src/frontend/Navigation/ScreenGroups/AppScreens.tsx +++ b/src/frontend/Navigation/ScreenGroups/AppScreens.tsx @@ -180,7 +180,7 @@ const HomeTabs = () => { component={MapScreen} options={{ tabBarIcon: params => ( - + ), }} /> @@ -189,11 +189,7 @@ const HomeTabs = () => { component={CameraScreen} options={{ tabBarIcon: params => ( - + ), }} /> diff --git a/src/frontend/hooks/useCurrentTab.ts b/src/frontend/hooks/useCurrentTab.ts index 385ae53cd..545d3923e 100644 --- a/src/frontend/hooks/useCurrentTab.ts +++ b/src/frontend/hooks/useCurrentTab.ts @@ -5,8 +5,8 @@ import { getFocusedRouteNameFromRoute, } from '@react-navigation/native'; import {useGPSModalContext} from '../contexts/GPSModalContext'; -import {TabName} from '../Navigation/ScreenGroups/AppScreens'; import {useNavigationStore} from './useNavigationStore'; +import {TabName} from '../Navigation/types'; export const useCurrentTab = () => { const {setCurrentTab} = useNavigationStore(); diff --git a/src/frontend/screens/MapScreen/gps/GPSDisabled.tsx b/src/frontend/screens/MapScreen/gps/GPSDisabled.tsx index 848b1d1ac..7e279900e 100644 --- a/src/frontend/screens/MapScreen/gps/GPSDisabled.tsx +++ b/src/frontend/screens/MapScreen/gps/GPSDisabled.tsx @@ -3,15 +3,33 @@ import {Image, Linking, StyleSheet, View} from 'react-native'; import {Button} from '../../../sharedComponents/Button'; import {Text} from '../../../sharedComponents/Text'; import * as Location from 'expo-location'; +import {defineMessages, useIntl} from 'react-intl'; const handleOpenSettings = () => { Linking.sendIntent('android.settings.LOCATION_SOURCE_SETTINGS'); }; +const m = defineMessages({ + gpsDisabledTitle: { + id: 'Modal.GPSDisable.title', + defaultMessage: 'GPS Disabled', + }, + gpsDisabledDescription: { + id: 'Modal.GPSDisable.description', + defaultMessage: + 'To create a Track CoMapeo needs access to your location and GPS.', + }, + gpsDisabledButtonText: { + id: 'Modal.GPSDisable.button', + defaultMessage: 'Enable', + }, +}); + interface GPSDisabled { setIsGranted: React.Dispatch>; } export const GPSDisabled: React.FC = ({setIsGranted}) => { + const {formatMessage} = useIntl(); const requestForLocationPermissions = async () => { const [foregroundPermission, backgroundPermission] = await Promise.all([ Location.requestForegroundPermissionsAsync(), @@ -36,15 +54,17 @@ export const GPSDisabled: React.FC = ({setIsGranted}) => { style={styles.image} /> - GPS Disabled + {formatMessage(m.gpsDisabledTitle)} - To create a Track CoMapeo needs access to your location and GPS. + {formatMessage(m.gpsDisabledDescription)} ); diff --git a/src/frontend/screens/MapScreen/gps/GPSEnabled.tsx b/src/frontend/screens/MapScreen/gps/GPSEnabled.tsx index d55c052fc..b7a665f84 100644 --- a/src/frontend/screens/MapScreen/gps/GPSEnabled.tsx +++ b/src/frontend/screens/MapScreen/gps/GPSEnabled.tsx @@ -6,8 +6,29 @@ import {useTracking} from '../../../hooks/tracks/useTracking'; import StartTrackingIcon from '../../../images/StartTracking.svg'; import StopTrackingIcon from '../../../images/StopTracking.svg'; import {useTrackTimerContext} from '../../../contexts/TrackTimerContext'; +import {defineMessages, useIntl} from 'react-intl'; + +const m = defineMessages({ + defaultButtonText: { + id: 'Modal.GPSEnable.button.default', + defaultMessage: 'Start Tracks', + }, + stopButtonText: { + id: 'Modal.GPSEnable.button.stop', + defaultMessage: 'Stop Tracks', + }, + loadingButtonText: { + id: 'Modal.GPSEnable.button.loading', + defaultMessage: 'Loading...', + }, + trackingDescription: { + id: 'Modal.GPSEnable.trackingDescription', + defaultMessage: 'You’ve been recording for', + }, +}); export const GPSEnabled = () => { + const {formatMessage} = useIntl(); const {isTracking, cancelTracking, startTracking, loading} = useTracking(); const {timer} = useTrackTimerContext(); @@ -18,9 +39,9 @@ export const GPSEnabled = () => { }, [cancelTracking, isTracking, startTracking]); const getButtonTitle = () => { - if (loading) return 'Loading...'; - if (isTracking) return 'Stop Tracks'; - return 'Start Tracks'; + if (loading) return m.loadingButtonText; + if (isTracking) return m.stopButtonText; + return m.defaultButtonText; }; return ( @@ -32,13 +53,17 @@ export const GPSEnabled = () => { style={styles.button}> {isTracking ? : } - {getButtonTitle()} + + {formatMessage(getButtonTitle())} + {isTracking && ( - You’ve been recording for + + {formatMessage(m.trackingDescription)} + {timer} )} From 815c42cea39245a54c09b83ace58504803cc914a Mon Sep 17 00:00:00 2001 From: bohdanprog Date: Tue, 16 Apr 2024 14:59:47 +0200 Subject: [PATCH 049/123] add changes to en.json --- messages/en.json | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/messages/en.json b/messages/en.json index 8c9e85e20..8370c1364 100644 --- a/messages/en.json +++ b/messages/en.json @@ -19,6 +19,27 @@ "description": "Title of dialog that shows when cancelling a new observation", "message": "Discard observation?" }, + "Modal.GPSDisable.button": { + "message": "Enable" + }, + "Modal.GPSDisable.description": { + "message": "To create a Track CoMapeo needs access to your location and GPS." + }, + "Modal.GPSDisable.title": { + "message": "GPS Disabled" + }, + "Modal.GPSEnable.button.default": { + "message": "Start Tracks" + }, + "Modal.GPSEnable.button.loading": { + "message": "Loading..." + }, + "Modal.GPSEnable.button.stop": { + "message": "Stop Tracks" + }, + "Modal.GPSEnable.trackingDescription": { + "message": "You’ve been recording for" + }, "Screens.Settings.AppSettings.coordinateSystem": { "message": "Coordinate System" }, From d36f508194d6b2850ae0fd62f4fb38725d91d64b Mon Sep 17 00:00:00 2001 From: bohdanprog Date: Tue, 16 Apr 2024 16:33:53 +0200 Subject: [PATCH 050/123] changed GPS pill --- .../Navigation/ScreenGroups/AppScreens.tsx | 32 ++------ src/frontend/images/ActiveGPSSignal.svg | 9 --- src/frontend/images/NoGPSSignal.svg | 3 - .../screens/MapScreen/ObsevationMapLayer.tsx | 1 - .../screens/MapScreen/gps/GPSPill.tsx | 78 +++++++++++++++++++ src/frontend/sharedComponents/GpsPill.tsx | 76 ------------------ 6 files changed, 84 insertions(+), 115 deletions(-) delete mode 100644 src/frontend/images/ActiveGPSSignal.svg delete mode 100644 src/frontend/images/NoGPSSignal.svg create mode 100644 src/frontend/screens/MapScreen/gps/GPSPill.tsx delete mode 100644 src/frontend/sharedComponents/GpsPill.tsx diff --git a/src/frontend/Navigation/ScreenGroups/AppScreens.tsx b/src/frontend/Navigation/ScreenGroups/AppScreens.tsx index 6c8f93a40..4c755c04e 100644 --- a/src/frontend/Navigation/ScreenGroups/AppScreens.tsx +++ b/src/frontend/Navigation/ScreenGroups/AppScreens.tsx @@ -44,10 +44,6 @@ import { createNavigationOptions as createDeviceNameEditNavOptions, } from '../../screens/Settings/ProjectSettings/DeviceName/EditScreen'; import {TabBarIcon} from './TabBar/TabBarIcon'; -import {useLocation} from '../../hooks/useLocation'; -import {useForegroundPermissions} from 'expo-location'; -import {useLocationProviderStatus} from '../../hooks/useLocationProviderStatus'; -import {getLocationStatus} from '../../lib/utils'; import { GpsModal, createNavigationOptions as createGpsModalNavigationOptions, @@ -140,19 +136,6 @@ const Tab = createBottomTabNavigator(); const HomeTabs = () => { const {handleTabPress} = useCurrentTab(); - const locationState = useLocation({maxDistanceInterval: 0}); - const [permissions] = useForegroundPermissions(); - const locationProviderStatus = useLocationProviderStatus(); - - const precision = locationState.location?.coords.accuracy; - - const locationStatus = - !!locationState.error || !permissions?.granted - ? 'error' - : getLocationStatus({ - location: locationState.location, - providerStatus: locationProviderStatus, - }); return ( { height: TAB_BAR_HEIGHT, }, tabBarShowLabel: false, - header: () => ( - - ), headerTransparent: true, tabBarTestID: 'tabBarButton' + route.name, })} @@ -179,6 +154,7 @@ const HomeTabs = () => { name="Map" component={MapScreen} options={{ + header: () => , tabBarIcon: params => ( ), @@ -188,6 +164,7 @@ const HomeTabs = () => { name="Camera" component={CameraScreen} options={{ + headerShown: false, tabBarIcon: params => ( ), @@ -195,7 +172,10 @@ const HomeTabs = () => { /> <>} /> diff --git a/src/frontend/images/ActiveGPSSignal.svg b/src/frontend/images/ActiveGPSSignal.svg deleted file mode 100644 index 940d69e45..000000000 --- a/src/frontend/images/ActiveGPSSignal.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/src/frontend/images/NoGPSSignal.svg b/src/frontend/images/NoGPSSignal.svg deleted file mode 100644 index 13bbf016e..000000000 --- a/src/frontend/images/NoGPSSignal.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/frontend/screens/MapScreen/ObsevationMapLayer.tsx b/src/frontend/screens/MapScreen/ObsevationMapLayer.tsx index dc6ae5c78..1d5e9bf72 100644 --- a/src/frontend/screens/MapScreen/ObsevationMapLayer.tsx +++ b/src/frontend/screens/MapScreen/ObsevationMapLayer.tsx @@ -4,7 +4,6 @@ import MapboxGL from '@rnmapbox/maps'; import {useAllObservations} from '../../hooks/useAllObservations'; import {useNavigationFromHomeTabs} from '../../hooks/useNavigationWithTypes'; import {OnPressEvent} from '@rnmapbox/maps/lib/typescript/src/types/OnPressEvent'; -import {useTracking} from '../../hooks/tracks/useTracking'; import {useCurrentTrackStore} from '../../hooks/tracks/useCurrentTrackStore'; const DEFAULT_MARKER_COLOR = '#F29D4B'; diff --git a/src/frontend/screens/MapScreen/gps/GPSPill.tsx b/src/frontend/screens/MapScreen/gps/GPSPill.tsx new file mode 100644 index 000000000..bd272aeee --- /dev/null +++ b/src/frontend/screens/MapScreen/gps/GPSPill.tsx @@ -0,0 +1,78 @@ +import React, {useMemo} from 'react'; +import {StyleSheet, TouchableOpacity, View} from 'react-native'; +import {Text} from '../../../sharedComponents/Text'; +import * as Location from 'expo-location'; +import {useLocation} from '../../../hooks/useLocation'; +import {useNavigationFromHomeTabs} from '../../../hooks/useNavigationWithTypes'; +import {useIsFocused} from '@react-navigation/native'; +import {useLocationProviderStatus} from '../../../hooks/useLocationProviderStatus'; +import {getLocationStatus} from '../../../lib/utils'; +import {defineMessages, useIntl} from 'react-intl'; +import {GpsIcon} from '../../../sharedComponents/icons'; + +const m = defineMessages({ + noGps: { + id: 'sharedComponents.GpsPill.noGps', + defaultMessage: 'No GPS', + }, + searching: { + id: 'sharedComponents.GpsPill.searching', + defaultMessage: 'Searching…', + }, +}); + +export const GPSPill = () => { + const isFocused = useIsFocused(); + const {formatMessage: t} = useIntl(); + const locationState = useLocation({maxDistanceInterval: 0}); + const [permissions] = Location.useForegroundPermissions(); + const locationProviderStatus = useLocationProviderStatus(); + + const precision = locationState.location?.coords.accuracy; + + const status = useMemo(() => { + const isError = !!locationState.error || !permissions?.granted; + + return isError + ? 'error' + : getLocationStatus({ + location: locationState.location, + providerStatus: locationProviderStatus, + }); + }, [ + locationProviderStatus, + locationState.error, + locationState.location, + permissions?.granted, + ]); + + const text = useMemo(() => { + if (status === 'error') return t(m.noGps); + else if (status === 'searching' || typeof precision === 'undefined') { + return t(m.searching); + } else return `± ${Math.round(precision!)} m`; + }, [precision, status, t]); + + const navigation = useNavigationFromHomeTabs(); + + return ( + navigation.navigate('GpsModal')}> + + + {isFocused && } + {text} + + + + ); +}; + +const styles = StyleSheet.create({ + indicatorWrapper: { + backgroundColor: '#333333', + borderRadius: 20, + padding: 14.5, + }, + wrapper: {flexDirection: 'row', alignItems: 'center'}, + text: {marginLeft: 5, color: '#fff', fontSize: 15}, +}); diff --git a/src/frontend/sharedComponents/GpsPill.tsx b/src/frontend/sharedComponents/GpsPill.tsx deleted file mode 100644 index 0b35f973f..000000000 --- a/src/frontend/sharedComponents/GpsPill.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import * as React from 'react'; -import {View, StyleSheet} from 'react-native'; -import {defineMessages, useIntl} from 'react-intl'; -import {useIsFocused} from '@react-navigation/native'; -import {TouchableOpacity} from 'react-native-gesture-handler'; - -import {BLACK, WHITE} from '../lib/styles'; -import type {LocationStatus} from '../lib/utils'; -import {Text} from './Text'; -import {GpsIcon} from './icons'; - -const m = defineMessages({ - noGps: { - id: 'sharedComponents.GpsPill.noGps', - defaultMessage: 'No GPS', - }, - searching: { - id: 'sharedComponents.GpsPill.searching', - defaultMessage: 'Searching…', - }, -}); - -interface Props { - onPress?: () => void; - precision?: number; - variant: LocationStatus; -} - -export const GpsPill = React.memo( - ({onPress, variant, precision}: Props) => { - const isFocused = useIsFocused(); - const {formatMessage: t} = useIntl(); - let text: string; - if (variant === 'error') text = t(m.noGps); - else if (variant === 'searching' || typeof precision === 'undefined') - text = t(m.searching); - else text = `± ${precision} m`; - return ( - - - - {isFocused && } - - - {text} - - - - ); - }, -); - -const styles = StyleSheet.create({ - container: { - flex: 0, - minWidth: 100, - maxWidth: 200, - borderRadius: 18, - height: 36, - paddingLeft: 32, - paddingRight: 20, - borderWidth: 3, - borderColor: '#33333366', - backgroundColor: BLACK, - justifyContent: 'center', - alignItems: 'center', - flexDirection: 'row', - }, - error: {backgroundColor: '#FF0000'}, - text: {color: WHITE}, - icon: {position: 'absolute', left: 6}, -}); From ed9ce3aad22740ecd73d7fd4b55e2e0eb530e338 Mon Sep 17 00:00:00 2001 From: bohdanprog Date: Tue, 16 Apr 2024 16:34:44 +0200 Subject: [PATCH 051/123] add enum type with tab names,changes check in navigation listener --- .../Navigation/ScreenGroups/AppScreens.tsx | 9 +++++++-- .../ScreenGroups/TabBar/TrackingTabBarIcon.tsx | 8 ++++++-- src/frontend/Navigation/types.ts | 8 +++++--- src/frontend/hooks/useCurrentTab.ts | 15 +++------------ src/frontend/sharedComponents/HomeHeader.tsx | 18 +++--------------- 5 files changed, 24 insertions(+), 34 deletions(-) diff --git a/src/frontend/Navigation/ScreenGroups/AppScreens.tsx b/src/frontend/Navigation/ScreenGroups/AppScreens.tsx index 4c755c04e..d674c12c6 100644 --- a/src/frontend/Navigation/ScreenGroups/AppScreens.tsx +++ b/src/frontend/Navigation/ScreenGroups/AppScreens.tsx @@ -50,6 +50,7 @@ import { } from '../../screens/GpsModal'; import {useCurrentTab} from '../../hooks/useCurrentTab'; import {TrackingTabBarIcon} from './TabBar/TrackingTabBarIcon'; +import {TabName} from '../types'; export const TAB_BAR_HEIGHT = 70; @@ -156,7 +157,7 @@ const HomeTabs = () => { options={{ header: () => , tabBarIcon: params => ( - + ), }} /> @@ -166,7 +167,11 @@ const HomeTabs = () => { options={{ headerShown: false, tabBarIcon: params => ( - + ), }} /> diff --git a/src/frontend/Navigation/ScreenGroups/TabBar/TrackingTabBarIcon.tsx b/src/frontend/Navigation/ScreenGroups/TabBar/TrackingTabBarIcon.tsx index 3bd53af26..a981ff268 100644 --- a/src/frontend/Navigation/ScreenGroups/TabBar/TrackingTabBarIcon.tsx +++ b/src/frontend/Navigation/ScreenGroups/TabBar/TrackingTabBarIcon.tsx @@ -3,7 +3,7 @@ import {StyleSheet, View} from 'react-native'; import {TabBarIcon} from './TabBarIcon'; import {useTracking} from '../../../hooks/tracks/useTracking'; import {Text} from '../../../sharedComponents/Text'; -import {TabBarIconProps} from '../../types'; +import {TabBarIconProps, TabName} from '../../types'; import {useTrackTimerContext} from '../../../contexts/TrackTimerContext'; export const TrackingTabBarIcon: FC = props => { @@ -18,7 +18,11 @@ export const TrackingTabBarIcon: FC = props => { {timer} )} - + ); }; diff --git a/src/frontend/Navigation/types.ts b/src/frontend/Navigation/types.ts index 5df57c839..581d18cb6 100644 --- a/src/frontend/Navigation/types.ts +++ b/src/frontend/Navigation/types.ts @@ -1,9 +1,11 @@ -import {HomeTabsList} from './ScreenGroups/AppScreens'; - export interface TabBarIconProps { size: number; focused: boolean; color: string; } -export type TabName = keyof HomeTabsList; +export enum TabName { + Map = 'Map', + Camera = 'Camera', + Tracking = 'Tracking', +} diff --git a/src/frontend/hooks/useCurrentTab.ts b/src/frontend/hooks/useCurrentTab.ts index 545d3923e..b522e0b2d 100644 --- a/src/frontend/hooks/useCurrentTab.ts +++ b/src/frontend/hooks/useCurrentTab.ts @@ -1,9 +1,4 @@ -import { - useNavigation, - useRoute, - EventArg, - getFocusedRouteNameFromRoute, -} from '@react-navigation/native'; +import {useNavigation, EventArg} from '@react-navigation/native'; import {useGPSModalContext} from '../contexts/GPSModalContext'; import {useNavigationStore} from './useNavigationStore'; import {TabName} from '../Navigation/types'; @@ -11,7 +6,6 @@ import {TabName} from '../Navigation/types'; export const useCurrentTab = () => { const {setCurrentTab} = useNavigationStore(); const navigation = useNavigation(); - const route = useRoute(); const {bottomSheetRef} = useGPSModalContext(); const handleTabPress = ({ @@ -19,16 +13,13 @@ export const useCurrentTab = () => { preventDefault, }: EventArg<'tabPress', true, undefined>) => { const targetTab = target?.split('-')[0]; - if (targetTab === 'Tracking') { + if (targetTab === TabName.Tracking) { preventDefault(); bottomSheetRef.current?.present(); + navigation.navigate('Map' as never); } else { bottomSheetRef.current?.close(); } - const currentTab = getFocusedRouteNameFromRoute(route); - if (currentTab === 'Camera') { - navigation.navigate('Map' as never); - } setCurrentTab((target?.split('-')[0] || 'Map') as unknown as TabName); }; diff --git a/src/frontend/sharedComponents/HomeHeader.tsx b/src/frontend/sharedComponents/HomeHeader.tsx index 9cb57f217..4a6ba97de 100644 --- a/src/frontend/sharedComponents/HomeHeader.tsx +++ b/src/frontend/sharedComponents/HomeHeader.tsx @@ -4,15 +4,9 @@ import LinearGradient from 'react-native-linear-gradient'; import {IconButton} from './IconButton'; import {ObservationListIcon} from './icons'; import {useNavigationFromHomeTabs} from '../hooks/useNavigationWithTypes'; -import {GpsPill} from './GpsPill'; -import {LocationStatus} from '../lib/utils'; +import {GPSPill} from '../screens/MapScreen/gps/GPSPill'; -interface Props { - locationStatus: LocationStatus; - precision?: number; -} - -export const HomeHeader = ({locationStatus, precision}: Props) => { +export const HomeHeader = () => { const navigation = useNavigationFromHomeTabs(); return ( @@ -22,13 +16,7 @@ export const HomeHeader = ({locationStatus, precision}: Props) => { colors={['#0006', '#0000']} /> {/* Placeholder for left button */} - { - navigation.navigate('GpsModal'); - }} - /> + { navigation.navigate('ObservationList'); From 312bf28d4623e9ec0debf18381aa49b0d63b03fe Mon Sep 17 00:00:00 2001 From: bohdanprog Date: Wed, 17 Apr 2024 09:02:26 +0200 Subject: [PATCH 052/123] set default route name to navigation store, use enum names in tabs name --- .eslintrc.js | 6 ---- .../Navigation/ScreenGroups/AppScreens.tsx | 29 +++++++------------ .../ScreenGroups/TabBar/CameraTabBarIcon.tsx | 9 ++++++ .../ScreenGroups/TabBar/MapTabBarIcon.tsx | 7 +++++ src/frontend/hooks/useNavigationStore.ts | 8 ++--- 5 files changed, 30 insertions(+), 29 deletions(-) create mode 100644 src/frontend/Navigation/ScreenGroups/TabBar/CameraTabBarIcon.tsx create mode 100644 src/frontend/Navigation/ScreenGroups/TabBar/MapTabBarIcon.tsx diff --git a/.eslintrc.js b/.eslintrc.js index dd3753ed7..e2064424b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -17,12 +17,6 @@ module.exports = { 'no-shadow': 'off', 'no-undef': 'off', 'react-native/no-inline-styles': 'off', - 'react/no-unstable-nested-components': [ - 'warn', - { - allowAsProps: true, - }, - ], }, }, ], diff --git a/src/frontend/Navigation/ScreenGroups/AppScreens.tsx b/src/frontend/Navigation/ScreenGroups/AppScreens.tsx index d674c12c6..12a6d6563 100644 --- a/src/frontend/Navigation/ScreenGroups/AppScreens.tsx +++ b/src/frontend/Navigation/ScreenGroups/AppScreens.tsx @@ -43,7 +43,6 @@ import { EditScreen as DeviceNameEditScreen, createNavigationOptions as createDeviceNameEditNavOptions, } from '../../screens/Settings/ProjectSettings/DeviceName/EditScreen'; -import {TabBarIcon} from './TabBar/TabBarIcon'; import { GpsModal, createNavigationOptions as createGpsModalNavigationOptions, @@ -51,6 +50,8 @@ import { import {useCurrentTab} from '../../hooks/useCurrentTab'; import {TrackingTabBarIcon} from './TabBar/TrackingTabBarIcon'; import {TabName} from '../types'; +import {CameraTabBarIcon} from './TabBar/CameraTabBarIcon'; +import {MapTabBarIcon} from './TabBar/MapTabBarIcon'; export const TAB_BAR_HEIGHT = 70; @@ -142,41 +143,31 @@ const HomeTabs = () => { ({ - tabBarStyle: { - height: TAB_BAR_HEIGHT, - }, + tabBarStyle: {height: TAB_BAR_HEIGHT}, tabBarShowLabel: false, headerTransparent: true, tabBarTestID: 'tabBarButton' + route.name, })} - initialRouteName="Map" + initialRouteName={TabName.Map} backBehavior="initialRoute"> , - tabBarIcon: params => ( - - ), + header: HomeHeader, + tabBarIcon: MapTabBarIcon, }} /> ( - - ), + tabBarIcon: CameraTabBarIcon, }} /> = props => { + return ( + + ); +}; diff --git a/src/frontend/Navigation/ScreenGroups/TabBar/MapTabBarIcon.tsx b/src/frontend/Navigation/ScreenGroups/TabBar/MapTabBarIcon.tsx new file mode 100644 index 000000000..0025dabe4 --- /dev/null +++ b/src/frontend/Navigation/ScreenGroups/TabBar/MapTabBarIcon.tsx @@ -0,0 +1,7 @@ +import React, {FC} from 'react'; +import {TabBarIconProps, TabName} from '../../types'; +import {TabBarIcon} from './TabBarIcon'; + +export const MapTabBarIcon: FC = props => { + return ; +}; diff --git a/src/frontend/hooks/useNavigationStore.ts b/src/frontend/hooks/useNavigationStore.ts index 840eb1d02..170eb023c 100644 --- a/src/frontend/hooks/useNavigationStore.ts +++ b/src/frontend/hooks/useNavigationStore.ts @@ -1,14 +1,14 @@ import {create} from 'zustand'; -import {HomeTabsList} from '../Navigation/ScreenGroups/AppScreens'; - -type TabName = keyof HomeTabsList; +import {TabName} from '../Navigation/types'; type NavigationStoreState = { currentTab: TabName; + initialRouteName: TabName.Map; setCurrentTab: (tab: TabName) => void; }; export const useNavigationStore = create(set => ({ - currentTab: 'Map' as TabName, + initialRouteName: TabName.Map, + currentTab: TabName.Map, setCurrentTab: (tab: TabName) => set(() => ({currentTab: tab})), })); From 03d09339a10fc0a76b2152a458de9a5b62e3ec7a Mon Sep 17 00:00:00 2001 From: bohdanprog Date: Wed, 17 Apr 2024 10:36:04 +0200 Subject: [PATCH 053/123] add test case for calculate distance function, rename varialbes --- src/frontend/utils/distance.test.ts | 54 ++++++++++++++++++++++++++++ src/frontend/utils/distance.ts | 55 +++++++++++++++++++++++------ 2 files changed, 98 insertions(+), 11 deletions(-) create mode 100644 src/frontend/utils/distance.test.ts diff --git a/src/frontend/utils/distance.test.ts b/src/frontend/utils/distance.test.ts new file mode 100644 index 000000000..199842a97 --- /dev/null +++ b/src/frontend/utils/distance.test.ts @@ -0,0 +1,54 @@ +import {calculateTotalDistance} from './distance'; + +describe('calculateTotalDistance', () => { + it('calculateTotalDistance between two different points Warsaw - Cracow', () => { + const distance = 251.98; + const listOfPoints = [ + {latitude: 52.229675, longitude: 21.01223}, + {latitude: 50.064651, longitude: 19.944981}, + ]; + + expect(Number(calculateTotalDistance(listOfPoints).toFixed(2))).toBe( + distance, + ); + }); + it('calculateTotalDistance between different points Cracow - Warsaw - Vienna', () => { + const distance = 807.54; + const listOfPoints = [ + {latitude: 50.064651, longitude: 19.944981}, + {latitude: 52.229675, longitude: 21.01223}, + {latitude: 48.2083537, longitude: 16.3725042}, + ]; + + expect(Number(calculateTotalDistance(listOfPoints).toFixed(2))).toBe( + distance, + ); + }); + it('calculateTotalDistance between different points Cracow - Warsaw - Vienna - Berlin', () => { + const distance = 1331.19; + const listOfPoints = [ + {latitude: 50.064651, longitude: 19.944981}, + {latitude: 52.229675, longitude: 21.01223}, + {latitude: 48.2083537, longitude: 16.3725042}, + {latitude: 52.523403, longitude: 13.4114}, + ]; + + expect(Number(calculateTotalDistance(listOfPoints).toFixed(2))).toBe( + distance, + ); + }); + it('calculateTotalDistance between different points Cracow - Warsaw - Vienna - Berlin - Amsterdam ', () => { + const distance = 1908.61; + const listOfPoints = [ + {latitude: 50.064651, longitude: 19.944981}, + {latitude: 52.229675, longitude: 21.01223}, + {latitude: 48.2083537, longitude: 16.3725042}, + {latitude: 52.523403, longitude: 13.4114}, + {latitude: 52.37403, longitude: 4.88969}, + ]; + + expect(Number(calculateTotalDistance(listOfPoints).toFixed(2))).toBe( + distance, + ); + }); +}); diff --git a/src/frontend/utils/distance.ts b/src/frontend/utils/distance.ts index fc8bda85c..36b8d6baf 100644 --- a/src/frontend/utils/distance.ts +++ b/src/frontend/utils/distance.ts @@ -2,7 +2,36 @@ import {LonLatData} from '../sharedTypes/location'; const EARTH_RADIUS = 6371; // Radius of the earth in km -const degreesToRadians = (degrees: number): number => degrees * (Math.PI / 180); +function degreesToRadians(degrees: number): number { + return degrees * (Math.PI / 180); +} + +function calculateHaversine( + deltaLatitude: number, + deltaLongitude: number, + pointA: LonLatData, + pointB: LonLatData, +): number { + const deltaLatitudeHalfSineSquared = Math.pow(Math.sin(deltaLatitude / 2), 2); + const pointALatitudeRadianCosine = Math.cos( + degreesToRadians(pointA.latitude), + ); + const pointBLatitudeRadianCosine = Math.cos( + degreesToRadians(pointB.latitude), + ); + const deltaLongitudeHalfSineSquared = Math.pow( + Math.sin(deltaLongitude / 2), + 2, + ); + + const cosineProduct = + pointALatitudeRadianCosine * + pointBLatitudeRadianCosine * + deltaLongitudeHalfSineSquared; + const haversine = deltaLatitudeHalfSineSquared + cosineProduct; + + return haversine; +} // Based on https://en.wikipedia.org/wiki/Haversine_formula export const calculateTotalDistance = (points: LonLatData[]): number => @@ -14,16 +43,20 @@ export const calculateTotalDistance = (points: LonLatData[]): number => const pointA = arr[i - 1]!!; const pointB = currentValue; - const dLat = degreesToRadians(pointB.latitude - pointA.latitude); - const dLon = degreesToRadians(pointB.longitude - pointA.longitude); + const deltaLatitude = degreesToRadians(pointB.latitude - pointA.latitude); + const deltaLongitude = degreesToRadians( + pointB.longitude - pointA.longitude, + ); + + const haversine = calculateHaversine( + deltaLatitude, + deltaLongitude, + pointA, + pointB, + ); - const a = - Math.sin(dLat / 2) * Math.sin(dLat / 2) + - Math.cos(degreesToRadians(pointA.latitude)) * - Math.cos(degreesToRadians(pointB.latitude)) * - Math.sin(dLon / 2) * - Math.sin(dLon / 2); - const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + const distanceFactor = + 2 * Math.atan2(Math.sqrt(haversine), Math.sqrt(1 - haversine)); - return previousValue + EARTH_RADIUS * c; + return previousValue + EARTH_RADIUS * distanceFactor; }, 0); From adba27845e6de88b7ca057fc0d5e575ceaf84f3f Mon Sep 17 00:00:00 2001 From: bohdanprog Date: Wed, 17 Apr 2024 11:03:54 +0200 Subject: [PATCH 054/123] changed gps pill padding --- src/frontend/screens/MapScreen/gps/GPSPill.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/frontend/screens/MapScreen/gps/GPSPill.tsx b/src/frontend/screens/MapScreen/gps/GPSPill.tsx index bd272aeee..3e5236dc7 100644 --- a/src/frontend/screens/MapScreen/gps/GPSPill.tsx +++ b/src/frontend/screens/MapScreen/gps/GPSPill.tsx @@ -71,7 +71,8 @@ const styles = StyleSheet.create({ indicatorWrapper: { backgroundColor: '#333333', borderRadius: 20, - padding: 14.5, + paddingVertical: 14, + paddingHorizontal: 10, }, wrapper: {flexDirection: 'row', alignItems: 'center'}, text: {marginLeft: 5, color: '#fff', fontSize: 15}, From 9d28264ea8237a6623b1e3a0b823fc8c34ef32f4 Mon Sep 17 00:00:00 2001 From: bohdanprog Date: Wed, 17 Apr 2024 11:06:14 +0200 Subject: [PATCH 055/123] using in setCurrentTab function enum --- src/frontend/screens/MapScreen/gps/GPSModal.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/frontend/screens/MapScreen/gps/GPSModal.tsx b/src/frontend/screens/MapScreen/gps/GPSModal.tsx index 636177cfe..34714b74d 100644 --- a/src/frontend/screens/MapScreen/gps/GPSModal.tsx +++ b/src/frontend/screens/MapScreen/gps/GPSModal.tsx @@ -5,6 +5,7 @@ import * as Location from 'expo-location'; import {useGPSModalContext} from '../../../contexts/GPSModalContext'; import {useNavigationStore} from '../../../hooks/useNavigationStore'; import {CustomBottomSheetModal} from '../../../sharedComponents/BottomSheetModal/CustomBottomSheetModal'; +import {TabName} from '../../../Navigation/types'; export const GPSModal = () => { const {setCurrentTab} = useNavigationStore(); @@ -22,7 +23,7 @@ export const GPSModal = () => { }, [backgroundStatus, foregroundStatus, isGranted]); const onBottomSheetDismiss = () => { - setCurrentTab('Map'); + setCurrentTab(TabName.Map); bottomSheetRef.current?.close(); }; From 20a32ba9b653eb1d7c4bc395045362a409c5e0a4 Mon Sep 17 00:00:00 2001 From: bohdanprog Date: Wed, 17 Apr 2024 14:36:56 +0200 Subject: [PATCH 056/123] create save track screen, create discard modal --- ios/Podfile | 2 +- src/frontend/Navigation/AppNavigator.tsx | 11 +- .../Navigation/ScreenGroups/AppScreens.tsx | 9 ++ src/frontend/hooks/tracks/useTracking.ts | 5 +- src/frontend/images/close.svg | 4 + .../MapScreen/track/SaveTrackScreen.tsx | 16 +++ .../track/saveTrack/DiscardTrackModal.tsx | 126 ++++++++++++++++++ .../track/saveTrack/SaveTrackHeader.tsx | 40 ++++++ 8 files changed, 206 insertions(+), 7 deletions(-) create mode 100644 src/frontend/images/close.svg create mode 100644 src/frontend/screens/MapScreen/track/SaveTrackScreen.tsx create mode 100644 src/frontend/screens/MapScreen/track/saveTrack/DiscardTrackModal.tsx create mode 100644 src/frontend/screens/MapScreen/track/saveTrack/SaveTrackHeader.tsx diff --git a/ios/Podfile b/ios/Podfile index 37c93bb72..7ae4a093b 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -7,7 +7,7 @@ require Pod::Executable.execute_command('node', ['-p', require File.join(File.dirname(`node --print "require.resolve('expo/package.json')"`), "scripts/autolinking") -platform :ios, podfile_properties['ios.deploymentTarget'] || '13.4' +platform :ios, '13.4' install! 'cocoapods', :deterministic_uuids => false # https://github.com/rnmapbox/maps/blob/v10.0/ios/install.md#mapbox-maps-sdk-v10 diff --git a/src/frontend/Navigation/AppNavigator.tsx b/src/frontend/Navigation/AppNavigator.tsx index 2a65124ad..e7ca127d5 100644 --- a/src/frontend/Navigation/AppNavigator.tsx +++ b/src/frontend/Navigation/AppNavigator.tsx @@ -65,11 +65,12 @@ export const AppNavigator = ({permissionAsked}: {permissionAsked: boolean}) => { return ( {deviceInfo.data?.name ? createDefaultScreenGroup(formatMessage) diff --git a/src/frontend/Navigation/ScreenGroups/AppScreens.tsx b/src/frontend/Navigation/ScreenGroups/AppScreens.tsx index 12a6d6563..69883b049 100644 --- a/src/frontend/Navigation/ScreenGroups/AppScreens.tsx +++ b/src/frontend/Navigation/ScreenGroups/AppScreens.tsx @@ -52,6 +52,7 @@ 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'; export const TAB_BAR_HEIGHT = 70; @@ -132,6 +133,7 @@ export type AppList = { }; DeviceNameDisplay: undefined; DeviceNameEdit: undefined; + SaveTrackScreen: undefined; }; const Tab = createBottomTabNavigator(); @@ -339,5 +341,12 @@ export const createDefaultScreenGroup = ( component={GpsModal} options={createGpsModalNavigationOptions({intl})} /> + ); diff --git a/src/frontend/hooks/tracks/useTracking.ts b/src/frontend/hooks/tracks/useTracking.ts index 3fde724b0..2c8a0a5c6 100644 --- a/src/frontend/hooks/tracks/useTracking.ts +++ b/src/frontend/hooks/tracks/useTracking.ts @@ -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'; @@ -13,6 +14,7 @@ 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); @@ -56,10 +58,11 @@ export function useTracking() { setTracking(true); setLoading(false); - }, [addNewTrackLocations, isTracking, setTracking]); + }, [isTracking, setTracking]); const cancelTracking = useCallback(async () => { await Location.stopLocationUpdatesAsync(LOCATION_TASK_NAME); + bottomSheetRef.current?.close(); setTracking(false); }, [setTracking]); diff --git a/src/frontend/images/close.svg b/src/frontend/images/close.svg new file mode 100644 index 000000000..769a8983d --- /dev/null +++ b/src/frontend/images/close.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/frontend/screens/MapScreen/track/SaveTrackScreen.tsx b/src/frontend/screens/MapScreen/track/SaveTrackScreen.tsx new file mode 100644 index 000000000..b5098dc32 --- /dev/null +++ b/src/frontend/screens/MapScreen/track/SaveTrackScreen.tsx @@ -0,0 +1,16 @@ +import React, {useRef} from 'react'; +import {SafeAreaView} from 'react-native'; +import {BottomSheetModal} from '@gorhom/bottom-sheet'; +import {SaveTrackHeader} from './saveTrack/SaveTrackHeader'; +import {DiscardTrackModal} from './saveTrack/DiscardTrackModal'; + +export const SaveTrackScreen = () => { + const bottomSheetRef = useRef(null); + + return ( + + + + + ); +}; diff --git a/src/frontend/screens/MapScreen/track/saveTrack/DiscardTrackModal.tsx b/src/frontend/screens/MapScreen/track/saveTrack/DiscardTrackModal.tsx new file mode 100644 index 000000000..f128d0ce5 --- /dev/null +++ b/src/frontend/screens/MapScreen/track/saveTrack/DiscardTrackModal.tsx @@ -0,0 +1,126 @@ +import React, {FC, useCallback} from 'react'; +import { + BottomSheetBackdrop, + BottomSheetBackdropProps, + BottomSheetModal, + BottomSheetView, +} from '@gorhom/bottom-sheet'; +import {BottomSheetModalMethods} from '@gorhom/bottom-sheet/lib/typescript/types'; +import {StyleSheet, View} from 'react-native'; +import {Text} from '../../../../sharedComponents/Text'; +import {Button} from '../../../../sharedComponents/Button'; +import {defineMessages, useIntl} from 'react-intl'; +import ErrorIcon from '../../../../images/Error.svg'; +import {COMAPEO_BLUE, MAGENTA, WHITE} from '../../../../lib/styles'; +import {TabName} from '../../../../Navigation/types'; +import {useNavigationFromHomeTabs} from '../../../../hooks/useNavigationWithTypes'; + +export interface DiscardTrackModal { + bottomSheetRef: React.RefObject; +} + +const m = defineMessages({ + discardTrackTitle: { + id: 'Modal.DiscardTrack.title', + defaultMessage: 'Discard Track?', + }, + discardTrackDescription: { + id: 'Modal.DiscardTrack.description', + defaultMessage: 'Your Track will not be saved.\n This cannot be undone.', + }, + discardTrackDiscardButton: { + id: 'Modal.GPSDisable.discardButton', + defaultMessage: 'Discard Track', + }, + discardTrackDefaultButton: { + id: 'Modal.GPSDisable.defaultButton', + defaultMessage: 'Continue Editing', + }, +}); + +export const DiscardTrackModal: FC = ({bottomSheetRef}) => { + const {formatMessage} = useIntl(); + const navigation = useNavigationFromHomeTabs(); + + const onClose = () => bottomSheetRef.current?.close(); + + const renderBackdrop = useCallback( + (props: BottomSheetBackdropProps) => ( + + ), + [], + ); + + return ( + null}> + + + + {formatMessage(m.discardTrackTitle)} + + {formatMessage(m.discardTrackDescription)} + + + + + + + ); +}; + +export const styles = StyleSheet.create({ + modal: {borderBottomLeftRadius: 0, borderBottomRightRadius: 0}, + wrapper: { + paddingTop: 30, + paddingHorizontal: 40, + paddingBottom: 20, + alignItems: 'center', + display: 'flex', + justifyContent: 'center', + }, + image: {marginBottom: 15}, + title: {fontSize: 24, fontWeight: 'bold', textAlign: 'center'}, + description: {fontSize: 16, textAlign: 'center', marginVertical: 10}, + discardButton: {backgroundColor: MAGENTA, marginBottom: 20}, + discardButtonText: { + color: WHITE, + }, + buttonText: { + fontWeight: '700', + fontSize: 16, + }, + defaultButtonText: { + color: COMAPEO_BLUE, + }, + defaultButton: { + borderWidth: 1.5, + borderColor: '#CCCCD6', + backgroundColor: 'transparent', + }, +}); diff --git a/src/frontend/screens/MapScreen/track/saveTrack/SaveTrackHeader.tsx b/src/frontend/screens/MapScreen/track/saveTrack/SaveTrackHeader.tsx new file mode 100644 index 000000000..1f6adedba --- /dev/null +++ b/src/frontend/screens/MapScreen/track/saveTrack/SaveTrackHeader.tsx @@ -0,0 +1,40 @@ +import React, {FC} from 'react'; +import {View, Image, StyleSheet, Pressable} from 'react-native'; +import {Text} from '../../../../sharedComponents/Text'; +import Close from '../../../../images/close.svg'; +import {BottomSheetModalMethods} from '@gorhom/bottom-sheet/lib/typescript/types'; + +export interface SaveTrackHeader { + bottomSheetRef: React.RefObject; +} +export const SaveTrackHeader: FC = ({bottomSheetRef}) => { + return ( + + + bottomSheetRef.current?.present()}> + + + New Observation + + + + ); +}; +const styles = StyleSheet.create({ + container: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + paddingVertical: 10, + paddingHorizontal: 20, + }, + closeWrapper: {flexDirection: 'row', alignItems: 'center'}, + closeIcon: {width: 15, height: 15, marginRight: 20}, + text: {fontSize: 16, fontWeight: 'bold'}, + completeIcon: {width: 30, height: 30}, +}); From 78e4a87d8023b2023edbc88486747d33f2a25ab5 Mon Sep 17 00:00:00 2001 From: bohdanprog Date: Wed, 17 Apr 2024 14:37:17 +0200 Subject: [PATCH 057/123] add translations --- messages/en.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/messages/en.json b/messages/en.json index 8370c1364..715d95874 100644 --- a/messages/en.json +++ b/messages/en.json @@ -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" }, From 6fa47da5f8e1f1196612a8b34b8c2ffcfa8e8c31 Mon Sep 17 00:00:00 2001 From: bohdanprog Date: Wed, 17 Apr 2024 15:25:53 +0200 Subject: [PATCH 058/123] update calculate distance function --- src/frontend/utils/distance.test.ts | 87 ++++++++++++++++++++++++++++- src/frontend/utils/distance.ts | 36 ++++++++++-- 2 files changed, 117 insertions(+), 6 deletions(-) diff --git a/src/frontend/utils/distance.test.ts b/src/frontend/utils/distance.test.ts index 199842a97..e80b629f0 100644 --- a/src/frontend/utils/distance.test.ts +++ b/src/frontend/utils/distance.test.ts @@ -1,6 +1,13 @@ import {calculateTotalDistance} from './distance'; describe('calculateTotalDistance', () => { + it('calculates the total distance for a single point (should be 0)', () => { + const points = [ + {latitude: 40.7128, longitude: -74.006}, // New York City, USA + ]; + + expect(calculateTotalDistance(points)).toBe(0); + }); it('calculateTotalDistance between two different points Warsaw - Cracow', () => { const distance = 251.98; const listOfPoints = [ @@ -37,7 +44,7 @@ describe('calculateTotalDistance', () => { distance, ); }); - it('calculateTotalDistance between different points Cracow - Warsaw - Vienna - Berlin - Amsterdam ', () => { + it('calculateTotalDistance between different points Cracow - Warsaw - Vienna - Berlin - Amsterdam', () => { const distance = 1908.61; const listOfPoints = [ {latitude: 50.064651, longitude: 19.944981}, @@ -51,4 +58,82 @@ describe('calculateTotalDistance', () => { distance, ); }); + + it('calculates the total distance for two points', () => { + const points = [ + {latitude: 40.7128, longitude: -74.006}, // New York City, USA + {latitude: 51.5074, longitude: -0.1278}, // London, UK + ]; + + expect(calculateTotalDistance(points)).toBeCloseTo(5571, -1); + }); + + it('calculates the total distance for multiple points', () => { + const points = [ + {latitude: 40.7128, longitude: -74.006}, // New York City, USA + {latitude: 51.5074, longitude: -0.1278}, // London, UK + {latitude: 35.6895, longitude: 139.6917}, // Tokyo, Japan + {latitude: 34.0522, longitude: -118.2437}, // Los Angeles, USA + ]; + + expect(calculateTotalDistance(points)).toBeCloseTo(23944.409, 1); + }); + it('calculateTotalDistance between different points New York City - London', () => { + const distance = 5567.83; + const listOfPoints = [ + {latitude: 40.7128, longitude: -74.006}, // New York City, USA + {latitude: 51.5074, longitude: -0.1278}, // London, UK + ]; + + expect(calculateTotalDistance(listOfPoints)).toBeCloseTo(distance, -1); + }); + + it('calculateTotalDistance between different points Tokyo - Sydney', () => { + const distance = 7825.21; + const listOfPoints = [ + {latitude: 35.6895, longitude: 139.6917}, // Tokyo, Japan + {latitude: -33.8688, longitude: 151.2093}, // Sydney, Australia + ]; + + expect(Number(calculateTotalDistance(listOfPoints).toFixed(2))).toBeCloseTo( + distance, + -1, + ); + }); + it('calculateTotalDistance between different points Rio de Janeiro, Brazil - Rome', () => { + const distance = 9200.25; + const listOfPoints = [ + {latitude: -22.9068, longitude: -43.1729}, // Rio de Janeiro, Brazil + {latitude: 41.9028, longitude: 12.4964}, // Rome, Italy + ]; + + expect(Number(calculateTotalDistance(listOfPoints).toFixed(2))).toBeCloseTo( + distance, + -1, + ); + }); + it('calculateTotalDistance between different points New Delhi - Los Angeles', () => { + const distance = 12857.05; + const listOfPoints = [ + {latitude: 28.6139, longitude: 77.209}, // New Delhi, India + {latitude: 34.0522, longitude: -118.2437}, // Los Angeles, USA + ]; + + expect(Number(calculateTotalDistance(listOfPoints).toFixed(2))).toBeCloseTo( + distance, + -1, + ); + }); + it('calculateTotalDistance between different points Stockholm - Nairobi', () => { + const distance = 6936.42; + const listOfPoints = [ + {latitude: 59.3293, longitude: 18.0686}, + {latitude: -1.2864, longitude: 36.8172}, + ]; + + expect(Number(calculateTotalDistance(listOfPoints).toFixed(2))).toBeCloseTo( + distance, + -1, + ); + }); }); diff --git a/src/frontend/utils/distance.ts b/src/frontend/utils/distance.ts index 36b8d6baf..04fbea8c4 100644 --- a/src/frontend/utils/distance.ts +++ b/src/frontend/utils/distance.ts @@ -24,15 +24,41 @@ function calculateHaversine( 2, ); - const cosineProduct = - pointALatitudeRadianCosine * - pointBLatitudeRadianCosine * - deltaLongitudeHalfSineSquared; - const haversine = deltaLatitudeHalfSineSquared + cosineProduct; + const cosineProduct = pointALatitudeRadianCosine * pointBLatitudeRadianCosine; + + const haversine = + deltaLatitudeHalfSineSquared + + cosineProduct * deltaLongitudeHalfSineSquared; return haversine; } +// deltaLatitude: number, +// deltaLongitude: number, +// pointA: LonLatData, +// pointB: LonLatData, +// ): number { +// const deltaLatitudeHalfSineSquared = Math.pow(Math.sin(deltaLatitude / 2), 2); +// const pointALatitudeRadianCosine = Math.cos( +// degreesToRadians(pointA.latitude), +// ); +// const pointBLatitudeRadianCosine = Math.cos( +// degreesToRadians(pointB.latitude), +// ); +// const deltaLongitudeHalfSineSquared = Math.pow( +// Math.sin(deltaLongitude / 2), +// 2, +// ); + +// const cosineProduct = +// pointALatitudeRadianCosine * +// pointBLatitudeRadianCosine * +// deltaLongitudeHalfSineSquared; +// const haversine = deltaLatitudeHalfSineSquared + cosineProduct; + +// return haversine; +// } + // Based on https://en.wikipedia.org/wiki/Haversine_formula export const calculateTotalDistance = (points: LonLatData[]): number => points.reduce((previousValue, currentValue, i, arr) => { From 2b1e4eeead728c5ed13a18cd3cdb58e688e3688a Mon Sep 17 00:00:00 2001 From: bohdanprog Date: Wed, 17 Apr 2024 15:28:42 +0200 Subject: [PATCH 059/123] remove comment --- src/frontend/utils/distance.ts | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/src/frontend/utils/distance.ts b/src/frontend/utils/distance.ts index 04fbea8c4..02f99b0c5 100644 --- a/src/frontend/utils/distance.ts +++ b/src/frontend/utils/distance.ts @@ -33,32 +33,6 @@ function calculateHaversine( return haversine; } -// deltaLatitude: number, -// deltaLongitude: number, -// pointA: LonLatData, -// pointB: LonLatData, -// ): number { -// const deltaLatitudeHalfSineSquared = Math.pow(Math.sin(deltaLatitude / 2), 2); -// const pointALatitudeRadianCosine = Math.cos( -// degreesToRadians(pointA.latitude), -// ); -// const pointBLatitudeRadianCosine = Math.cos( -// degreesToRadians(pointB.latitude), -// ); -// const deltaLongitudeHalfSineSquared = Math.pow( -// Math.sin(deltaLongitude / 2), -// 2, -// ); - -// const cosineProduct = -// pointALatitudeRadianCosine * -// pointBLatitudeRadianCosine * -// deltaLongitudeHalfSineSquared; -// const haversine = deltaLatitudeHalfSineSquared + cosineProduct; - -// return haversine; -// } - // Based on https://en.wikipedia.org/wiki/Haversine_formula export const calculateTotalDistance = (points: LonLatData[]): number => points.reduce((previousValue, currentValue, i, arr) => { From 2c7da916ae3a6a07a79f077b1f2cf9e31aa6fd7c Mon Sep 17 00:00:00 2001 From: bohdanprog Date: Wed, 17 Apr 2024 15:38:36 +0200 Subject: [PATCH 060/123] fix problem with timing calculation --- src/frontend/hooks/useFormattedTimeSince.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frontend/hooks/useFormattedTimeSince.ts b/src/frontend/hooks/useFormattedTimeSince.ts index 8777c608c..c08d139bf 100644 --- a/src/frontend/hooks/useFormattedTimeSince.ts +++ b/src/frontend/hooks/useFormattedTimeSince.ts @@ -11,6 +11,6 @@ export const useFormattedTimeSince = (start: Date, interval: number) => { return () => clearInterval(timer); }, [interval]); - const millisPassed = currentTime.getTime() - start.getTime(); + const millisPassed = Math.abs(currentTime.getTime() - start.getTime()); return Duration.fromMillis(millisPassed).toFormat('hh:mm:ss'); }; From 9d5f7e4d4293270e8e6ee9666cc773da1245a6b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20Stefa=C5=84czyk?= Date: Tue, 16 Apr 2024 17:08:05 +0200 Subject: [PATCH 061/123] improve typing a bit --- src/frontend/hooks/useCurrentTab.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/frontend/hooks/useCurrentTab.ts b/src/frontend/hooks/useCurrentTab.ts index b522e0b2d..c22a158a0 100644 --- a/src/frontend/hooks/useCurrentTab.ts +++ b/src/frontend/hooks/useCurrentTab.ts @@ -16,11 +16,11 @@ export const useCurrentTab = () => { if (targetTab === TabName.Tracking) { preventDefault(); bottomSheetRef.current?.present(); - navigation.navigate('Map' as never); + navigation.navigate(TabName.Map as never); } else { bottomSheetRef.current?.close(); } - setCurrentTab((target?.split('-')[0] || 'Map') as unknown as TabName); + setCurrentTab((targetTab || 'Map') as TabName); }; return {handleTabPress}; From 19f4ab97701e0ad4d93fd497f9aa0b04c5501682 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20Stefa=C5=84czyk?= Date: Thu, 18 Apr 2024 10:10:35 +0200 Subject: [PATCH 062/123] add shared location context --- src/frontend/contexts/ExternalProviders.tsx | 19 +++-- .../contexts/SharedLocationContext.tsx | 69 ++++++++++++++++++ src/frontend/hooks/useLocation.ts | 73 +++++++++---------- src/frontend/screens/MapScreen/index.tsx | 3 +- .../MapScreen/track/UserTooltipMarker.tsx | 4 +- 5 files changed, 120 insertions(+), 48 deletions(-) create mode 100644 src/frontend/contexts/SharedLocationContext.tsx diff --git a/src/frontend/contexts/ExternalProviders.tsx b/src/frontend/contexts/ExternalProviders.tsx index f4830bc8a..7611df536 100644 --- a/src/frontend/contexts/ExternalProviders.tsx +++ b/src/frontend/contexts/ExternalProviders.tsx @@ -13,6 +13,7 @@ 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; @@ -28,13 +29,17 @@ export const ExternalProviders = ({ return ( - - - - {children} - - - + + + + + + {children} + + + + + ); diff --git a/src/frontend/contexts/SharedLocationContext.tsx b/src/frontend/contexts/SharedLocationContext.tsx new file mode 100644 index 000000000..4224220e3 --- /dev/null +++ b/src/frontend/contexts/SharedLocationContext.tsx @@ -0,0 +1,69 @@ +import {createContext, useContext, useEffect, useRef, useState} from 'react'; +import {LocationState, useLocation} from '../hooks/useLocation'; +import React from 'react'; +import { + getBackgroundPermissionsAsync, + getForegroundPermissionsAsync, +} from 'expo-location'; +import {AppState} from 'react-native'; + +interface SharedLocationContext { + location: LocationState; + bgPermissions: boolean | null; + fgPermissions: boolean | null; +} + +const SharedLocationContext = createContext(null); + +const SharedLocationContextProvider = ({ + children, +}: { + children: React.ReactNode; +}) => { + const location = useLocation({maxDistanceInterval: 3}); + const appState = useRef(AppState.currentState); + const [bgPermissions, setBgPermissions] = useState(null); + const [fgPermissions, setFgPermissions] = useState(null); + + useEffect(() => { + const sub = AppState.addEventListener('change', newState => { + if ( + appState.current.match(/inactive|background/) && + newState === 'active' + ) { + getBackgroundPermissionsAsync().then(({granted}) => + setBgPermissions(granted), + ); + getForegroundPermissionsAsync().then(({granted}) => + setFgPermissions(granted), + ); + } + appState.current = newState; + }); + + return () => sub.remove(); + }, []); + + return ( + + {children} + + ); +}; + +function useSharedLocationContext() { + const context = useContext(SharedLocationContext); + if (!context) { + throw new Error( + 'useSharedLocationContext must be used within a SharedLocationContextProvider', + ); + } + return context.location; +} + +export {SharedLocationContextProvider, useSharedLocationContext}; diff --git a/src/frontend/hooks/useLocation.ts b/src/frontend/hooks/useLocation.ts index bd6a2f5ac..aa1c5b546 100644 --- a/src/frontend/hooks/useLocation.ts +++ b/src/frontend/hooks/useLocation.ts @@ -1,4 +1,3 @@ -import {useFocusEffect} from '@react-navigation/native'; import CheapRuler from 'cheap-ruler'; import { watchPositionAsync, @@ -6,7 +5,7 @@ import { type LocationObject, Accuracy, } from 'expo-location'; -import React from 'react'; +import React, {useEffect} from 'react'; interface LocationOptions { /** Only update location if it has changed by at least this distance in meters (or maxTimeInterval has passed) */ @@ -37,46 +36,44 @@ export function useLocation({ const [permissions] = useForegroundPermissions(); - useFocusEffect( - React.useCallback(() => { - if (!permissions || !permissions.granted) return; + useEffect(() => { + if (!permissions || !permissions.granted) return; - let ignore = false; - const locationSubscriptionProm = watchPositionAsync( - { - accuracy: Accuracy.BestForNavigation, - distanceInterval, - }, - debounceLocation({ - minTimeInterval, - maxTimeInterval, - maxDistanceInterval, - })(location => { - if (ignore) return; - setLocation({location, error: undefined}); - }), - ); - - // Should not happen because we are checking permissions above, but just in case - locationSubscriptionProm.catch(error => { + let ignore = false; + const locationSubscriptionProm = watchPositionAsync( + { + accuracy: Accuracy.BestForNavigation, + distanceInterval, + }, + debounceLocation({ + minTimeInterval, + maxTimeInterval, + maxDistanceInterval, + })(location => { if (ignore) return; - setLocation(({location}) => { - return {location, error}; - }); + setLocation({location, error: undefined}); + }), + ); + + // Should not happen because we are checking permissions above, but just in case + locationSubscriptionProm.catch(error => { + if (ignore) return; + setLocation(({location}) => { + return {location, error}; }); + }); - return () => { - ignore = true; - locationSubscriptionProm.then(sub => sub.remove()); - }; - }, [ - permissions, - distanceInterval, - minTimeInterval, - maxTimeInterval, - maxDistanceInterval, - ]), - ); + return () => { + ignore = true; + locationSubscriptionProm.then(sub => sub.remove()); + }; + }, [ + distanceInterval, + maxDistanceInterval, + maxTimeInterval, + minTimeInterval, + permissions, + ]); return location; } diff --git a/src/frontend/screens/MapScreen/index.tsx b/src/frontend/screens/MapScreen/index.tsx index e6e8b956a..68a1ed5b8 100644 --- a/src/frontend/screens/MapScreen/index.tsx +++ b/src/frontend/screens/MapScreen/index.tsx @@ -20,6 +20,7 @@ import {useLocationProviderStatus} from '../../hooks/useLocationProviderStatus'; import {GPSModal} from './gps/GPSModal'; import {TrackPathLayer} from './track/TrackPathLayer'; import {UserLocation} from './UserLocation'; +import {useSharedLocationContext} from '../../contexts/SharedLocationContext'; // This is the default zoom used when the map first loads, and also the zoom // that the map will zoom to if the user clicks the "Locate" button and the @@ -38,7 +39,7 @@ export const MapScreen = () => { const [following, setFollowing] = React.useState(true); const {newDraft} = useDraftObservation(); const {navigate} = useNavigationFromHomeTabs(); - const {location} = useLocation({maxDistanceInterval: MIN_DISPLACEMENT}); + const {location} = useSharedLocationContext(); const savedLocation = useLastKnownLocation(); const coords = location && getCoords(location); const locationProviderStatus = useLocationProviderStatus(); diff --git a/src/frontend/screens/MapScreen/track/UserTooltipMarker.tsx b/src/frontend/screens/MapScreen/track/UserTooltipMarker.tsx index 2eb2ce462..90a76899e 100644 --- a/src/frontend/screens/MapScreen/track/UserTooltipMarker.tsx +++ b/src/frontend/screens/MapScreen/track/UserTooltipMarker.tsx @@ -1,13 +1,13 @@ import {MarkerView} from '@rnmapbox/maps'; import {StyleSheet, Text, View} from 'react-native'; -import {useLocation} from '../../../hooks/useLocation'; import React from 'react'; import {useCurrentTrackStore} from '../../../hooks/tracks/useCurrentTrackStore'; import {useTrackTimerContext} from '../../../contexts/TrackTimerContext'; +import {useSharedLocationContext} from '../../../contexts/SharedLocationContext'; export const UserTooltipMarker = () => { const {timer} = useTrackTimerContext(); - const {location} = useLocation({maxDistanceInterval: 0}); + const {location} = useSharedLocationContext(); const totalDistance = useCurrentTrackStore(state => state.distance); return ( From fe1d38875ad5e9b7889ecc2281d7d25802ed25be Mon Sep 17 00:00:00 2001 From: bohdanprog Date: Thu, 18 Apr 2024 10:11:34 +0200 Subject: [PATCH 063/123] fix problem with gps modal and tab screen --- .../Navigation/ScreenGroups/AppScreens.tsx | 6 +++ .../ScreenGroups/TabBar/CameraTabBarIcon.tsx | 9 +++- .../ScreenGroups/TabBar/MapTabBarIcon.tsx | 11 ++++- .../ScreenGroups/TabBar/TabBarIcon.tsx | 11 ++--- .../TabBar/TrackingTabBarIcon.tsx | 4 +- src/frontend/hooks/useCurrentTab.ts | 12 ++--- .../screens/MapScreen/gps/GPSEnabled.tsx | 2 +- .../screens/MapScreen/gps/GPSModal.tsx | 40 ++++++++++++---- .../CustomBottomSheetModal.tsx | 48 +++++-------------- 9 files changed, 77 insertions(+), 66 deletions(-) diff --git a/src/frontend/Navigation/ScreenGroups/AppScreens.tsx b/src/frontend/Navigation/ScreenGroups/AppScreens.tsx index 12a6d6563..a037ba789 100644 --- a/src/frontend/Navigation/ScreenGroups/AppScreens.tsx +++ b/src/frontend/Navigation/ScreenGroups/AppScreens.tsx @@ -172,6 +172,12 @@ const HomeTabs = () => { tabBarIcon: TrackingTabBarIcon, headerShown: false, }} + listeners={({navigation}) => ({ + tabPress: e => { + e.preventDefault(); + navigation.navigate(TabName.Map); + }, + })} children={() => <>} /> diff --git a/src/frontend/Navigation/ScreenGroups/TabBar/CameraTabBarIcon.tsx b/src/frontend/Navigation/ScreenGroups/TabBar/CameraTabBarIcon.tsx index 30f0c73a0..352393169 100644 --- a/src/frontend/Navigation/ScreenGroups/TabBar/CameraTabBarIcon.tsx +++ b/src/frontend/Navigation/ScreenGroups/TabBar/CameraTabBarIcon.tsx @@ -1,9 +1,16 @@ import React, {FC} from 'react'; import {TabBarIconProps, TabName} from '../../types'; import {TabBarIcon} from './TabBarIcon'; +import {useNavigationStore} from '../../../hooks/useNavigationStore'; export const CameraTabBarIcon: FC = props => { + const {currentTab} = useNavigationStore(); + return ( - + ); }; diff --git a/src/frontend/Navigation/ScreenGroups/TabBar/MapTabBarIcon.tsx b/src/frontend/Navigation/ScreenGroups/TabBar/MapTabBarIcon.tsx index 0025dabe4..b4f3bdb5d 100644 --- a/src/frontend/Navigation/ScreenGroups/TabBar/MapTabBarIcon.tsx +++ b/src/frontend/Navigation/ScreenGroups/TabBar/MapTabBarIcon.tsx @@ -1,7 +1,16 @@ import React, {FC} from 'react'; import {TabBarIconProps, TabName} from '../../types'; import {TabBarIcon} from './TabBarIcon'; +import {useNavigationStore} from '../../../hooks/useNavigationStore'; export const MapTabBarIcon: FC = props => { - return ; + const {currentTab} = useNavigationStore(); + + return ( + + ); }; diff --git a/src/frontend/Navigation/ScreenGroups/TabBar/TabBarIcon.tsx b/src/frontend/Navigation/ScreenGroups/TabBar/TabBarIcon.tsx index 14dd65986..2a614b202 100644 --- a/src/frontend/Navigation/ScreenGroups/TabBar/TabBarIcon.tsx +++ b/src/frontend/Navigation/ScreenGroups/TabBar/TabBarIcon.tsx @@ -1,22 +1,19 @@ import React, {FC} from 'react'; import MaterialIcons from 'react-native-vector-icons/MaterialIcons'; -import {useNavigationStore} from '../../../hooks/useNavigationStore'; -import {TabBarIconProps, TabName} from '../../types'; +import {TabBarIconProps} from '../../types'; import {COMAPEO_BLUE, MEDIUM_GREY} from '../../../lib/styles'; export interface TabBarIcon extends TabBarIconProps { - tabName: TabName; + isFocused: boolean; iconName: string; } -export const TabBarIcon: FC = ({size, tabName, iconName}) => { - const {currentTab} = useNavigationStore(); - +export const TabBarIcon: FC = ({size, iconName, isFocused}) => { return ( ); }; diff --git a/src/frontend/Navigation/ScreenGroups/TabBar/TrackingTabBarIcon.tsx b/src/frontend/Navigation/ScreenGroups/TabBar/TrackingTabBarIcon.tsx index a981ff268..2b5ca1241 100644 --- a/src/frontend/Navigation/ScreenGroups/TabBar/TrackingTabBarIcon.tsx +++ b/src/frontend/Navigation/ScreenGroups/TabBar/TrackingTabBarIcon.tsx @@ -5,10 +5,12 @@ import {useTracking} from '../../../hooks/tracks/useTracking'; import {Text} from '../../../sharedComponents/Text'; import {TabBarIconProps, TabName} from '../../types'; import {useTrackTimerContext} from '../../../contexts/TrackTimerContext'; +import {useNavigationStore} from '../../../hooks/useNavigationStore'; export const TrackingTabBarIcon: FC = props => { const {isTracking} = useTracking(); const {timer} = useTrackTimerContext(); + const {currentTab} = useNavigationStore(); return ( <> @@ -20,7 +22,7 @@ export const TrackingTabBarIcon: FC = props => { )} diff --git a/src/frontend/hooks/useCurrentTab.ts b/src/frontend/hooks/useCurrentTab.ts index b522e0b2d..8aaac7202 100644 --- a/src/frontend/hooks/useCurrentTab.ts +++ b/src/frontend/hooks/useCurrentTab.ts @@ -1,26 +1,20 @@ -import {useNavigation, EventArg} from '@react-navigation/native'; +import {EventArg} from '@react-navigation/native'; import {useGPSModalContext} from '../contexts/GPSModalContext'; import {useNavigationStore} from './useNavigationStore'; import {TabName} from '../Navigation/types'; export const useCurrentTab = () => { const {setCurrentTab} = useNavigationStore(); - const navigation = useNavigation(); const {bottomSheetRef} = useGPSModalContext(); - const handleTabPress = ({ - target, - preventDefault, - }: EventArg<'tabPress', true, undefined>) => { + const handleTabPress = ({target}: EventArg<'tabPress', true, undefined>) => { const targetTab = target?.split('-')[0]; if (targetTab === TabName.Tracking) { - preventDefault(); bottomSheetRef.current?.present(); - navigation.navigate('Map' as never); } else { bottomSheetRef.current?.close(); } - setCurrentTab((target?.split('-')[0] || 'Map') as unknown as TabName); + setCurrentTab(targetTab as unknown as TabName); }; return {handleTabPress}; diff --git a/src/frontend/screens/MapScreen/gps/GPSEnabled.tsx b/src/frontend/screens/MapScreen/gps/GPSEnabled.tsx index b7a665f84..cadbc903c 100644 --- a/src/frontend/screens/MapScreen/gps/GPSEnabled.tsx +++ b/src/frontend/screens/MapScreen/gps/GPSEnabled.tsx @@ -74,7 +74,7 @@ export const GPSEnabled = () => { const getStyles = (isTracking: boolean) => { return StyleSheet.create({ button: {backgroundColor: isTracking ? '#D92222' : '#0066FF'}, - container: {paddingHorizontal: 20, paddingVertical: 30}, + container: {paddingHorizontal: 20, paddingVertical: 30, height: 140}, buttonWrapper: { flexDirection: 'row', display: 'flex', diff --git a/src/frontend/screens/MapScreen/gps/GPSModal.tsx b/src/frontend/screens/MapScreen/gps/GPSModal.tsx index 34714b74d..1ede73efd 100644 --- a/src/frontend/screens/MapScreen/gps/GPSModal.tsx +++ b/src/frontend/screens/MapScreen/gps/GPSModal.tsx @@ -4,15 +4,17 @@ import {GPSEnabled} from './GPSEnabled'; import * as Location from 'expo-location'; import {useGPSModalContext} from '../../../contexts/GPSModalContext'; import {useNavigationStore} from '../../../hooks/useNavigationStore'; -import {CustomBottomSheetModal} from '../../../sharedComponents/BottomSheetModal/CustomBottomSheetModal'; +import {BottomSheetModal, BottomSheetView} from '@gorhom/bottom-sheet'; +import {TAB_BAR_HEIGHT} from '../../../Navigation/ScreenGroups/AppScreens'; +import {StyleSheet} from 'react-native'; import {TabName} from '../../../Navigation/types'; +import {useFocusEffect} from '@react-navigation/native'; export const GPSModal = () => { const {setCurrentTab} = useNavigationStore(); const [backgroundStatus] = Location.useBackgroundPermissions(); const [foregroundStatus] = Location.useForegroundPermissions(); - const [currentIndex, setCurrentIndex] = useState(-1); const [isGranted, setIsGranted] = useState(null); const {bottomSheetRef} = useGPSModalContext(); @@ -24,16 +26,34 @@ export const GPSModal = () => { const onBottomSheetDismiss = () => { setCurrentTab(TabName.Map); - bottomSheetRef.current?.close(); }; + useFocusEffect(() => { + return () => bottomSheetRef?.current?.close(); + }); return ( - - {isGranted ? : } - + null}> + + {isGranted ? ( + + ) : ( + + )} + + ); }; + +const styles = StyleSheet.create({ + modal: { + borderBottomLeftRadius: 0, + borderBottomRightRadius: 0, + minHeight: 140, + }, +}); diff --git a/src/frontend/sharedComponents/BottomSheetModal/CustomBottomSheetModal.tsx b/src/frontend/sharedComponents/BottomSheetModal/CustomBottomSheetModal.tsx index 2cdb2ca83..2c58454ed 100644 --- a/src/frontend/sharedComponents/BottomSheetModal/CustomBottomSheetModal.tsx +++ b/src/frontend/sharedComponents/BottomSheetModal/CustomBottomSheetModal.tsx @@ -1,5 +1,5 @@ import React, {FC} from 'react'; -import {StyleSheet, TouchableWithoutFeedback, View} from 'react-native'; +import {StyleSheet} from 'react-native'; import {BottomSheetModal, BottomSheetView} from '@gorhom/bottom-sheet'; import {BottomSheetModalMethods} from '@gorhom/bottom-sheet/lib/typescript/types'; import {TAB_BAR_HEIGHT} from '../../Navigation/ScreenGroups/AppScreens'; @@ -7,48 +7,24 @@ import {TAB_BAR_HEIGHT} from '../../Navigation/ScreenGroups/AppScreens'; interface CustomBottomSheetModal { dismiss: () => void; bottomSheetRef: React.RefObject; - currentIndex: number; - setCurrentIndex: React.Dispatch>; children: React.ReactNode; } export const CustomBottomSheetModal: FC = ({ - dismiss, bottomSheetRef, - currentIndex, - setCurrentIndex, children, }) => { return ( - <> - - - - null}> - {children} - - + null}> + {children} + ); }; - -const styles = StyleSheet.create({ - wrapper: { - position: 'absolute', - height: '100%', - width: '100%', - backgroundColor: 'transparent', - }, - modal: {borderBottomLeftRadius: 0, borderBottomRightRadius: 0}, -}); From 213b61d0a29e078f025441d334837eab688ece86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20Stefa=C5=84czyk?= Date: Thu, 18 Apr 2024 10:45:40 +0200 Subject: [PATCH 064/123] add back rounding of gps precision --- src/frontend/screens/MapScreen/gps/GPSPill.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frontend/screens/MapScreen/gps/GPSPill.tsx b/src/frontend/screens/MapScreen/gps/GPSPill.tsx index 10f8e3bb1..6001d493d 100644 --- a/src/frontend/screens/MapScreen/gps/GPSPill.tsx +++ b/src/frontend/screens/MapScreen/gps/GPSPill.tsx @@ -48,7 +48,7 @@ export const GPSPill = () => { if (status === 'error') return t(m.noGps); else if (status === 'searching' || typeof precision === 'undefined') { return t(m.searching); - } else return `± ${precision!} m`; + } else return `± ${Math.round(precision!)} m`; }, [precision, status, t]); const navigation = useNavigationFromHomeTabs(); From 4070000dd70e9ccabac2926c45c38634b03dfee9 Mon Sep 17 00:00:00 2001 From: bohdanprog Date: Thu, 18 Apr 2024 12:38:47 +0200 Subject: [PATCH 065/123] add description option to save track screen --- .../MapScreen/track/SaveTrackScreen.tsx | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/src/frontend/screens/MapScreen/track/SaveTrackScreen.tsx b/src/frontend/screens/MapScreen/track/SaveTrackScreen.tsx index b5098dc32..7f5da2c67 100644 --- a/src/frontend/screens/MapScreen/track/SaveTrackScreen.tsx +++ b/src/frontend/screens/MapScreen/track/SaveTrackScreen.tsx @@ -1,16 +1,38 @@ import React, {useRef} from 'react'; -import {SafeAreaView} from 'react-native'; +import {SafeAreaView, ScrollView} from 'react-native'; import {BottomSheetModal} from '@gorhom/bottom-sheet'; import {SaveTrackHeader} from './saveTrack/SaveTrackHeader'; import {DiscardTrackModal} from './saveTrack/DiscardTrackModal'; +import {DescriptionField} from '../../ObservationEdit/DescriptionField'; +import {useFieldsQuery} from '../../../hooks/server/fields'; +import {Field} from '@mapeo/schema'; +import {FieldDetails} from '../../Observation/FieldDetails'; export const SaveTrackScreen = () => { const bottomSheetRef = useRef(null); + const fieldsQuery = useFieldsQuery(); + + const defaultAcc: Field[] = []; + // const fields = !fieldsQuery.data + // ? undefined + // : preset.fieldIds.reduce((acc, pres) => { + // const fieldToAdd = fieldsQuery.data.find( + // field => field.tagKey === pres, + // ); + // if (!fieldToAdd) return acc; + // return [...acc, fieldToAdd]; + // }, defaultAcc); return ( - - + + + + + {/* */} + + {/* {fields && fields.length > 0 && } */} + ); }; From 3be3b22e7d4196d7c5b5b9e3c4159dd6f4947e5e Mon Sep 17 00:00:00 2001 From: bohdanprog Date: Thu, 18 Apr 2024 12:40:19 +0200 Subject: [PATCH 066/123] restore changes in podfile --- ios/Podfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/Podfile b/ios/Podfile index 7ae4a093b..37c93bb72 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -7,7 +7,7 @@ require Pod::Executable.execute_command('node', ['-p', require File.join(File.dirname(`node --print "require.resolve('expo/package.json')"`), "scripts/autolinking") -platform :ios, '13.4' +platform :ios, podfile_properties['ios.deploymentTarget'] || '13.4' install! 'cocoapods', :deterministic_uuids => false # https://github.com/rnmapbox/maps/blob/v10.0/ios/install.md#mapbox-maps-sdk-v10 From 6f80c3accd432e602dc80d4c7a13b6df8cb136a8 Mon Sep 17 00:00:00 2001 From: bohdanprog Date: Thu, 18 Apr 2024 13:36:34 +0200 Subject: [PATCH 067/123] create all fields in save track screen --- src/frontend/images/Track.svg | 18 +++ src/frontend/images/camera.svg | 4 + src/frontend/images/details.svg | 3 + .../MapScreen/track/SaveTrackScreen.tsx | 109 ++++++++++++++---- .../track/saveTrack/SaveTrackHeader.tsx | 2 + .../track/saveTrack/TrackDescriptionField.tsx | 51 ++++++++ .../screens/ObservationEdit/BottomSheet.tsx | 6 +- 7 files changed, 166 insertions(+), 27 deletions(-) create mode 100644 src/frontend/images/Track.svg create mode 100644 src/frontend/images/camera.svg create mode 100644 src/frontend/images/details.svg create mode 100644 src/frontend/screens/MapScreen/track/saveTrack/TrackDescriptionField.tsx diff --git a/src/frontend/images/Track.svg b/src/frontend/images/Track.svg new file mode 100644 index 000000000..cdbb37459 --- /dev/null +++ b/src/frontend/images/Track.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/frontend/images/camera.svg b/src/frontend/images/camera.svg new file mode 100644 index 000000000..6822fca4e --- /dev/null +++ b/src/frontend/images/camera.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/frontend/images/details.svg b/src/frontend/images/details.svg new file mode 100644 index 000000000..f6f833b5f --- /dev/null +++ b/src/frontend/images/details.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/frontend/screens/MapScreen/track/SaveTrackScreen.tsx b/src/frontend/screens/MapScreen/track/SaveTrackScreen.tsx index 7f5da2c67..b497a9967 100644 --- a/src/frontend/screens/MapScreen/track/SaveTrackScreen.tsx +++ b/src/frontend/screens/MapScreen/track/SaveTrackScreen.tsx @@ -1,38 +1,99 @@ -import React, {useRef} from 'react'; -import {SafeAreaView, ScrollView} from 'react-native'; +import React, {useRef, useState} from 'react'; +import {SafeAreaView, ScrollView, StyleSheet, View} from 'react-native'; import {BottomSheetModal} from '@gorhom/bottom-sheet'; import {SaveTrackHeader} from './saveTrack/SaveTrackHeader'; import {DiscardTrackModal} from './saveTrack/DiscardTrackModal'; import {DescriptionField} from '../../ObservationEdit/DescriptionField'; -import {useFieldsQuery} from '../../../hooks/server/fields'; -import {Field} from '@mapeo/schema'; -import {FieldDetails} from '../../Observation/FieldDetails'; +import {BottomSheet} from '../../ObservationEdit/BottomSheet'; +import PhotoIcon from '../../../images/camera.svg'; +import DetailsIcon from '../../../images/details.svg'; +import TrackIcon from '../../../images/Track.svg'; +import {defineMessages, useIntl} from 'react-intl'; +import {Text} from '../../../sharedComponents/Text'; +import {TrackDescriptionField} from './saveTrack/TrackDescriptionField'; -export const SaveTrackScreen = () => { +const m = defineMessages({ + editTitle: { + id: 'screens.ObservationEdit.editTitle', + defaultMessage: 'Edit Observation', + description: 'screen title for edit observation screen', + }, + newTitle: { + id: 'screens.ObservationEdit.newTitle', + defaultMessage: 'New Observation', + description: 'screen title for new observation screen', + }, + detailsButton: { + id: 'screens.ObservationEdit.ObservationEditView.saveTrackDetails', + defaultMessage: 'Details', + description: 'Button label for check details', + }, + photoButton: { + id: 'screens.ObservationEdit.ObservationEditView.saveTrackCamera', + defaultMessage: 'Camera', + description: 'Button label for adding photo', + }, +}); + +export const SaveTrackScreen = ({navigation}) => { + const {formatMessage: t} = useIntl(); + const [description, setDescription] = useState(''); const bottomSheetRef = useRef(null); - const fieldsQuery = useFieldsQuery(); - const defaultAcc: Field[] = []; - // const fields = !fieldsQuery.data - // ? undefined - // : preset.fieldIds.reduce((acc, pres) => { - // const fieldToAdd = fieldsQuery.data.find( - // field => field.tagKey === pres, - // ); - // if (!fieldToAdd) return acc; - // return [...acc, fieldToAdd]; - // }, defaultAcc); + const handleCameraPress = React.useCallback(() => { + navigation.navigate('AddPhoto'); + }, [navigation]); + const bottomSheetItems = [ + { + icon: , + label: t(m.photoButton), + onPress: handleCameraPress, + }, + { + icon: , + label: t(m.detailsButton), + onPress: () => {}, + }, + ]; return ( - - - - + + + + + + Track + + - {/* */} - - {/* {fields && fields.length > 0 && } */} + ); }; + +const styles = StyleSheet.create({ + container: { + flex: 1, + flexDirection: 'column', + alignContent: 'stretch', + }, + scrollViewContent: { + paddingHorizontal: 20, + flexDirection: 'column', + alignContent: 'stretch', + }, +}); diff --git a/src/frontend/screens/MapScreen/track/saveTrack/SaveTrackHeader.tsx b/src/frontend/screens/MapScreen/track/saveTrack/SaveTrackHeader.tsx index 1f6adedba..a5ba02ef5 100644 --- a/src/frontend/screens/MapScreen/track/saveTrack/SaveTrackHeader.tsx +++ b/src/frontend/screens/MapScreen/track/saveTrack/SaveTrackHeader.tsx @@ -27,6 +27,8 @@ export const SaveTrackHeader: FC = ({bottomSheetRef}) => { }; const styles = StyleSheet.create({ container: { + borderBottomWidth: 1, + borderBottomColor: '#EDEDED', flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', diff --git a/src/frontend/screens/MapScreen/track/saveTrack/TrackDescriptionField.tsx b/src/frontend/screens/MapScreen/track/saveTrack/TrackDescriptionField.tsx new file mode 100644 index 000000000..915812ca2 --- /dev/null +++ b/src/frontend/screens/MapScreen/track/saveTrack/TrackDescriptionField.tsx @@ -0,0 +1,51 @@ +import * as React from 'react'; +import {defineMessages, useIntl} from 'react-intl'; +import {StyleSheet, TextInput} from 'react-native'; + +const m = defineMessages({ + descriptionPlaceholder: { + id: 'screens.ObservationEdit.ObservationEditView.descriptionPlaceholder', + defaultMessage: 'What is happening here?', + description: 'Placeholder for description/notes field', + }, +}); + +interface DescriptionField { + description: string; + setDescription: React.Dispatch>; +} +export const TrackDescriptionField: React.FC = ({ + description, + setDescription, +}) => { + const {formatMessage: t} = useIntl(); + + return ( + + ); +}; + +const styles = StyleSheet.create({ + textInput: { + flex: 1, + minHeight: 100, + fontSize: 20, + paddingHorizontal: 20, + color: 'black', + // backgroundColor: 'red', + alignItems: 'flex-start', + justifyContent: 'flex-start', + textAlignVertical: 'top', + }, +}); diff --git a/src/frontend/screens/ObservationEdit/BottomSheet.tsx b/src/frontend/screens/ObservationEdit/BottomSheet.tsx index 41d99c28a..07f300687 100644 --- a/src/frontend/screens/ObservationEdit/BottomSheet.tsx +++ b/src/frontend/screens/ObservationEdit/BottomSheet.tsx @@ -107,7 +107,8 @@ const styles = StyleSheet.create({ }, itemContainer: { flex: 0, - height: 60, + paddingVertical: 24, + paddingHorizontal: 20, flexDirection: 'row', alignItems: 'center', borderTopWidth: 1, @@ -117,8 +118,7 @@ const styles = StyleSheet.create({ flex: 0, alignItems: 'center', justifyContent: 'center', - paddingLeft: 30, - paddingRight: 30, + paddingRight: 10, }, itemLabel: { flex: 1, From e0196ddb37e9477b7fbb8f4dc072ef720d61fa64 Mon Sep 17 00:00:00 2001 From: bohdanprog Date: Thu, 18 Apr 2024 13:50:38 +0200 Subject: [PATCH 068/123] add translations --- messages/en.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/messages/en.json b/messages/en.json index 715d95874..41cdde25c 100644 --- a/messages/en.json +++ b/messages/en.json @@ -374,6 +374,14 @@ "description": "Button label for adding photo", "message": "Add Photo" }, + "screens.ObservationEdit.ObservationEditView.saveTrackCamera": { + "description": "Button label for adding photo", + "message": "Camera" + }, + "screens.ObservationEdit.ObservationEditView.saveTrackDetails": { + "description": "Button label for check details", + "message": "Details" + }, "screens.ObservationEdit.ObservationEditView.searching": { "description": "Shown in new observation screen whilst looking for GPS", "message": "Searching…" From cd7fc9fa02cd5066eb336fa1dae9dd887800834f Mon Sep 17 00:00:00 2001 From: bohdanprog Date: Thu, 18 Apr 2024 14:04:38 +0200 Subject: [PATCH 069/123] update packages, create hook for creating track --- package-lock.json | 102 +++++++++++------- package.json | 4 +- src/backend/package-lock.json | 4 +- src/backend/package.json | 4 +- src/frontend/hooks/server/track.ts | 21 ++++ .../track/saveTrack/TrackDescriptionField.tsx | 1 - 6 files changed, 92 insertions(+), 44 deletions(-) create mode 100644 src/frontend/hooks/server/track.ts diff --git a/package-lock.json b/package-lock.json index 71d584c8f..c0a0b3b66 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ "@formatjs/intl-pluralrules": "^5.2.4", "@formatjs/intl-relativetimeformat": "^11.2.4", "@gorhom/bottom-sheet": "^4.5.1", - "@mapeo/ipc": "^0.2.0", + "@mapeo/ipc": "^0.3.0", "@react-native-community/hooks": "^2.8.0", "@react-native-community/netinfo": "11.1.0", "@react-native-picker/picker": "2.6.1", @@ -79,7 +79,7 @@ "@babel/preset-env": "^7.20.0", "@babel/runtime": "^7.20.0", "@formatjs/cli": "^6.2.0", - "@mapeo/core": "^9.0.0-alpha.6", + "@mapeo/core": "^9.0.0-alpha.7", "@mapeo/schema": "3.0.0-next.13", "@react-native-community/cli": "^12.3.6", "@react-native/babel-preset": "^0.73.21", @@ -5364,9 +5364,9 @@ } }, "node_modules/@mapeo/core": { - "version": "9.0.0-alpha.6", - "resolved": "https://registry.npmjs.org/@mapeo/core/-/core-9.0.0-alpha.6.tgz", - "integrity": "sha512-b1eHOmgVbtE02YAMR/kgfgHX5Mt//4gV290pgC+dPpZqccAgDIdBlaviJKRIGHCM16ZETdilx1NvSH+PJHYYGA==", + "version": "9.0.0-alpha.7", + "resolved": "https://registry.npmjs.org/@mapeo/core/-/core-9.0.0-alpha.7.tgz", + "integrity": "sha512-8DXZPKtMMVLTgZX3F8Eew3KnLB6bmf9v7uda9TvMX14YM9np4G9IiYafPS/X7kFNvr7h823wMzkRotNnHZ3byg==", "hasInstallScript": true, "dependencies": { "@digidem/types": "^2.2.0", @@ -5376,7 +5376,7 @@ "@fastify/type-provider-typebox": "^3.3.0", "@hyperswarm/secret-stream": "^6.1.2", "@mapeo/crypto": "1.0.0-alpha.10", - "@mapeo/schema": "3.0.0-next.14", + "@mapeo/schema": "3.0.0-next.15", "@mapeo/sqlite-indexer": "1.0.0-alpha.8", "@sinclair/typebox": "^0.29.6", "b4a": "^1.6.3", @@ -5386,7 +5386,7 @@ "compact-encoding": "^2.12.0", "corestore": "^6.8.4", "debug": "^4.3.4", - "drizzle-orm": "0.28.2", + "drizzle-orm": "^0.30.8", "fastify": ">= 4", "fastify-plugin": "^4.5.0", "hyperblobs": "2.3.0", @@ -5411,15 +5411,15 @@ "throttle-debounce": "^5.0.0", "tiny-typed-emitter": "^2.1.0", "type-fest": "^4.5.0", - "undici": "^6.7.0", + "undici": "^6.13.0", "varint": "^6.0.0", "yauzl-promise": "^4.0.0" } }, "node_modules/@mapeo/core/node_modules/@mapeo/schema": { - "version": "3.0.0-next.14", - "resolved": "https://registry.npmjs.org/@mapeo/schema/-/schema-3.0.0-next.14.tgz", - "integrity": "sha512-i0AUHbwMxUyggk6SDURxLPXOVCWlLAszSKUYm2fviQXYrGNcUALniw8JBn3x5jzfCFW1xrTUNhIcnt4IuF95mA==", + "version": "3.0.0-next.15", + "resolved": "https://registry.npmjs.org/@mapeo/schema/-/schema-3.0.0-next.15.tgz", + "integrity": "sha512-7g5GLkDhZLeStPIkVkTqElidng9cKtzNDJSQww5+Z7PWpkniMLDJYLki9TchEJEmPoAgwxgULNBiC69cMnNo9Q==", "dependencies": { "@json-schema-tools/dereferencer": "^1.6.1", "ajv": "^8.12.0", @@ -5460,15 +5460,15 @@ } }, "node_modules/@mapeo/core/node_modules/glob": { - "version": "10.3.10", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", - "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "version": "10.3.12", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", + "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==", "dependencies": { "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", + "jackspeak": "^2.3.6", "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" + "minipass": "^7.0.4", + "path-scurry": "^1.10.2" }, "bin": { "glob": "dist/esm/bin.mjs" @@ -5524,9 +5524,9 @@ } }, "node_modules/@mapeo/core/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -5613,9 +5613,9 @@ } }, "node_modules/@mapeo/ipc": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@mapeo/ipc/-/ipc-0.2.0.tgz", - "integrity": "sha512-qAkKlwpIJw0Srr9pR4w9pbluIADzkMHpRbOuxSYY5+fonmNuEc8esRXWwvaL4SeBEIoUsuIOpWuEDOIoYlRk6Q==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@mapeo/ipc/-/ipc-0.3.0.tgz", + "integrity": "sha512-8OdARTEBgCFCXrcJqwPuz397i10MiCIGG1Y9SSZ7myzN2o72lbuVAu7SX/D2gbHrVVD2tuia9EwaDBVsznas2w==", "dependencies": { "eventemitter3": "^5.0.1", "p-defer": "^4.0.0", @@ -5625,7 +5625,7 @@ "node": ">=18.17.1" }, "peerDependencies": { - "@mapeo/core": "9.0.0-alpha.6" + "@mapeo/core": "9.0.0-alpha.7" } }, "node_modules/@mapeo/schema": { @@ -11666,26 +11666,33 @@ } }, "node_modules/drizzle-orm": { - "version": "0.28.2", - "license": "Apache-2.0", + "version": "0.30.8", + "resolved": "https://registry.npmjs.org/drizzle-orm/-/drizzle-orm-0.30.8.tgz", + "integrity": "sha512-9pBJA0IjnpPpzZ6s9jlS1CQAbKoBmbn2GJesPhXaVblAA/joOJ4AWWevYcqvLGj9SvThBAl7WscN8Zwgg5mnTw==", "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=3", + "@electric-sql/pglite": ">=0.1.1", "@libsql/client": "*", "@neondatabase/serverless": ">=0.1", + "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1", "@types/better-sqlite3": "*", "@types/pg": "*", + "@types/react": ">=18", "@types/sql.js": "*", - "@vercel/postgres": "*", + "@vercel/postgres": ">=0.8.0", + "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", + "expo-sqlite": ">=13.2.0", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", + "react": ">=18", "sql.js": ">=1", "sqlite3": ">=5" }, @@ -11696,12 +11703,18 @@ "@cloudflare/workers-types": { "optional": true }, + "@electric-sql/pglite": { + "optional": true + }, "@libsql/client": { "optional": true }, "@neondatabase/serverless": { "optional": true }, + "@op-engineering/op-sqlite": { + "optional": true + }, "@opentelemetry/api": { "optional": true }, @@ -11714,18 +11727,27 @@ "@types/pg": { "optional": true }, + "@types/react": { + "optional": true + }, "@types/sql.js": { "optional": true }, "@vercel/postgres": { "optional": true }, + "@xata.io/client": { + "optional": true + }, "better-sqlite3": { "optional": true }, "bun-types": { "optional": true }, + "expo-sqlite": { + "optional": true + }, "knex": { "optional": true }, @@ -11741,6 +11763,9 @@ "postgres": { "optional": true }, + "react": { + "optional": true + }, "sql.js": { "optional": true }, @@ -14913,8 +14938,9 @@ } }, "node_modules/jackspeak": { - "version": "2.3.5", - "license": "BlueOak-1.0.0", + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", + "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", "dependencies": { "@isaacs/cliui": "^8.0.2" }, @@ -19456,10 +19482,11 @@ "license": "MIT" }, "node_modules/path-scurry": { - "version": "1.10.1", - "license": "BlueOak-1.0.0", + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz", + "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==", "dependencies": { - "lru-cache": "^9.1.1 || ^10.0.0", + "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { @@ -19470,8 +19497,9 @@ } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.0.1", - "license": "ISC", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", + "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", "engines": { "node": "14 || >=16.14" } @@ -23332,9 +23360,9 @@ } }, "node_modules/undici": { - "version": "6.10.1", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.10.1.tgz", - "integrity": "sha512-kSzmWrOx3XBKTgPm4Tal8Hyl3yf+hzlA00SAf4goxv8LZYafKmS6gJD/7Fe5HH/DMNiFTRXvkwhLo7mUn5fuQQ==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.13.0.tgz", + "integrity": "sha512-Q2rtqmZWrbP8nePMq7mOJIN98M0fYvSgV89vwl/BQRT4mDOeY2GXZngfGpcBBhtky3woM7G24wZV3Q304Bv6cw==", "engines": { "node": ">=18.0" } diff --git a/package.json b/package.json index d1aeb1b87..48daa4bdd 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "@formatjs/intl-pluralrules": "^5.2.4", "@formatjs/intl-relativetimeformat": "^11.2.4", "@gorhom/bottom-sheet": "^4.5.1", - "@mapeo/ipc": "^0.2.0", + "@mapeo/ipc": "^0.3.0", "@react-native-community/hooks": "^2.8.0", "@react-native-community/netinfo": "11.1.0", "@react-native-picker/picker": "2.6.1", @@ -92,7 +92,7 @@ "@babel/preset-env": "^7.20.0", "@babel/runtime": "^7.20.0", "@formatjs/cli": "^6.2.0", - "@mapeo/core": "^9.0.0-alpha.6", + "@mapeo/core": "^9.0.0-alpha.7", "@mapeo/schema": "3.0.0-next.13", "@react-native-community/cli": "^12.3.6", "@react-native/babel-preset": "^0.73.21", diff --git a/src/backend/package-lock.json b/src/backend/package-lock.json index bd7ce4ef4..b79dfcbee 100644 --- a/src/backend/package-lock.json +++ b/src/backend/package-lock.json @@ -10,8 +10,8 @@ "hasInstallScript": true, "license": "MIT", "dependencies": { - "@mapeo/core": "^9.0.0-alpha.6", - "@mapeo/ipc": "^0.2.0", + "@mapeo/core": "^9.0.0-alpha.7", + "@mapeo/ipc": "^0.3.0", "debug": "^4.3.4" }, "devDependencies": { diff --git a/src/backend/package.json b/src/backend/package.json index af8204ed2..285adddfe 100644 --- a/src/backend/package.json +++ b/src/backend/package.json @@ -13,8 +13,8 @@ "author": "Digital Democracy", "license": "MIT", "dependencies": { - "@mapeo/core": "^9.0.0-alpha.6", - "@mapeo/ipc": "^0.2.0", + "@mapeo/core": "^9.0.0-alpha.7", + "@mapeo/ipc": "^0.3.0", "debug": "^4.3.4" }, "devDependencies": { diff --git a/src/frontend/hooks/server/track.ts b/src/frontend/hooks/server/track.ts new file mode 100644 index 000000000..c7047202c --- /dev/null +++ b/src/frontend/hooks/server/track.ts @@ -0,0 +1,21 @@ +import {MapeoProject} from '@mapeo/core/dist/mapeo-project'; +import {useQueryClient, useMutation} from '@tanstack/react-query'; +import {useProject} from './projects'; + +export const TRACK_KEY = 'track'; + +export function useCreateTrack() { + const queryClient = useQueryClient(); + const project = useProject(); + return useMutation({ + mutationFn: async ( + params: Parameters[0], + ) => { + if (!project) throw new Error('Project instance does not exist'); + return project.track.create(params); + }, + onSuccess: () => { + queryClient.invalidateQueries({queryKey: [TRACK_KEY]}); + }, + }); +} diff --git a/src/frontend/screens/MapScreen/track/saveTrack/TrackDescriptionField.tsx b/src/frontend/screens/MapScreen/track/saveTrack/TrackDescriptionField.tsx index 915812ca2..70816513e 100644 --- a/src/frontend/screens/MapScreen/track/saveTrack/TrackDescriptionField.tsx +++ b/src/frontend/screens/MapScreen/track/saveTrack/TrackDescriptionField.tsx @@ -43,7 +43,6 @@ const styles = StyleSheet.create({ fontSize: 20, paddingHorizontal: 20, color: 'black', - // backgroundColor: 'red', alignItems: 'flex-start', justifyContent: 'flex-start', textAlignVertical: 'top', From 4497d29d0ca159115b6f7552fda98f8a0e5ca28e Mon Sep 17 00:00:00 2001 From: bohdanprog Date: Thu, 18 Apr 2024 14:29:06 +0200 Subject: [PATCH 070/123] add discard button icon --- src/frontend/images/delete.svg | 10 ++++++++++ .../track/saveTrack/DiscardTrackModal.tsx | 20 ++++++++++++++----- 2 files changed, 25 insertions(+), 5 deletions(-) create mode 100644 src/frontend/images/delete.svg diff --git a/src/frontend/images/delete.svg b/src/frontend/images/delete.svg new file mode 100644 index 000000000..3ebb3bf2c --- /dev/null +++ b/src/frontend/images/delete.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/frontend/screens/MapScreen/track/saveTrack/DiscardTrackModal.tsx b/src/frontend/screens/MapScreen/track/saveTrack/DiscardTrackModal.tsx index f128d0ce5..092377302 100644 --- a/src/frontend/screens/MapScreen/track/saveTrack/DiscardTrackModal.tsx +++ b/src/frontend/screens/MapScreen/track/saveTrack/DiscardTrackModal.tsx @@ -12,8 +12,8 @@ import {Button} from '../../../../sharedComponents/Button'; import {defineMessages, useIntl} from 'react-intl'; import ErrorIcon from '../../../../images/Error.svg'; import {COMAPEO_BLUE, MAGENTA, WHITE} from '../../../../lib/styles'; -import {TabName} from '../../../../Navigation/types'; import {useNavigationFromHomeTabs} from '../../../../hooks/useNavigationWithTypes'; +import DiscardIcon from '../../../../images/delete.svg'; export interface DiscardTrackModal { bottomSheetRef: React.RefObject; @@ -79,9 +79,12 @@ export const DiscardTrackModal: FC = ({bottomSheetRef}) => { onClose(); }} style={styles.discardButton}> - - {formatMessage(m.discardTrackDiscardButton)} - + + + + {formatMessage(m.discardTrackDiscardButton)} + + - - - - + + , + }, + { + onPress: handleContinue, + text: formatMessage(m.discardTrackDefaultButton), + variation: 'outlined', + }, + ]} + title={formatMessage(m.discardTrackTitle)} + description={formatMessage(m.discardTrackDescription)} + icon={} + /> ); }; export const styles = StyleSheet.create({ - modal: {borderBottomLeftRadius: 0, borderBottomRightRadius: 0}, - wrapper: { - paddingTop: 30, - paddingHorizontal: 40, - paddingBottom: 20, - alignItems: 'center', - display: 'flex', - justifyContent: 'center', - }, image: {marginBottom: 15}, - title: {fontSize: 24, fontWeight: 'bold', textAlign: 'center'}, - description: {fontSize: 16, textAlign: 'center', marginVertical: 10}, - discardButton: { - backgroundColor: MAGENTA, - marginBottom: 20, - }, - discardButtonWrapper: { - flexDirection: 'row', - }, - discardButtonText: { - marginLeft: 10, - color: WHITE, - }, - buttonText: { - fontWeight: '700', - fontSize: 16, - }, - defaultButtonText: { - color: COMAPEO_BLUE, - }, - defaultButton: { - borderWidth: 1.5, - borderColor: '#CCCCD6', - backgroundColor: 'transparent', - }, }); diff --git a/src/frontend/screens/MapScreen/track/saveTrack/SaveTrackHeader.tsx b/src/frontend/screens/MapScreen/track/saveTrack/SaveTrackHeader.tsx index 0b5d51ed6..255c84cab 100644 --- a/src/frontend/screens/MapScreen/track/saveTrack/SaveTrackHeader.tsx +++ b/src/frontend/screens/MapScreen/track/saveTrack/SaveTrackHeader.tsx @@ -2,7 +2,6 @@ import React, {FC} from 'react'; import {View, Image, StyleSheet, Pressable} from 'react-native'; import {Text} from '../../../../sharedComponents/Text'; import Close from '../../../../images/close.svg'; -import {BottomSheetModalMethods} from '@gorhom/bottom-sheet/lib/typescript/types'; import {useCreateTrack} from '../../../../hooks/server/track'; import {useCurrentTrackStore} from '../../../../hooks/tracks/useCurrentTrackStore'; import {DateTime} from 'luxon'; @@ -19,12 +18,12 @@ const m = defineMessages({ }); export interface SaveTrackHeader { - bottomSheetRef: React.RefObject; + openSheet: () => void; description: string; } export const SaveTrackHeader: FC = ({ - bottomSheetRef, + openSheet, description, }) => { const saveTrack = useCreateTrack(); @@ -67,9 +66,7 @@ export const SaveTrackHeader: FC = ({ return ( - bottomSheetRef.current?.present()}> + {t(m.trackEditScreenTitle)} diff --git a/src/frontend/screens/MapScreen/track/saveTrack/TrackDescriptionField.tsx b/src/frontend/screens/MapScreen/track/saveTrack/TrackDescriptionField.tsx index c591d3e6e..47a48401d 100644 --- a/src/frontend/screens/MapScreen/track/saveTrack/TrackDescriptionField.tsx +++ b/src/frontend/screens/MapScreen/track/saveTrack/TrackDescriptionField.tsx @@ -41,7 +41,6 @@ const styles = StyleSheet.create({ flex: 1, minHeight: 100, fontSize: 20, - paddingHorizontal: 20, color: 'black', alignItems: 'flex-start', justifyContent: 'flex-start', From c20cb74bcc66866a3f9af63d4f62ca4e529deebb Mon Sep 17 00:00:00 2001 From: bohdanprog Date: Tue, 30 Apr 2024 09:30:17 +0200 Subject: [PATCH 119/123] create share component DiscardModal, fixed dependency in useTracking hook --- .../track/saveTrack/DiscardTrackModal.tsx | 86 ------------------- .../sharedComponents/DiscardModal.tsx | 27 ++++++ 2 files changed, 27 insertions(+), 86 deletions(-) delete mode 100644 src/frontend/screens/MapScreen/track/saveTrack/DiscardTrackModal.tsx create mode 100644 src/frontend/sharedComponents/DiscardModal.tsx diff --git a/src/frontend/screens/MapScreen/track/saveTrack/DiscardTrackModal.tsx b/src/frontend/screens/MapScreen/track/saveTrack/DiscardTrackModal.tsx deleted file mode 100644 index 6ba45f9ea..000000000 --- a/src/frontend/screens/MapScreen/track/saveTrack/DiscardTrackModal.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import React, {FC} from 'react'; -import {BottomSheetModalMethods} from '@gorhom/bottom-sheet/lib/typescript/types'; -import {StyleSheet} from 'react-native'; -import {defineMessages, useIntl} from 'react-intl'; -import ErrorIcon from '../../../../images/Error.svg'; -import {useNavigationFromHomeTabs} from '../../../../hooks/useNavigationWithTypes'; -import {useCurrentTrackStore} from '../../../../hooks/tracks/useCurrentTrackStore'; -import {TabName} from '../../../../Navigation/types'; -import DiscardIcon from '../../../../images/delete.svg'; -import { - BottomSheetContent, - BottomSheetModal, -} from '../../../../sharedComponents/BottomSheetModal'; - -export interface DiscardTrackModal { - bottomSheetRef: React.RefObject; - isOpen: boolean; -} - -const m = defineMessages({ - discardTrackTitle: { - id: 'Modal.DiscardTrack.title', - defaultMessage: 'Discard Track?', - }, - discardTrackDescription: { - id: 'Modal.DiscardTrack.description', - defaultMessage: 'Your Track will not be saved.\n This cannot be undone.', - }, - discardTrackDiscardButton: { - id: 'Modal.GPSDisable.discardButton', - defaultMessage: 'Discard Track', - }, - discardTrackDefaultButton: { - id: 'Modal.GPSDisable.defaultButton', - defaultMessage: 'Continue Editing', - }, -}); - -export const DiscardTrackModal: FC = ({ - bottomSheetRef, - isOpen, -}) => { - const {formatMessage} = useIntl(); - const navigation = useNavigationFromHomeTabs(); - const clearCurrentTrack = useCurrentTrackStore( - state => state.clearCurrentTrack, - ); - const handleDiscard = () => { - bottomSheetRef.current?.close(); - navigation.navigate(TabName.Map); - clearCurrentTrack(); - }; - - const handleContinue = () => { - bottomSheetRef.current?.close(); - }; - - return ( - - , - }, - { - onPress: handleContinue, - text: formatMessage(m.discardTrackDefaultButton), - variation: 'outlined', - }, - ]} - title={formatMessage(m.discardTrackTitle)} - description={formatMessage(m.discardTrackDescription)} - icon={} - /> - - ); -}; - -export const styles = StyleSheet.create({ - image: {marginBottom: 15}, -}); diff --git a/src/frontend/sharedComponents/DiscardModal.tsx b/src/frontend/sharedComponents/DiscardModal.tsx new file mode 100644 index 000000000..f6374600c --- /dev/null +++ b/src/frontend/sharedComponents/DiscardModal.tsx @@ -0,0 +1,27 @@ +import React, {FC, ReactNode, RefObject} from 'react'; +import {BottomSheetModalMethods} from '@gorhom/bottom-sheet/lib/typescript/types'; +import { + BottomSheetContent, + BottomSheetModal, +} from '../../../../sharedComponents/BottomSheetModal'; +import {ActionButtonConfig} from '../../../../sharedComponents/BottomSheet/Content.tsx'; + +export interface DiscardModal { + bottomSheetRef: RefObject; + isOpen: boolean; + title: string; + description: string; + buttonConfigs: ActionButtonConfig[]; + icon?: ReactNode; + loading?: boolean; +} + +export const DiscardModal: FC = props => { + const {bottomSheetRef, isOpen} = props; + + return ( + + + + ); +}; From b19e32b25bb2a7c5d495cd98d4b3252e7a27c2f2 Mon Sep 17 00:00:00 2001 From: bohdanprog Date: Tue, 30 Apr 2024 09:31:36 +0200 Subject: [PATCH 120/123] add edit track screen, add update track method --- src/frontend/hooks/tracks/useTracking.ts | 2 +- .../MapScreen/track/SaveTrackScreen.tsx | 98 ++++++++++++++----- .../sharedComponents/BottomSheet/Content.tsx | 2 +- 3 files changed, 76 insertions(+), 26 deletions(-) diff --git a/src/frontend/hooks/tracks/useTracking.ts b/src/frontend/hooks/tracks/useTracking.ts index c84fceef7..5db003023 100644 --- a/src/frontend/hooks/tracks/useTracking.ts +++ b/src/frontend/hooks/tracks/useTracking.ts @@ -41,7 +41,7 @@ export function useTracking() { } }, ); - }, []); + }, [addNewLocations]); const startTracking = useCallback(async () => { if (isTracking) { diff --git a/src/frontend/screens/MapScreen/track/SaveTrackScreen.tsx b/src/frontend/screens/MapScreen/track/SaveTrackScreen.tsx index e1b7d840b..d09fe26a0 100644 --- a/src/frontend/screens/MapScreen/track/SaveTrackScreen.tsx +++ b/src/frontend/screens/MapScreen/track/SaveTrackScreen.tsx @@ -1,7 +1,7 @@ import React, {useState} from 'react'; import {SafeAreaView, ScrollView, StyleSheet, View} from 'react-native'; import {SaveTrackHeader} from './saveTrack/SaveTrackHeader'; -import {DiscardTrackModal} from './saveTrack/DiscardTrackModal'; +import {DiscardModal} from '../../../sharedComponents/DiscardModal.tsx'; import {BottomSheet} from '../../../sharedComponents/BottomSheet/BottomSheet'; import PhotoIcon from '../../../images/camera.svg'; import DetailsIcon from '../../../images/details.svg'; @@ -9,30 +9,18 @@ import TrackIcon from '../../../images/Track.svg'; import {defineMessages, useIntl} from 'react-intl'; import {Text} from '../../../sharedComponents/Text'; import {TrackDescriptionField} from './saveTrack/TrackDescriptionField'; -import {NavigationProp} from '@react-navigation/native'; import {useBottomSheetModal} from '../../../sharedComponents/BottomSheetModal'; +import DiscardIcon from '../../../images/delete.svg'; +import ErrorIcon from '../../../images/Error.svg'; +import {TabName} from '../../../Navigation/types.ts'; +import {useCurrentTrackStore} from '../../../hooks/tracks/useCurrentTrackStore.ts'; +import {useNavigationFromHomeTabs} from '../../../hooks/useNavigationWithTypes.ts'; -const m = defineMessages({ - newTitle: { - id: 'screens.SaveTrack.track', - defaultMessage: 'Track', - description: 'Category title for new track screen', - }, - detailsButton: { - id: 'screens.SaveTrack.TrackEditView.saveTrackDetails', - defaultMessage: 'Details', - description: 'Button label for check details', - }, - photoButton: { - id: 'screens.SaveTrack.TrackEditView.saveTrackCamera', - defaultMessage: 'Camera', - description: 'Button label for adding photo', - }, -}); - -export const SaveTrackScreen: React.FC<{navigation: NavigationProp}> = ({ - navigation, -}) => { +export const SaveTrackScreen = () => { + const navigation = useNavigationFromHomeTabs(); + const clearCurrentTrack = useCurrentTrackStore( + state => state.clearCurrentTrack, + ); const {formatMessage: t} = useIntl(); const [description, setDescription] = useState(''); const {sheetRef, isOpen, openSheet} = useBottomSheetModal({ @@ -55,6 +43,13 @@ export const SaveTrackScreen: React.FC<{navigation: NavigationProp}> = ({ onPress: () => {}, }, ]; + + const handleDiscard = () => { + sheetRef.current?.close(); + navigation.navigate(TabName.Map); + clearCurrentTrack(); + }; + return ( @@ -69,7 +64,27 @@ export const SaveTrackScreen: React.FC<{navigation: NavigationProp}> = ({ description={description} setDescription={setDescription} /> - + , + }, + { + onPress: () => sheetRef.current?.close(), + text: t(m.discardTrackDefaultButton), + variation: 'outlined', + }, + ]} + title={t(m.discardTrackTitle)} + description={t(m.discardTrackDescription)} + icon={} + /> @@ -78,6 +93,7 @@ export const SaveTrackScreen: React.FC<{navigation: NavigationProp}> = ({ const styles = StyleSheet.create({ icon: {width: 30, height: 30}, + image: {marginBottom: 15}, titleText: {fontSize: 20, fontWeight: '700'}, container: { flex: 1, @@ -99,3 +115,37 @@ const styles = StyleSheet.create({ alignContent: 'stretch', }, }); + +const m = defineMessages({ + newTitle: { + id: 'screens.SaveTrack.track', + defaultMessage: 'Track', + description: 'Category title for new track screen', + }, + detailsButton: { + id: 'screens.SaveTrack.TrackEditView.saveTrackDetails', + defaultMessage: 'Details', + description: 'Button label for check details', + }, + photoButton: { + id: 'screens.SaveTrack.TrackEditView.saveTrackCamera', + defaultMessage: 'Camera', + description: 'Button label for adding photo', + }, + discardTrackTitle: { + id: 'Modal.DiscardTrack.title', + defaultMessage: 'Discard Track?', + }, + discardTrackDescription: { + id: 'Modal.DiscardTrack.description', + defaultMessage: 'Your Track will not be saved.\n This cannot be undone.', + }, + discardTrackDiscardButton: { + id: 'Modal.GPSDisable.discardButton', + defaultMessage: 'Discard Track', + }, + discardTrackDefaultButton: { + id: 'Modal.GPSDisable.defaultButton', + defaultMessage: 'Continue Editing', + }, +}); diff --git a/src/frontend/sharedComponents/BottomSheet/Content.tsx b/src/frontend/sharedComponents/BottomSheet/Content.tsx index c41837243..8400278c5 100644 --- a/src/frontend/sharedComponents/BottomSheet/Content.tsx +++ b/src/frontend/sharedComponents/BottomSheet/Content.tsx @@ -23,7 +23,7 @@ interface SecondaryActionButtonConfig extends BaseActionButtonConfig { variation: 'outlined'; } -type ActionButtonConfig = +export type ActionButtonConfig = | PrimaryActionButtonConfig | SecondaryActionButtonConfig; From 0fa31af2733666bfc16160f606c51801be34e2ef Mon Sep 17 00:00:00 2001 From: bohdanprog Date: Tue, 30 Apr 2024 09:31:43 +0200 Subject: [PATCH 121/123] add edit track screen, add update track method --- src/frontend/sharedComponents/DiscardModal.tsx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/frontend/sharedComponents/DiscardModal.tsx b/src/frontend/sharedComponents/DiscardModal.tsx index f6374600c..eae7c809d 100644 --- a/src/frontend/sharedComponents/DiscardModal.tsx +++ b/src/frontend/sharedComponents/DiscardModal.tsx @@ -1,10 +1,7 @@ import React, {FC, ReactNode, RefObject} from 'react'; import {BottomSheetModalMethods} from '@gorhom/bottom-sheet/lib/typescript/types'; -import { - BottomSheetContent, - BottomSheetModal, -} from '../../../../sharedComponents/BottomSheetModal'; -import {ActionButtonConfig} from '../../../../sharedComponents/BottomSheet/Content.tsx'; +import {BottomSheetContent, BottomSheetModal} from './BottomSheetModal'; +import {ActionButtonConfig} from './BottomSheet/Content.tsx'; export interface DiscardModal { bottomSheetRef: RefObject; From 524b58a388e64c2864e3bca3c7230738d7be52d3 Mon Sep 17 00:00:00 2001 From: bohdanprog Date: Tue, 30 Apr 2024 13:38:24 +0200 Subject: [PATCH 122/123] add back handler on Save Track Screen --- .../MapScreen/track/SaveTrackScreen.tsx | 33 ++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/src/frontend/screens/MapScreen/track/SaveTrackScreen.tsx b/src/frontend/screens/MapScreen/track/SaveTrackScreen.tsx index d09fe26a0..bf5a57101 100644 --- a/src/frontend/screens/MapScreen/track/SaveTrackScreen.tsx +++ b/src/frontend/screens/MapScreen/track/SaveTrackScreen.tsx @@ -1,5 +1,11 @@ import React, {useState} from 'react'; -import {SafeAreaView, ScrollView, StyleSheet, View} from 'react-native'; +import { + BackHandler, + SafeAreaView, + ScrollView, + StyleSheet, + View, +} from 'react-native'; import {SaveTrackHeader} from './saveTrack/SaveTrackHeader'; import {DiscardModal} from '../../../sharedComponents/DiscardModal.tsx'; import {BottomSheet} from '../../../sharedComponents/BottomSheet/BottomSheet'; @@ -15,6 +21,7 @@ import ErrorIcon from '../../../images/Error.svg'; import {TabName} from '../../../Navigation/types.ts'; import {useCurrentTrackStore} from '../../../hooks/tracks/useCurrentTrackStore.ts'; import {useNavigationFromHomeTabs} from '../../../hooks/useNavigationWithTypes.ts'; +import {useFocusEffect} from '@react-navigation/native'; export const SaveTrackScreen = () => { const navigation = useNavigationFromHomeTabs(); @@ -23,7 +30,7 @@ export const SaveTrackScreen = () => { ); const {formatMessage: t} = useIntl(); const [description, setDescription] = useState(''); - const {sheetRef, isOpen, openSheet} = useBottomSheetModal({ + const {sheetRef, isOpen, openSheet, closeSheet} = useBottomSheetModal({ openOnMount: false, }); @@ -45,11 +52,29 @@ export const SaveTrackScreen = () => { ]; const handleDiscard = () => { - sheetRef.current?.close(); + closeSheet(); navigation.navigate(TabName.Map); clearCurrentTrack(); }; + // disables back button + useFocusEffect( + React.useCallback(() => { + const disableBack = () => { + openSheet(); + }; + const subscription = BackHandler.addEventListener( + 'hardwareBackPress', + () => { + disableBack(); + return true; + }, + ); + + return () => subscription.remove(); + }, [openSheet]), + ); + return ( @@ -76,7 +101,7 @@ export const SaveTrackScreen = () => { icon: , }, { - onPress: () => sheetRef.current?.close(), + onPress: closeSheet, text: t(m.discardTrackDefaultButton), variation: 'outlined', }, From af009b00798d08021983e5d1a8fd280adbf53410 Mon Sep 17 00:00:00 2001 From: bohdanprog Date: Tue, 30 Apr 2024 15:21:15 +0200 Subject: [PATCH 123/123] changed save track screen, using header from react-native-navigation instead custom header --- messages/en.json | 2 +- .../Navigation/ScreenGroups/AppScreens.tsx | 8 +- .../MapScreen/track/SaveTrackScreen.tsx | 29 +++++- .../track/saveTrack/SaveTrackButton.tsx | 62 ++++++++++++ .../track/saveTrack/SaveTrackHeader.tsx | 98 ------------------- .../track/saveTrack/TrackDescriptionField.tsx | 1 + 6 files changed, 89 insertions(+), 111 deletions(-) create mode 100644 src/frontend/screens/MapScreen/track/saveTrack/SaveTrackButton.tsx delete mode 100644 src/frontend/screens/MapScreen/track/saveTrack/SaveTrackHeader.tsx diff --git a/messages/en.json b/messages/en.json index 9f8cc8ea1..ba1064190 100644 --- a/messages/en.json +++ b/messages/en.json @@ -44,7 +44,7 @@ "message": "Start Tracks" }, "Modal.GPSEnable.button.loading": { - "message": "Loading..." + "message": "Loading…" }, "Modal.GPSEnable.button.stop": { "message": "Stop Tracks" diff --git a/src/frontend/Navigation/ScreenGroups/AppScreens.tsx b/src/frontend/Navigation/ScreenGroups/AppScreens.tsx index e48ff9a76..96a5e2f45 100644 --- a/src/frontend/Navigation/ScreenGroups/AppScreens.tsx +++ b/src/frontend/Navigation/ScreenGroups/AppScreens.tsx @@ -367,13 +367,7 @@ export const createDefaultScreenGroup = ( component={GpsModal} options={createGpsModalNavigationOptions({intl})} /> - + { const navigation = useNavigationFromHomeTabs(); @@ -57,9 +59,22 @@ export const SaveTrackScreen = () => { clearCurrentTrack(); }; + useFocusEffect( + useCallback(() => { + navigation.setOptions({ + headerLeft: () => ( + + + + ), + headerRight: () => , + }); + }, [description, navigation, openSheet]), + ); + // disables back button useFocusEffect( - React.useCallback(() => { + useCallback(() => { const disableBack = () => { openSheet(); }; @@ -77,7 +92,6 @@ export const SaveTrackScreen = () => { return ( - @@ -141,7 +155,12 @@ const styles = StyleSheet.create({ }, }); -const m = defineMessages({ +export const m = defineMessages({ + trackEditScreenTitle: { + id: 'screens.SaveTrack.TrackEditView.title', + defaultMessage: 'New Track', + description: 'Title for new track screen', + }, newTitle: { id: 'screens.SaveTrack.track', defaultMessage: 'Track', diff --git a/src/frontend/screens/MapScreen/track/saveTrack/SaveTrackButton.tsx b/src/frontend/screens/MapScreen/track/saveTrack/SaveTrackButton.tsx new file mode 100644 index 000000000..1f4ff6b15 --- /dev/null +++ b/src/frontend/screens/MapScreen/track/saveTrack/SaveTrackButton.tsx @@ -0,0 +1,62 @@ +import {Image, Pressable, StyleSheet} from 'react-native'; +import React, {FC} from 'react'; +import {DateTime} from 'luxon'; +import {TabName} from '../../../../Navigation/types.ts'; +import {useCreateTrack} from '../../../../hooks/server/track.ts'; +import {useCurrentTrackStore} from '../../../../hooks/tracks/useCurrentTrackStore.ts'; +import {useNavigationFromHomeTabs} from '../../../../hooks/useNavigationWithTypes.ts'; + +interface SaveTrackButton { + description: string; +} + +export const SaveTrackButton: FC = ({description}) => { + const saveTrack = useCreateTrack(); + const navigation = useNavigationFromHomeTabs(); + const currentTrack = useCurrentTrackStore(); + + const handleSaveClick = () => { + saveTrack.mutate( + { + schemaName: 'track', + attachments: [], + refs: currentTrack.observations.map(observationId => ({ + id: observationId, + type: 'observation', + })), + tags: { + notes: description, + }, + locations: currentTrack.locationHistory.map(loc => { + return { + coords: { + latitude: loc.latitude, + longitude: loc.longitude, + }, + mocked: false, + timestamp: DateTime.fromMillis(loc.timestamp).toISO()!, + }; + }), + }, + { + onSuccess: () => { + navigation.navigate(TabName.Map); + currentTrack.clearCurrentTrack(); + }, + }, + ); + }; + + return ( + + + + ); +}; + +const styles = StyleSheet.create({ + completeIcon: {width: 30, height: 30}, +}); diff --git a/src/frontend/screens/MapScreen/track/saveTrack/SaveTrackHeader.tsx b/src/frontend/screens/MapScreen/track/saveTrack/SaveTrackHeader.tsx deleted file mode 100644 index 255c84cab..000000000 --- a/src/frontend/screens/MapScreen/track/saveTrack/SaveTrackHeader.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import React, {FC} from 'react'; -import {View, Image, StyleSheet, Pressable} from 'react-native'; -import {Text} from '../../../../sharedComponents/Text'; -import Close from '../../../../images/close.svg'; -import {useCreateTrack} from '../../../../hooks/server/track'; -import {useCurrentTrackStore} from '../../../../hooks/tracks/useCurrentTrackStore'; -import {DateTime} from 'luxon'; -import {TabName} from '../../../../Navigation/types'; -import {useNavigationFromHomeTabs} from '../../../../hooks/useNavigationWithTypes'; -import {defineMessages, useIntl} from 'react-intl'; - -const m = defineMessages({ - trackEditScreenTitle: { - id: 'screens.SaveTrack.TrackEditView.title', - defaultMessage: 'New Track', - description: 'Title for new track screen', - }, -}); - -export interface SaveTrackHeader { - openSheet: () => void; - description: string; -} - -export const SaveTrackHeader: FC = ({ - openSheet, - description, -}) => { - const saveTrack = useCreateTrack(); - const currentTrack = useCurrentTrackStore(); - const navigation = useNavigationFromHomeTabs(); - - const {formatMessage: t} = useIntl(); - - const handleSaveClick = () => { - saveTrack.mutate( - { - schemaName: 'track', - attachments: [], - refs: currentTrack.observations.map(observationId => ({ - id: observationId, - type: 'observation', - })), - tags: { - notes: description, - }, - locations: currentTrack.locationHistory.map(loc => { - return { - coords: { - latitude: loc.latitude, - longitude: loc.longitude, - }, - mocked: false, - timestamp: DateTime.fromMillis(loc.timestamp).toISO()!, - }; - }), - }, - { - onSuccess: () => { - navigation.navigate(TabName.Map); - currentTrack.clearCurrentTrack(); - }, - }, - ); - }; - return ( - - - - - - {t(m.trackEditScreenTitle)} - - - - - - ); -}; - -const styles = StyleSheet.create({ - container: { - borderBottomWidth: 1, - borderBottomColor: '#EDEDED', - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', - paddingVertical: 10, - paddingHorizontal: 20, - }, - closeWrapper: {flexDirection: 'row', alignItems: 'center'}, - closeIcon: {width: 15, height: 15, marginRight: 20}, - text: {fontSize: 16, fontWeight: 'bold'}, - completeIcon: {width: 30, height: 30}, -}); diff --git a/src/frontend/screens/MapScreen/track/saveTrack/TrackDescriptionField.tsx b/src/frontend/screens/MapScreen/track/saveTrack/TrackDescriptionField.tsx index 47a48401d..cbab06d1e 100644 --- a/src/frontend/screens/MapScreen/track/saveTrack/TrackDescriptionField.tsx +++ b/src/frontend/screens/MapScreen/track/saveTrack/TrackDescriptionField.tsx @@ -39,6 +39,7 @@ export const TrackDescriptionField: React.FC = ({ const styles = StyleSheet.create({ textInput: { flex: 1, + paddingVertical: 20, minHeight: 100, fontSize: 20, color: 'black',