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 01/79] 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 02/79] 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 03/79] 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 04/79] 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 05/79] 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 06/79] 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 07/79] 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 08/79] 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 09/79] 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 10/79] 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 11/79] 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 12/79] 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 13/79] 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 14/79] 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 15/79] 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 16/79] 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 17/79] 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 18/79] 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 19/79] 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 20/79] 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 21/79] 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 22/79] 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 23/79] 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 24/79] 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 25/79] 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 26/79] 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 27/79] 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 28/79] 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 29/79] 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 30/79] 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 31/79] 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 32/79] 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 33/79] 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 34/79] 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 35/79] 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 36/79] 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 37/79] 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 38/79] 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 39/79] 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 40/79] 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 41/79] 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 42/79] 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 43/79] 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 44/79] 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 45/79] 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 46/79] 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 47/79] 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 48/79] 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 49/79] 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 50/79] 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 51/79] 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 52/79] 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 53/79] 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 54/79] 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 55/79] 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 6fa47da5f8e1f1196612a8b34b8c2ffcfa8e8c31 Mon Sep 17 00:00:00 2001 From: bohdanprog Date: Wed, 17 Apr 2024 15:25:53 +0200 Subject: [PATCH 56/79] 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 57/79] 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 58/79] 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 59/79] 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 60/79] 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 61/79] 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 62/79] 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 328e4c65c74c3c8f14274de433f75e18ad969826 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20Stefa=C5=84czyk?= Date: Fri, 19 Apr 2024 12:16:24 +0200 Subject: [PATCH 63/79] code quality improvement --- src/frontend/contexts/SharedLocationContext.tsx | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/frontend/contexts/SharedLocationContext.tsx b/src/frontend/contexts/SharedLocationContext.tsx index 8700d70d6..bf9bfceb2 100644 --- a/src/frontend/contexts/SharedLocationContext.tsx +++ b/src/frontend/contexts/SharedLocationContext.tsx @@ -25,29 +25,28 @@ const SharedLocationContextProvider = ({ const [bgPermissions, setBgPermissions] = useState(null); const [fgPermissions, setFgPermissions] = useState(null); - useEffect(() => { + const refreshPermissionState = () => { getBackgroundPermissionsAsync().then(({granted}) => setBgPermissions(granted), ); getForegroundPermissionsAsync().then(({granted}) => setFgPermissions(granted), ); - const sub = AppState.addEventListener('change', newState => { + }; + + useEffect(refreshPermissionState, []); + useEffect(() => { + const subscription = AppState.addEventListener('change', newState => { if ( appState.current.match(/inactive|background/) && newState === 'active' ) { - getBackgroundPermissionsAsync().then(({granted}) => - setBgPermissions(granted), - ); - getForegroundPermissionsAsync().then(({granted}) => - setFgPermissions(granted), - ); + refreshPermissionState(); } appState.current = newState; }); - return () => sub.remove(); + return () => subscription.remove(); }, []); return ( From c73ebda87ba48f3a4af017d0609420f655be6c02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20Stefa=C5=84czyk?= Date: Mon, 22 Apr 2024 17:14:42 +0200 Subject: [PATCH 64/79] use cheap-ruler instead own distance calc --- src/frontend/utils/distance.test.ts | 139 ---------------------------- src/frontend/utils/distance.ts | 61 +++--------- 2 files changed, 12 insertions(+), 188 deletions(-) delete mode 100644 src/frontend/utils/distance.test.ts diff --git a/src/frontend/utils/distance.test.ts b/src/frontend/utils/distance.test.ts deleted file mode 100644 index e80b629f0..000000000 --- a/src/frontend/utils/distance.test.ts +++ /dev/null @@ -1,139 +0,0 @@ -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 = [ - {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, - ); - }); - - 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 02f99b0c5..545a4525b 100644 --- a/src/frontend/utils/distance.ts +++ b/src/frontend/utils/distance.ts @@ -1,62 +1,25 @@ +import CheapRuler from 'cheap-ruler'; import {LonLatData} from '../sharedTypes/location'; -const EARTH_RADIUS = 6371; // Radius of the earth in km +export const calculateTotalDistance = (points: LonLatData[]): number => { + if (points.length <= 1) { + return 0; + } -function degreesToRadians(degrees: number): number { - return degrees * (Math.PI / 180); -} + const ruler = new CheapRuler(points[0]!.latitude, 'kilometers'); -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; - - const haversine = - deltaLatitudeHalfSineSquared + - cosineProduct * deltaLongitudeHalfSineSquared; - - return haversine; -} - -// Based on https://en.wikipedia.org/wiki/Haversine_formula -export const calculateTotalDistance = (points: LonLatData[]): number => - points.reduce((previousValue, currentValue, i, arr) => { + return points.reduce((previousValue, currentValue, i, arr) => { if (i === 0) { return previousValue; } const pointA = arr[i - 1]!!; const pointB = currentValue; - - const deltaLatitude = degreesToRadians(pointB.latitude - pointA.latitude); - const deltaLongitude = degreesToRadians( - pointB.longitude - pointA.longitude, - ); - - const haversine = calculateHaversine( - deltaLatitude, - deltaLongitude, - pointA, - pointB, + const distance = ruler.distance( + [pointA.longitude, pointA.latitude], + [pointB.longitude, pointB.latitude], ); - const distanceFactor = - 2 * Math.atan2(Math.sqrt(haversine), Math.sqrt(1 - haversine)); - - return previousValue + EARTH_RADIUS * distanceFactor; + return previousValue + distance; }, 0); +}; From b4f92a4824425d04c4415d231eb90304bd6b5d0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20Stefa=C5=84czyk?= Date: Mon, 22 Apr 2024 17:16:44 +0200 Subject: [PATCH 65/79] Use ellipsis symbol instead triple dot Co-authored-by: Andrew Chou --- messages/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/en.json b/messages/en.json index 8370c1364..6e63e4444 100644 --- a/messages/en.json +++ b/messages/en.json @@ -32,7 +32,7 @@ "message": "Start Tracks" }, "Modal.GPSEnable.button.loading": { - "message": "Loading..." + "message": "Loading…" }, "Modal.GPSEnable.button.stop": { "message": "Stop Tracks" From cfc9ab7761f8b08b7bd4741c91339091f28dfdc9 Mon Sep 17 00:00:00 2001 From: bohdanprog Date: Mon, 22 Apr 2024 19:10:26 +0200 Subject: [PATCH 66/79] add header to tab navigator, fixed problem with navigator in homeHeader component --- src/frontend/Navigation/ScreenGroups/AppScreens.tsx | 3 +-- src/frontend/sharedComponents/HomeHeader.tsx | 7 ++----- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/frontend/Navigation/ScreenGroups/AppScreens.tsx b/src/frontend/Navigation/ScreenGroups/AppScreens.tsx index a037ba789..0ca6f0b8f 100644 --- a/src/frontend/Navigation/ScreenGroups/AppScreens.tsx +++ b/src/frontend/Navigation/ScreenGroups/AppScreens.tsx @@ -147,6 +147,7 @@ const HomeTabs = () => { tabBarShowLabel: false, headerTransparent: true, tabBarTestID: 'tabBarButton' + route.name, + header: HomeHeader, })} initialRouteName={TabName.Map} backBehavior="initialRoute"> @@ -154,7 +155,6 @@ const HomeTabs = () => { name={TabName.Map} component={MapScreen} options={{ - header: HomeHeader, tabBarIcon: MapTabBarIcon, }} /> @@ -162,7 +162,6 @@ const HomeTabs = () => { name={TabName.Camera} component={CameraScreen} options={{ - headerShown: false, tabBarIcon: CameraTabBarIcon, }} /> diff --git a/src/frontend/sharedComponents/HomeHeader.tsx b/src/frontend/sharedComponents/HomeHeader.tsx index 4a6ba97de..997fd731f 100644 --- a/src/frontend/sharedComponents/HomeHeader.tsx +++ b/src/frontend/sharedComponents/HomeHeader.tsx @@ -3,12 +3,9 @@ import {View, StyleSheet} from 'react-native'; import LinearGradient from 'react-native-linear-gradient'; import {IconButton} from './IconButton'; import {ObservationListIcon} from './icons'; -import {useNavigationFromHomeTabs} from '../hooks/useNavigationWithTypes'; import {GPSPill} from '../screens/MapScreen/gps/GPSPill'; -export const HomeHeader = () => { - const navigation = useNavigationFromHomeTabs(); - +export const HomeHeader = ({navigation}) => { return ( { { - navigation.navigate('ObservationList'); + navigation.navigate('ObservationList' as never); }} testID="observationListButton"> From 998808ae2926a5aec62d678eb822b17f241d803c Mon Sep 17 00:00:00 2001 From: bohdanprog Date: Mon, 22 Apr 2024 19:18:51 +0200 Subject: [PATCH 67/79] rename gpsModal to GPSPermissionModal also renamed other connected compontents --- .../GPSPermissionsDisabled.tsx} | 0 .../GPSPermissionsEnabled.tsx} | 0 .../GPSPermissionsModal.tsx} | 4 ++-- src/frontend/screens/MapScreen/index.tsx | 2 +- .../MapScreen/gps => sharedComponents}/GPSPill.tsx | 12 ++++++------ src/frontend/sharedComponents/HomeHeader.tsx | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) rename src/frontend/screens/MapScreen/{gps/GPSDisabled.tsx => GPSPermissions/GPSPermissionsDisabled.tsx} (100%) rename src/frontend/screens/MapScreen/{gps/GPSEnabled.tsx => GPSPermissions/GPSPermissionsEnabled.tsx} (100%) rename src/frontend/screens/MapScreen/{gps/GPSModal.tsx => GPSPermissions/GPSPermissionsModal.tsx} (94%) rename src/frontend/{screens/MapScreen/gps => sharedComponents}/GPSPill.tsx (83%) diff --git a/src/frontend/screens/MapScreen/gps/GPSDisabled.tsx b/src/frontend/screens/MapScreen/GPSPermissions/GPSPermissionsDisabled.tsx similarity index 100% rename from src/frontend/screens/MapScreen/gps/GPSDisabled.tsx rename to src/frontend/screens/MapScreen/GPSPermissions/GPSPermissionsDisabled.tsx diff --git a/src/frontend/screens/MapScreen/gps/GPSEnabled.tsx b/src/frontend/screens/MapScreen/GPSPermissions/GPSPermissionsEnabled.tsx similarity index 100% rename from src/frontend/screens/MapScreen/gps/GPSEnabled.tsx rename to src/frontend/screens/MapScreen/GPSPermissions/GPSPermissionsEnabled.tsx diff --git a/src/frontend/screens/MapScreen/gps/GPSModal.tsx b/src/frontend/screens/MapScreen/GPSPermissions/GPSPermissionsModal.tsx similarity index 94% rename from src/frontend/screens/MapScreen/gps/GPSModal.tsx rename to src/frontend/screens/MapScreen/GPSPermissions/GPSPermissionsModal.tsx index ea841f7af..f150a49fe 100644 --- a/src/frontend/screens/MapScreen/gps/GPSModal.tsx +++ b/src/frontend/screens/MapScreen/GPSPermissions/GPSPermissionsModal.tsx @@ -1,6 +1,6 @@ import React, {useEffect, useState} from 'react'; -import {GPSDisabled} from './GPSDisabled'; -import {GPSEnabled} from './GPSEnabled'; +import {GPSDisabled} from './GPSPermissionsDisabled'; +import {GPSEnabled} from './GPSPermissionsEnabled'; import * as Location from 'expo-location'; import {useGPSModalContext} from '../../../contexts/GPSModalContext'; import {useNavigationStore} from '../../../hooks/useNavigationStore'; diff --git a/src/frontend/screens/MapScreen/index.tsx b/src/frontend/screens/MapScreen/index.tsx index 1653e12bb..bbd4df6eb 100644 --- a/src/frontend/screens/MapScreen/index.tsx +++ b/src/frontend/screens/MapScreen/index.tsx @@ -17,7 +17,7 @@ import ScaleBar from 'react-native-scale-bar'; import {getCoords} from '../../hooks/useLocation'; import {useLastKnownLocation} from '../../hooks/useLastSavedLocation'; import {useLocationProviderStatus} from '../../hooks/useLocationProviderStatus'; -import {GPSModal} from './gps/GPSModal'; +import {GPSModal} from './GPSPermissions/GPSPermissionsModal'; import {TrackPathLayer} from './track/TrackPathLayer'; import {UserLocation} from './UserLocation'; import {useSharedLocationContext} from '../../contexts/SharedLocationContext'; diff --git a/src/frontend/screens/MapScreen/gps/GPSPill.tsx b/src/frontend/sharedComponents/GPSPill.tsx similarity index 83% rename from src/frontend/screens/MapScreen/gps/GPSPill.tsx rename to src/frontend/sharedComponents/GPSPill.tsx index 6001d493d..f6a0eb6fe 100644 --- a/src/frontend/screens/MapScreen/gps/GPSPill.tsx +++ b/src/frontend/sharedComponents/GPSPill.tsx @@ -1,13 +1,13 @@ import React, {useMemo} from 'react'; import {StyleSheet, TouchableOpacity, View} from 'react-native'; -import {Text} from '../../../sharedComponents/Text'; -import {useNavigationFromHomeTabs} from '../../../hooks/useNavigationWithTypes'; +import {Text} from './Text'; +import {useNavigationFromHomeTabs} from '../hooks/useNavigationWithTypes'; import {useIsFocused} from '@react-navigation/native'; -import {useLocationProviderStatus} from '../../../hooks/useLocationProviderStatus'; -import {getLocationStatus} from '../../../lib/utils'; +import {useLocationProviderStatus} from '../hooks/useLocationProviderStatus'; +import {getLocationStatus} from '../lib/utils'; import {defineMessages, useIntl} from 'react-intl'; -import {GpsIcon} from '../../../sharedComponents/icons'; -import {useSharedLocationContext} from '../../../contexts/SharedLocationContext'; +import {GpsIcon} from './icons'; +import {useSharedLocationContext} from '../contexts/SharedLocationContext'; const m = defineMessages({ noGps: { diff --git a/src/frontend/sharedComponents/HomeHeader.tsx b/src/frontend/sharedComponents/HomeHeader.tsx index 997fd731f..c0b9e18ce 100644 --- a/src/frontend/sharedComponents/HomeHeader.tsx +++ b/src/frontend/sharedComponents/HomeHeader.tsx @@ -3,7 +3,7 @@ import {View, StyleSheet} from 'react-native'; import LinearGradient from 'react-native-linear-gradient'; import {IconButton} from './IconButton'; import {ObservationListIcon} from './icons'; -import {GPSPill} from '../screens/MapScreen/gps/GPSPill'; +import {GPSPill} from './GPSPill'; export const HomeHeader = ({navigation}) => { return ( From 255f59054b2dc91e8f4320b59bfee6932fbff1e3 Mon Sep 17 00:00:00 2001 From: bohdanprog Date: Tue, 23 Apr 2024 09:02:40 +0200 Subject: [PATCH 68/79] fixed problem with dependency in useTracking hook --- src/frontend/hooks/tracks/useTracking.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/frontend/hooks/tracks/useTracking.ts b/src/frontend/hooks/tracks/useTracking.ts index 3fde724b0..0646d5985 100644 --- a/src/frontend/hooks/tracks/useTracking.ts +++ b/src/frontend/hooks/tracks/useTracking.ts @@ -18,10 +18,6 @@ export function useTracking() { const setTracking = useCurrentTrackStore(state => state.setTracking); const isTracking = useCurrentTrackStore(state => state.isTracking); - React.useEffect(() => { - TaskManager.defineTask(LOCATION_TASK_NAME, addNewTrackLocations); - }, []); - const addNewTrackLocations = useCallback( ({data, error}: LocationCallbackInfo) => { if (error) { @@ -40,6 +36,10 @@ export function useTracking() { [addNewLocations], ); + React.useEffect(() => { + TaskManager.defineTask(LOCATION_TASK_NAME, addNewTrackLocations); + }, [addNewTrackLocations]); + const startTracking = useCallback(async () => { setLoading(true); @@ -56,7 +56,7 @@ export function useTracking() { setTracking(true); setLoading(false); - }, [addNewTrackLocations, isTracking, setTracking]); + }, [isTracking, setTracking]); const cancelTracking = useCallback(async () => { await Location.stopLocationUpdatesAsync(LOCATION_TASK_NAME); From 719a83f2834180cbc8cea89bf01965dd86c4e2c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20Stefa=C5=84czyk?= Date: Tue, 23 Apr 2024 11:00:01 +0200 Subject: [PATCH 69/79] Apply review suggestions --- .../hooks/tracks/useCurrentTrackStore.ts | 24 ++++++++++----- src/frontend/hooks/tracks/useTracking.ts | 4 +-- src/frontend/hooks/useFormattedTimeSince.ts | 11 +++++-- .../CustomBottomSheetModal.tsx | 30 ------------------- src/frontend/utils/distance.ts | 17 ++--------- 5 files changed, 30 insertions(+), 56 deletions(-) delete mode 100644 src/frontend/sharedComponents/BottomSheetModal/CustomBottomSheetModal.tsx diff --git a/src/frontend/hooks/tracks/useCurrentTrackStore.ts b/src/frontend/hooks/tracks/useCurrentTrackStore.ts index 1a125abd3..2febd86ab 100644 --- a/src/frontend/hooks/tracks/useCurrentTrackStore.ts +++ b/src/frontend/hooks/tracks/useCurrentTrackStore.ts @@ -3,23 +3,30 @@ import {calculateTotalDistance} from '../../utils/distance'; import {LocationHistoryPoint} from '../../sharedTypes/location'; type TracksStoreState = { - isTracking: boolean; locationHistory: LocationHistoryPoint[]; observations: string[]; distance: number; - trackingSince: Date; addNewObservation: (observationId: string) => void; addNewLocations: (locationData: LocationHistoryPoint[]) => void; clearLocationHistory: () => void; setTracking: (val: boolean) => void; -}; +} & ( + | { + isTracking: true; + trackingSince: Date; + } + | { + isTracking: false; + trackingSince: null; + } +); export const useCurrentTrackStore = create(set => ({ isTracking: false, locationHistory: [], observations: [], distance: 0, - trackingSince: new Date(0), + trackingSince: null, addNewObservation: (id: string) => set(state => ({observations: [...state.observations, id]})), addNewLocations: data => @@ -49,8 +56,9 @@ export const useCurrentTrackStore = create(set => ({ }), clearLocationHistory: () => set(() => ({locationHistory: []})), setTracking: (val: boolean) => - set(() => ({ - isTracking: val, - trackingSince: val ? new Date() : new Date(0), - })), + set(() => + val + ? {isTracking: true, trackingSince: new Date()} + : {isTracking: false, trackingSince: null}, + ), })); diff --git a/src/frontend/hooks/tracks/useTracking.ts b/src/frontend/hooks/tracks/useTracking.ts index 0646d5985..51994c40f 100644 --- a/src/frontend/hooks/tracks/useTracking.ts +++ b/src/frontend/hooks/tracks/useTracking.ts @@ -41,14 +41,14 @@ export function useTracking() { }, [addNewTrackLocations]); const startTracking = useCallback(async () => { - setLoading(true); - if (isTracking) { console.warn('Start tracking attempt while tracking already enabled'); setLoading(false); return; } + setLoading(true); + await Location.startLocationUpdatesAsync(LOCATION_TASK_NAME, { accuracy: Location.Accuracy.Highest, activityType: Location.LocationActivityType.Fitness, diff --git a/src/frontend/hooks/useFormattedTimeSince.ts b/src/frontend/hooks/useFormattedTimeSince.ts index c08d139bf..ae01377c6 100644 --- a/src/frontend/hooks/useFormattedTimeSince.ts +++ b/src/frontend/hooks/useFormattedTimeSince.ts @@ -1,15 +1,22 @@ import {useEffect, useState} from 'react'; import {Duration} from 'luxon'; -export const useFormattedTimeSince = (start: Date, interval: number) => { +export const useFormattedTimeSince = (start: Date | null, interval: number) => { const [currentTime, setCurrentTime] = useState(new Date()); useEffect(() => { + if (!start) { + return; + } const timer = setInterval(() => { setCurrentTime(new Date()); }, interval); return () => clearInterval(timer); - }, [interval]); + }, [start, interval]); + + if (!start) { + return 'Unknown'; + } const millisPassed = Math.abs(currentTime.getTime() - start.getTime()); return Duration.fromMillis(millisPassed).toFormat('hh:mm:ss'); diff --git a/src/frontend/sharedComponents/BottomSheetModal/CustomBottomSheetModal.tsx b/src/frontend/sharedComponents/BottomSheetModal/CustomBottomSheetModal.tsx deleted file mode 100644 index 2c58454ed..000000000 --- a/src/frontend/sharedComponents/BottomSheetModal/CustomBottomSheetModal.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import React, {FC} from 'react'; -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'; - -interface CustomBottomSheetModal { - dismiss: () => void; - bottomSheetRef: React.RefObject; - children: React.ReactNode; -} - -export const CustomBottomSheetModal: FC = ({ - bottomSheetRef, - children, -}) => { - return ( - null}> - {children} - - ); -}; diff --git a/src/frontend/utils/distance.ts b/src/frontend/utils/distance.ts index 545a4525b..2aefde679 100644 --- a/src/frontend/utils/distance.ts +++ b/src/frontend/utils/distance.ts @@ -8,18 +8,7 @@ export const calculateTotalDistance = (points: LonLatData[]): number => { const ruler = new CheapRuler(points[0]!.latitude, 'kilometers'); - return points.reduce((previousValue, currentValue, i, arr) => { - if (i === 0) { - return previousValue; - } - - const pointA = arr[i - 1]!!; - const pointB = currentValue; - const distance = ruler.distance( - [pointA.longitude, pointA.latitude], - [pointB.longitude, pointB.latitude], - ); - - return previousValue + distance; - }, 0); + return ruler.lineDistance( + points.map(point => [point.longitude, point.latitude]), + ); }; From 063f160991c85d74df1447732e988481f832c011 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20Stefa=C5=84czyk?= Date: Tue, 23 Apr 2024 11:09:21 +0200 Subject: [PATCH 70/79] remove irrelevant changes to navigation --- src/frontend/sharedComponents/HomeHeader.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/frontend/sharedComponents/HomeHeader.tsx b/src/frontend/sharedComponents/HomeHeader.tsx index c0b9e18ce..770fc73c7 100644 --- a/src/frontend/sharedComponents/HomeHeader.tsx +++ b/src/frontend/sharedComponents/HomeHeader.tsx @@ -4,8 +4,10 @@ import LinearGradient from 'react-native-linear-gradient'; import {IconButton} from './IconButton'; import {ObservationListIcon} from './icons'; import {GPSPill} from './GPSPill'; +import {useNavigationFromHomeTabs} from '../hooks/useNavigationWithTypes'; -export const HomeHeader = ({navigation}) => { +export const HomeHeader = () => { + const navigation = useNavigationFromHomeTabs(); return ( { { - navigation.navigate('ObservationList' as never); + navigation.navigate('ObservationList'); }} testID="observationListButton"> From 119bf5627bee0883488f677fb130802e692da6ec Mon Sep 17 00:00:00 2001 From: bohdanprog Date: Tue, 23 Apr 2024 11:38:46 +0200 Subject: [PATCH 71/79] rename store from useNvigatorStore to useTabNvigatorStore --- .../Navigation/ScreenGroups/AppScreens.tsx | 15 ++++++++++++--- .../ScreenGroups/TabBar/CameraTabBarIcon.tsx | 4 ++-- .../ScreenGroups/TabBar/MapTabBarIcon.tsx | 4 ++-- .../ScreenGroups/TabBar/TrackingTabBarIcon.tsx | 4 ++-- src/frontend/hooks/useCurrentTab.ts | 4 ++-- ...avigationStore.ts => useTabNavigationStore.ts} | 2 +- .../GPSPermissions/GPSPermissionsModal.tsx | 4 ++-- src/frontend/screens/MapScreen/index.tsx | 12 +++++++++++- 8 files changed, 34 insertions(+), 15 deletions(-) rename src/frontend/hooks/{useNavigationStore.ts => useTabNavigationStore.ts} (81%) diff --git a/src/frontend/Navigation/ScreenGroups/AppScreens.tsx b/src/frontend/Navigation/ScreenGroups/AppScreens.tsx index cfd79010b..b23320150 100644 --- a/src/frontend/Navigation/ScreenGroups/AppScreens.tsx +++ b/src/frontend/Navigation/ScreenGroups/AppScreens.tsx @@ -1,4 +1,7 @@ -import {createBottomTabNavigator} from '@react-navigation/bottom-tabs'; +import { + BottomTabNavigationProp, + createBottomTabNavigator, +} from '@react-navigation/bottom-tabs'; import {NavigatorScreenParams} from '@react-navigation/native'; import * as React from 'react'; import {HomeHeader} from '../../sharedComponents/HomeHeader'; @@ -142,7 +145,9 @@ const HomeTabs = () => { return ( ({ tabBarStyle: {height: TAB_BAR_HEIGHT}, tabBarShowLabel: false, @@ -172,7 +177,11 @@ const HomeTabs = () => { tabBarIcon: TrackingTabBarIcon, headerShown: false, }} - listeners={({navigation}) => ({ + listeners={({ + navigation, + }: { + navigation: BottomTabNavigationProp; + }) => ({ tabPress: e => { e.preventDefault(); navigation.navigate(TabName.Map); diff --git a/src/frontend/Navigation/ScreenGroups/TabBar/CameraTabBarIcon.tsx b/src/frontend/Navigation/ScreenGroups/TabBar/CameraTabBarIcon.tsx index 352393169..40c35e394 100644 --- a/src/frontend/Navigation/ScreenGroups/TabBar/CameraTabBarIcon.tsx +++ b/src/frontend/Navigation/ScreenGroups/TabBar/CameraTabBarIcon.tsx @@ -1,10 +1,10 @@ import React, {FC} from 'react'; import {TabBarIconProps, TabName} from '../../types'; import {TabBarIcon} from './TabBarIcon'; -import {useNavigationStore} from '../../../hooks/useNavigationStore'; +import {useTabNavigationStore} from '../../../hooks/useTabNavigationStore.ts'; export const CameraTabBarIcon: FC = props => { - const {currentTab} = useNavigationStore(); + const {currentTab} = useTabNavigationStore(); return ( = props => { - const {currentTab} = useNavigationStore(); + const {currentTab} = useTabNavigationStore(); return ( = props => { const {isTracking} = useTracking(); const {timer} = useTrackTimerContext(); - const {currentTab} = useNavigationStore(); + const {currentTab} = useTabNavigationStore(); return ( <> diff --git a/src/frontend/hooks/useCurrentTab.ts b/src/frontend/hooks/useCurrentTab.ts index 20b87a13d..33520bd85 100644 --- a/src/frontend/hooks/useCurrentTab.ts +++ b/src/frontend/hooks/useCurrentTab.ts @@ -1,10 +1,10 @@ import {EventArg} from '@react-navigation/native'; import {useGPSModalContext} from '../contexts/GPSModalContext'; -import {useNavigationStore} from './useNavigationStore'; +import {useTabNavigationStore} from './useTabNavigationStore.ts'; import {TabName} from '../Navigation/types'; export const useCurrentTab = () => { - const {setCurrentTab} = useNavigationStore(); + const {setCurrentTab} = useTabNavigationStore(); const {bottomSheetRef} = useGPSModalContext(); const handleTabPress = ({target}: EventArg<'tabPress', true, undefined>) => { diff --git a/src/frontend/hooks/useNavigationStore.ts b/src/frontend/hooks/useTabNavigationStore.ts similarity index 81% rename from src/frontend/hooks/useNavigationStore.ts rename to src/frontend/hooks/useTabNavigationStore.ts index 170eb023c..3918e2b04 100644 --- a/src/frontend/hooks/useNavigationStore.ts +++ b/src/frontend/hooks/useTabNavigationStore.ts @@ -7,7 +7,7 @@ type NavigationStoreState = { setCurrentTab: (tab: TabName) => void; }; -export const useNavigationStore = create(set => ({ +export const useTabNavigationStore = create(set => ({ initialRouteName: TabName.Map, currentTab: TabName.Map, setCurrentTab: (tab: TabName) => set(() => ({currentTab: tab})), diff --git a/src/frontend/screens/MapScreen/GPSPermissions/GPSPermissionsModal.tsx b/src/frontend/screens/MapScreen/GPSPermissions/GPSPermissionsModal.tsx index f150a49fe..c560cc623 100644 --- a/src/frontend/screens/MapScreen/GPSPermissions/GPSPermissionsModal.tsx +++ b/src/frontend/screens/MapScreen/GPSPermissions/GPSPermissionsModal.tsx @@ -3,7 +3,7 @@ import {GPSDisabled} from './GPSPermissionsDisabled'; import {GPSEnabled} from './GPSPermissionsEnabled'; import * as Location from 'expo-location'; import {useGPSModalContext} from '../../../contexts/GPSModalContext'; -import {useNavigationStore} from '../../../hooks/useNavigationStore'; +import {useTabNavigationStore} from '../../../hooks/useTabNavigationStore.ts'; import {BottomSheetModal, BottomSheetView} from '@gorhom/bottom-sheet'; import {TAB_BAR_HEIGHT} from '../../../Navigation/ScreenGroups/AppScreens'; import {StyleSheet} from 'react-native'; @@ -11,7 +11,7 @@ import {TabName} from '../../../Navigation/types'; import {useFocusEffect} from '@react-navigation/native'; export const GPSModal = React.memo(() => { - const {setCurrentTab} = useNavigationStore(); + const {setCurrentTab} = useTabNavigationStore(); const [backgroundStatus] = Location.useBackgroundPermissions(); const [foregroundStatus] = Location.useForegroundPermissions(); diff --git a/src/frontend/screens/MapScreen/index.tsx b/src/frontend/screens/MapScreen/index.tsx index bbd4df6eb..6a7594345 100644 --- a/src/frontend/screens/MapScreen/index.tsx +++ b/src/frontend/screens/MapScreen/index.tsx @@ -21,6 +21,7 @@ import {GPSModal} from './GPSPermissions/GPSPermissionsModal'; import {TrackPathLayer} from './track/TrackPathLayer'; import {UserLocation} from './UserLocation'; import {useSharedLocationContext} from '../../contexts/SharedLocationContext'; +import {useNavigation} from '@react-navigation/native'; // 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 @@ -34,7 +35,7 @@ export const MAP_STYLE = Mapbox.StyleURL.Outdoors; export const MapScreen = () => { const [zoom, setZoom] = React.useState(DEFAULT_ZOOM); - + const navigation = useNavigation(); const [isFinishedLoading, setIsFinishedLoading] = React.useState(false); const [following, setFollowing] = React.useState(true); const {newDraft} = useDraftObservation(); @@ -64,6 +65,15 @@ export const MapScreen = () => { setIsFinishedLoading(true); } + React.useEffect(() => { + const unsubscribe = navigation.addListener('focus', e => { + // Prevent default action + console.log(e, 'e'); + // e.preventDefault(); + }); + return () => unsubscribe(); + }, []); + return ( Date: Tue, 23 Apr 2024 11:56:41 +0200 Subject: [PATCH 72/79] restore changes with GPSPill --- src/frontend/screens/MapScreen/index.tsx | 11 ----- src/frontend/sharedComponents/GPSPill.tsx | 46 +++++++++++++------- src/frontend/sharedComponents/HomeHeader.tsx | 15 ++++--- 3 files changed, 40 insertions(+), 32 deletions(-) diff --git a/src/frontend/screens/MapScreen/index.tsx b/src/frontend/screens/MapScreen/index.tsx index 6a7594345..899496083 100644 --- a/src/frontend/screens/MapScreen/index.tsx +++ b/src/frontend/screens/MapScreen/index.tsx @@ -21,7 +21,6 @@ import {GPSModal} from './GPSPermissions/GPSPermissionsModal'; import {TrackPathLayer} from './track/TrackPathLayer'; import {UserLocation} from './UserLocation'; import {useSharedLocationContext} from '../../contexts/SharedLocationContext'; -import {useNavigation} from '@react-navigation/native'; // 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 @@ -35,7 +34,6 @@ export const MAP_STYLE = Mapbox.StyleURL.Outdoors; export const MapScreen = () => { const [zoom, setZoom] = React.useState(DEFAULT_ZOOM); - const navigation = useNavigation(); const [isFinishedLoading, setIsFinishedLoading] = React.useState(false); const [following, setFollowing] = React.useState(true); const {newDraft} = useDraftObservation(); @@ -65,15 +63,6 @@ export const MapScreen = () => { setIsFinishedLoading(true); } - React.useEffect(() => { - const unsubscribe = navigation.addListener('focus', e => { - // Prevent default action - console.log(e, 'e'); - // e.preventDefault(); - }); - return () => unsubscribe(); - }, []); - return ( { +export const GPSPill = ({navigation}) => { const isFocused = useIsFocused(); const {formatMessage: t} = useIntl(); const {locationState, fgPermissions} = useSharedLocationContext(); @@ -51,27 +51,43 @@ export const GPSPill = () => { } else return `± ${Math.round(precision!)} m`; }, [precision, status, t]); - const navigation = useNavigationFromHomeTabs(); - return ( - navigation.navigate('GpsModal')}> - - + navigation.navigate('GPSModal' as never)} + testID="gpsPillButton"> + + {isFocused && } - {text} + + {text} + ); }; const styles = StyleSheet.create({ - indicatorWrapper: { - backgroundColor: '#333333', - borderRadius: 20, - paddingVertical: 14, - paddingHorizontal: 10, + 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', }, - wrapper: {flexDirection: 'row', alignItems: 'center'}, - text: {marginLeft: 5, color: '#fff', fontSize: 15}, + error: {backgroundColor: '#FF0000'}, + text: {color: WHITE}, + icon: {position: 'absolute', left: 6}, }); diff --git a/src/frontend/sharedComponents/HomeHeader.tsx b/src/frontend/sharedComponents/HomeHeader.tsx index 770fc73c7..de079690b 100644 --- a/src/frontend/sharedComponents/HomeHeader.tsx +++ b/src/frontend/sharedComponents/HomeHeader.tsx @@ -1,13 +1,16 @@ -import React from 'react'; +import React, {FC} from 'react'; import {View, StyleSheet} from 'react-native'; import LinearGradient from 'react-native-linear-gradient'; import {IconButton} from './IconButton'; import {ObservationListIcon} from './icons'; import {GPSPill} from './GPSPill'; -import {useNavigationFromHomeTabs} from '../hooks/useNavigationWithTypes'; +import {NavigationProp} from '@react-navigation/native'; -export const HomeHeader = () => { - const navigation = useNavigationFromHomeTabs(); +interface HomeHeader { + navigation: NavigationProp; +} + +export const HomeHeader: FC = ({navigation}) => { return ( { colors={['#0006', '#0000']} /> {/* Placeholder for left button */} - + { - navigation.navigate('ObservationList'); + navigation.navigate('ObservationList' as never); }} testID="observationListButton"> From 4aef7bc2f39e98221d69eb77d1d377f2a7adc60f Mon Sep 17 00:00:00 2001 From: bohdanprog Date: Tue, 23 Apr 2024 12:30:38 +0200 Subject: [PATCH 73/79] rename gps components --- .../GPSPermissions/GPSPermissionsDisabled.tsx | 6 ++++-- .../MapScreen/GPSPermissions/GPSPermissionsEnabled.tsx | 2 +- .../MapScreen/GPSPermissions/GPSPermissionsModal.tsx | 10 +++++----- src/frontend/screens/MapScreen/index.tsx | 4 ++-- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/frontend/screens/MapScreen/GPSPermissions/GPSPermissionsDisabled.tsx b/src/frontend/screens/MapScreen/GPSPermissions/GPSPermissionsDisabled.tsx index 7e279900e..29b409653 100644 --- a/src/frontend/screens/MapScreen/GPSPermissions/GPSPermissionsDisabled.tsx +++ b/src/frontend/screens/MapScreen/GPSPermissions/GPSPermissionsDisabled.tsx @@ -25,10 +25,12 @@ const m = defineMessages({ }, }); -interface GPSDisabled { +interface GPSPermissionsDisabled { setIsGranted: React.Dispatch>; } -export const GPSDisabled: React.FC = ({setIsGranted}) => { +export const GPSPermissionsDisabled: React.FC = ({ + setIsGranted, +}) => { const {formatMessage} = useIntl(); const requestForLocationPermissions = async () => { const [foregroundPermission, backgroundPermission] = await Promise.all([ diff --git a/src/frontend/screens/MapScreen/GPSPermissions/GPSPermissionsEnabled.tsx b/src/frontend/screens/MapScreen/GPSPermissions/GPSPermissionsEnabled.tsx index cadbc903c..9ef41fd40 100644 --- a/src/frontend/screens/MapScreen/GPSPermissions/GPSPermissionsEnabled.tsx +++ b/src/frontend/screens/MapScreen/GPSPermissions/GPSPermissionsEnabled.tsx @@ -27,7 +27,7 @@ const m = defineMessages({ }, }); -export const GPSEnabled = () => { +export const GPSPermissionsEnabled = () => { const {formatMessage} = useIntl(); const {isTracking, cancelTracking, startTracking, loading} = useTracking(); const {timer} = useTrackTimerContext(); diff --git a/src/frontend/screens/MapScreen/GPSPermissions/GPSPermissionsModal.tsx b/src/frontend/screens/MapScreen/GPSPermissions/GPSPermissionsModal.tsx index c560cc623..6a87124a2 100644 --- a/src/frontend/screens/MapScreen/GPSPermissions/GPSPermissionsModal.tsx +++ b/src/frontend/screens/MapScreen/GPSPermissions/GPSPermissionsModal.tsx @@ -1,6 +1,6 @@ import React, {useEffect, useState} from 'react'; -import {GPSDisabled} from './GPSPermissionsDisabled'; -import {GPSEnabled} from './GPSPermissionsEnabled'; +import {GPSPermissionsDisabled} from './GPSPermissionsDisabled'; +import {GPSPermissionsEnabled} from './GPSPermissionsEnabled'; import * as Location from 'expo-location'; import {useGPSModalContext} from '../../../contexts/GPSModalContext'; import {useTabNavigationStore} from '../../../hooks/useTabNavigationStore.ts'; @@ -10,7 +10,7 @@ import {StyleSheet} from 'react-native'; import {TabName} from '../../../Navigation/types'; import {useFocusEffect} from '@react-navigation/native'; -export const GPSModal = React.memo(() => { +export const GPSPermissionsModal = React.memo(() => { const {setCurrentTab} = useTabNavigationStore(); const [backgroundStatus] = Location.useBackgroundPermissions(); const [foregroundStatus] = Location.useForegroundPermissions(); @@ -41,9 +41,9 @@ export const GPSModal = React.memo(() => { handleComponent={() => null}> {isGranted ? ( - + ) : ( - + )} diff --git a/src/frontend/screens/MapScreen/index.tsx b/src/frontend/screens/MapScreen/index.tsx index 899496083..2d80fbd3e 100644 --- a/src/frontend/screens/MapScreen/index.tsx +++ b/src/frontend/screens/MapScreen/index.tsx @@ -17,7 +17,7 @@ import ScaleBar from 'react-native-scale-bar'; import {getCoords} from '../../hooks/useLocation'; import {useLastKnownLocation} from '../../hooks/useLastSavedLocation'; import {useLocationProviderStatus} from '../../hooks/useLocationProviderStatus'; -import {GPSModal} from './GPSPermissions/GPSPermissionsModal'; +import {GPSPermissionsModal} from './GPSPermissions/GPSPermissionsModal'; import {TrackPathLayer} from './track/TrackPathLayer'; import {UserLocation} from './UserLocation'; import {useSharedLocationContext} from '../../contexts/SharedLocationContext'; @@ -122,7 +122,7 @@ export const MapScreen = () => { onPress={handleAddPress} isLoading={!isFinishedLoading} /> - + ); }; From 22bbfdf4caf1f0d726af98ba918f164455c6e1b1 Mon Sep 17 00:00:00 2001 From: bohdanprog Date: Wed, 24 Apr 2024 08:48:17 +0200 Subject: [PATCH 74/79] remove unnecessary props isFocused --- .../Navigation/ScreenGroups/TabBar/CameraTabBarIcon.tsx | 2 +- .../Navigation/ScreenGroups/TabBar/MapTabBarIcon.tsx | 2 +- src/frontend/Navigation/ScreenGroups/TabBar/TabBarIcon.tsx | 5 ++--- .../Navigation/ScreenGroups/TabBar/TrackingTabBarIcon.tsx | 2 +- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/frontend/Navigation/ScreenGroups/TabBar/CameraTabBarIcon.tsx b/src/frontend/Navigation/ScreenGroups/TabBar/CameraTabBarIcon.tsx index 40c35e394..25694426c 100644 --- a/src/frontend/Navigation/ScreenGroups/TabBar/CameraTabBarIcon.tsx +++ b/src/frontend/Navigation/ScreenGroups/TabBar/CameraTabBarIcon.tsx @@ -9,7 +9,7 @@ export const CameraTabBarIcon: FC = props => { return ( ); diff --git a/src/frontend/Navigation/ScreenGroups/TabBar/MapTabBarIcon.tsx b/src/frontend/Navigation/ScreenGroups/TabBar/MapTabBarIcon.tsx index 89d38ecab..a7c7dbd82 100644 --- a/src/frontend/Navigation/ScreenGroups/TabBar/MapTabBarIcon.tsx +++ b/src/frontend/Navigation/ScreenGroups/TabBar/MapTabBarIcon.tsx @@ -9,7 +9,7 @@ export const MapTabBarIcon: FC = props => { return ( ); diff --git a/src/frontend/Navigation/ScreenGroups/TabBar/TabBarIcon.tsx b/src/frontend/Navigation/ScreenGroups/TabBar/TabBarIcon.tsx index 2a614b202..e2d6604ea 100644 --- a/src/frontend/Navigation/ScreenGroups/TabBar/TabBarIcon.tsx +++ b/src/frontend/Navigation/ScreenGroups/TabBar/TabBarIcon.tsx @@ -4,16 +4,15 @@ import {TabBarIconProps} from '../../types'; import {COMAPEO_BLUE, MEDIUM_GREY} from '../../../lib/styles'; export interface TabBarIcon extends TabBarIconProps { - isFocused: boolean; iconName: string; } -export const TabBarIcon: FC = ({size, iconName, isFocused}) => { +export const TabBarIcon: FC = ({size, iconName, focused}) => { return ( ); }; diff --git a/src/frontend/Navigation/ScreenGroups/TabBar/TrackingTabBarIcon.tsx b/src/frontend/Navigation/ScreenGroups/TabBar/TrackingTabBarIcon.tsx index dadb6a0e3..d18095e80 100644 --- a/src/frontend/Navigation/ScreenGroups/TabBar/TrackingTabBarIcon.tsx +++ b/src/frontend/Navigation/ScreenGroups/TabBar/TrackingTabBarIcon.tsx @@ -22,7 +22,7 @@ export const TrackingTabBarIcon: FC = props => { )} From ea50a90c337c6a54051a36e2b0012e41927c3234 Mon Sep 17 00:00:00 2001 From: bohdanprog Date: Wed, 24 Apr 2024 09:24:39 +0200 Subject: [PATCH 75/79] remove navigation cast type to as never, add navigation types --- src/frontend/sharedComponents/GPSPill.tsx | 13 +++++++++---- src/frontend/sharedComponents/HomeHeader.tsx | 12 +++--------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/frontend/sharedComponents/GPSPill.tsx b/src/frontend/sharedComponents/GPSPill.tsx index 8931ce2c5..3296e7420 100644 --- a/src/frontend/sharedComponents/GPSPill.tsx +++ b/src/frontend/sharedComponents/GPSPill.tsx @@ -1,13 +1,14 @@ -import React, {useMemo} from 'react'; +import React, {FC, useMemo} from 'react'; import {StyleSheet, TouchableOpacity, View} from 'react-native'; import {Text} from './Text'; -import {useIsFocused} from '@react-navigation/native'; +import {ParamListBase, useIsFocused} from '@react-navigation/native'; import {useLocationProviderStatus} from '../hooks/useLocationProviderStatus'; import {getLocationStatus} from '../lib/utils'; import {defineMessages, useIntl} from 'react-intl'; import {GpsIcon} from './icons'; import {useSharedLocationContext} from '../contexts/SharedLocationContext'; import {BLACK, WHITE} from '../lib/styles'; +import {BottomTabNavigationProp} from '@react-navigation/bottom-tabs'; const m = defineMessages({ noGps: { @@ -20,7 +21,11 @@ const m = defineMessages({ }, }); -export const GPSPill = ({navigation}) => { +interface GPSPill { + navigation: BottomTabNavigationProp; +} + +export const GPSPill: FC = ({navigation}) => { const isFocused = useIsFocused(); const {formatMessage: t} = useIntl(); const {locationState, fgPermissions} = useSharedLocationContext(); @@ -53,7 +58,7 @@ export const GPSPill = ({navigation}) => { return ( navigation.navigate('GPSModal' as never)} + onPress={() => navigation.navigate('GpsModal')} testID="gpsPillButton"> ; -} - -export const HomeHeader: FC = ({navigation}) => { +export const HomeHeader: FC = ({navigation}) => { return ( = ({navigation}) => { {/* Placeholder for left button */} { - navigation.navigate('ObservationList' as never); - }} + onPress={() => navigation.navigate('ObservationList')} testID="observationListButton"> From 09c3bc322a083cc06df56d002ba69edca03e1f6e Mon Sep 17 00:00:00 2001 From: bohdanprog Date: Wed, 24 Apr 2024 10:19:51 +0200 Subject: [PATCH 76/79] fixed timer nulable case --- src/frontend/contexts/TrackTimerContext.tsx | 2 +- src/frontend/hooks/useFormattedTimeSince.ts | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/frontend/contexts/TrackTimerContext.tsx b/src/frontend/contexts/TrackTimerContext.tsx index a95048160..55cb0237f 100644 --- a/src/frontend/contexts/TrackTimerContext.tsx +++ b/src/frontend/contexts/TrackTimerContext.tsx @@ -10,7 +10,7 @@ const TrackTimerContext = createContext(null); const TrackTimerContextProvider = ({children}: {children: React.ReactNode}) => { const trackingSince = useCurrentTrackStore(state => state.trackingSince); - const timer = useFormattedTimeSince(trackingSince, 1000); + const timer = useFormattedTimeSince(trackingSince ?? new Date(), 1000); return ( diff --git a/src/frontend/hooks/useFormattedTimeSince.ts b/src/frontend/hooks/useFormattedTimeSince.ts index ae01377c6..6ab87d453 100644 --- a/src/frontend/hooks/useFormattedTimeSince.ts +++ b/src/frontend/hooks/useFormattedTimeSince.ts @@ -1,7 +1,7 @@ import {useEffect, useState} from 'react'; import {Duration} from 'luxon'; -export const useFormattedTimeSince = (start: Date | null, interval: number) => { +export const useFormattedTimeSince = (start: Date, interval: number) => { const [currentTime, setCurrentTime] = useState(new Date()); useEffect(() => { @@ -14,10 +14,6 @@ export const useFormattedTimeSince = (start: Date | null, interval: number) => { return () => clearInterval(timer); }, [start, interval]); - if (!start) { - return 'Unknown'; - } - const millisPassed = Math.abs(currentTime.getTime() - start.getTime()); return Duration.fromMillis(millisPassed).toFormat('hh:mm:ss'); }; From da725cb9751652a496cc213bb6d84ecbd096394b Mon Sep 17 00:00:00 2001 From: bohdanprog Date: Wed, 24 Apr 2024 10:43:50 +0200 Subject: [PATCH 77/79] add another solution to track timer --- src/frontend/contexts/TrackTimerContext.tsx | 2 +- src/frontend/hooks/useFormattedTimeSince.ts | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/frontend/contexts/TrackTimerContext.tsx b/src/frontend/contexts/TrackTimerContext.tsx index 55cb0237f..a95048160 100644 --- a/src/frontend/contexts/TrackTimerContext.tsx +++ b/src/frontend/contexts/TrackTimerContext.tsx @@ -10,7 +10,7 @@ const TrackTimerContext = createContext(null); const TrackTimerContextProvider = ({children}: {children: React.ReactNode}) => { const trackingSince = useCurrentTrackStore(state => state.trackingSince); - const timer = useFormattedTimeSince(trackingSince ?? new Date(), 1000); + const timer = useFormattedTimeSince(trackingSince, 1000); return ( diff --git a/src/frontend/hooks/useFormattedTimeSince.ts b/src/frontend/hooks/useFormattedTimeSince.ts index 6ab87d453..363baf2ff 100644 --- a/src/frontend/hooks/useFormattedTimeSince.ts +++ b/src/frontend/hooks/useFormattedTimeSince.ts @@ -1,19 +1,18 @@ import {useEffect, useState} from 'react'; import {Duration} from 'luxon'; -export const useFormattedTimeSince = (start: Date, interval: number) => { +export const useFormattedTimeSince = (start: Date | null, interval: number) => { const [currentTime, setCurrentTime] = useState(new Date()); + let startDate = start ? start : new Date(); useEffect(() => { - if (!start) { - return; - } + setCurrentTime(new Date()); const timer = setInterval(() => { setCurrentTime(new Date()); }, interval); return () => clearInterval(timer); - }, [start, interval]); + }, [interval]); - const millisPassed = Math.abs(currentTime.getTime() - start.getTime()); + const millisPassed = Math.abs(currentTime.getTime() - startDate.getTime()); return Duration.fromMillis(millisPassed).toFormat('hh:mm:ss'); }; From d44027139525c7f9eaa4de2de62d461ed43bd2e7 Mon Sep 17 00:00:00 2001 From: bohdanprog Date: Wed, 24 Apr 2024 13:22:49 +0200 Subject: [PATCH 78/79] add feature flag for track --- babel.config.js | 1 + package-lock.json | 7 ++++ package.json | 1 + .../Navigation/ScreenGroups/AppScreens.tsx | 38 ++++++++++--------- 4 files changed, 29 insertions(+), 18 deletions(-) diff --git a/babel.config.js b/babel.config.js index 3c181ebfc..0b2916076 100644 --- a/babel.config.js +++ b/babel.config.js @@ -3,5 +3,6 @@ module.exports = { plugins: [ // react-native-reanimated/plugin has to be last 'react-native-reanimated/plugin', + 'transform-inline-environment-variables', ], }; diff --git a/package-lock.json b/package-lock.json index 17acc1129..176b62f7a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -103,6 +103,7 @@ "@typescript-eslint/eslint-plugin": "^5.62.0", "@typescript-eslint/parser": "^5.62.0", "babel-jest": "^29.6.3", + "babel-plugin-transform-inline-environment-variables": "^0.4.4", "eslint": "^8.19.0", "eslint-config-prettier": "^9.1.0", "execa": "^8.0.1", @@ -9718,6 +9719,12 @@ "@babel/plugin-syntax-flow": "^7.12.1" } }, + "node_modules/babel-plugin-transform-inline-environment-variables": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-inline-environment-variables/-/babel-plugin-transform-inline-environment-variables-0.4.4.tgz", + "integrity": "sha512-bJILBtn5a11SmtR2j/3mBOjX4K3weC6cq+NNZ7hG22wCAqpc3qtj/iN7dSe9HDiS46lgp1nHsQgeYrea/RUe+g==", + "dev": true + }, "node_modules/babel-preset-current-node-syntax": { "version": "1.0.1", "dev": true, diff --git a/package.json b/package.json index baf9f6e29..1ca04dd4f 100644 --- a/package.json +++ b/package.json @@ -117,6 +117,7 @@ "@typescript-eslint/eslint-plugin": "^5.62.0", "@typescript-eslint/parser": "^5.62.0", "babel-jest": "^29.6.3", + "babel-plugin-transform-inline-environment-variables": "^0.4.4", "eslint": "^8.19.0", "eslint-config-prettier": "^9.1.0", "execa": "^8.0.1", diff --git a/src/frontend/Navigation/ScreenGroups/AppScreens.tsx b/src/frontend/Navigation/ScreenGroups/AppScreens.tsx index b23320150..500788984 100644 --- a/src/frontend/Navigation/ScreenGroups/AppScreens.tsx +++ b/src/frontend/Navigation/ScreenGroups/AppScreens.tsx @@ -171,24 +171,26 @@ const HomeTabs = () => { tabBarIcon: CameraTabBarIcon, }} /> - ; - }) => ({ - tabPress: e => { - e.preventDefault(); - navigation.navigate(TabName.Map); - }, - })} - children={() => <>} - /> + {process.env.FEATURE_TRACKS && ( + ; + }) => ({ + tabPress: e => { + e.preventDefault(); + navigation.navigate(TabName.Map); + }, + })} + children={() => <>} + /> + )} ); }; From f3a63f432b2efeb878c6653a05888a84903586a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20Stefa=C5=84czyk?= Date: Wed, 24 Apr 2024 18:09:59 +0200 Subject: [PATCH 79/79] fix order of babel plugins --- babel.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/babel.config.js b/babel.config.js index 0b2916076..63f23328d 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,8 +1,8 @@ module.exports = { presets: ['module:@react-native/babel-preset'], plugins: [ + 'transform-inline-environment-variables', // react-native-reanimated/plugin has to be last 'react-native-reanimated/plugin', - 'transform-inline-environment-variables', ], };