Skip to content

Commit 5cf0636

Browse files
committed
feat: Add LocationSpeed component to monitor and display current speed
1 parent e2a3141 commit 5cf0636

File tree

8 files changed

+117
-19
lines changed

8 files changed

+117
-19
lines changed

App.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ import { GestureHandlerRootView } from 'react-native-gesture-handler'
6363
import Animated, { ZoomIn, ZoomOut } from 'react-native-reanimated'
6464
import './global.css'
6565
import NewLocationNote from '@screens/LocationNotes/NewLocationNote'
66+
import LocationSpeed from '@screens/LocationSpeed/LocationSpeed'
6667

6768
function App(): React.JSX.Element {
6869
const scheme = useColorScheme()
@@ -201,12 +202,14 @@ function Navigation() {
201202
<Stack.Screen name='NewLocationNote' component={NewLocationNote} options={GestureEnabled} />
202203
<Stack.Screen name='LocationTags' component={LocationTags} options={IOS_BOTTOM_STYLE} />
203204
<Stack.Screen name='LocationNote' component={LocationNote} options={GestureEnabled} />
205+
<Stack.Screen name='LocationSpeed' component={LocationSpeed} options={GestureEnabled} />
204206
</Stack.Navigator>
205207
</>
206208
)
207209
}
208210

209211
export type RootStackParamList = {
212+
LocationSpeed: undefined
210213
NewLocationNote: undefined
211214
LocationNote: LocationNoteParamList
212215
LocationNotes: undefined

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"android": "react-native run-android --active-arch-only",
77
"ios": "react-native run-ios",
88
"lint": "biome lint",
9+
"eslint": "eslint . --fix",
910
"format": "biome format --write",
1011
"dev": "react-native start",
1112
"test": "jest",
@@ -76,4 +77,4 @@
7677
"engines": {
7778
"node": ">=18"
7879
}
79-
}
80+
}

src/assets/icons/icons.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,3 +148,4 @@ export { default as Timer02Icon } from '@icons/timer-02-solid-rounded.svg'
148148
export { default as ArrowDown01StrokeRoundedIcon } from '@icons/arrow-down-01-stroke-rounded.svg'
149149
export { default as ArrowRight01StrokeRoundedIcon } from '@icons/arrow-right-01-stroke-rounded.svg'
150150
export { default as Location01Icon } from '@icons/location-01-solid-rounded.svg'
151+
export { default as Sent02Icon } from '@icons/sent-02-solid-rounded.svg'
Lines changed: 3 additions & 0 deletions
Loading

src/screens/LocationNotes/LocationDetails.tsx

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,21 @@ import {
44
LatitudeIcon,
55
LongitudeIcon,
66
MapsLocation02SolidIcon,
7-
Rocket01Icon,
7+
Sent02Icon,
88
Timer02Icon,
99
} from '@assets/icons/icons'
1010
import { Gap12 } from '@components/Gap'
1111
import RoundedIcon from '@components/RoundedIcon'
1212
import SettGroup from '@components/Settings/SettGroup'
1313
import { SettOption } from '@components/Settings/SettOption'
1414
import { Txt } from '@components/Text'
15-
import { getLatitude, getLongitude } from '@utils/utils'
16-
import { Linking } from 'react-native'
15+
import { getLatitude, getLongitude, layout } from '@utils/utils'
16+
import { Linking, Share } from 'react-native'
1717
import { GeoPosition } from 'react-native-geolocation-service'
18+
import Animated, { FadeIn, FadeOut } from 'react-native-reanimated'
19+
import { shareMessage } from './lib'
1820

19-
export default function LocationDetails({ data }: { data: GeoPosition | undefined }) {
21+
export default function LocationDetails({ data }: { data: GeoPosition | undefined | null }) {
2022
const { coords: { latitude, longitude, accuracy, altitude, altitudeAccuracy, speed } = {}, timestamp } = data || {}
2123

2224
return (
@@ -47,28 +49,40 @@ export default function LocationDetails({ data }: { data: GeoPosition | undefine
4749
</Txt>
4850
}
4951
/>
50-
<SettOption
52+
{/* <SettOption
5153
title='Speed'
5254
Icon={<RoundedIcon Icon={Rocket01Icon} className='bg-orange-500' />}
5355
Right={<Txt skeleton={speed === undefined}>{speed?.toFixed(0)} m/s</Txt>}
54-
/>
56+
/> */}
5557
<SettOption
5658
title='Timestamp'
5759
Icon={<RoundedIcon Icon={Timer02Icon} className='bg-accent' />}
5860
Right={<Txt skeleton={timestamp === undefined}>{new Date(timestamp || 0).toLocaleString()}</Txt>}
5961
/>
6062
</SettGroup>
6163
{latitude && longitude && (
62-
<SettGroup title='View on Map'>
63-
<SettOption
64-
title='View on Map'
65-
Icon={<RoundedIcon Icon={MapsLocation02SolidIcon} className='bg-green-500' />}
66-
onPress={() =>
67-
Linking.openURL(`https://www.google.com/maps/search/?api=1&query=${latitude},${longitude}`)
68-
}
69-
arrow
70-
/>
71-
</SettGroup>
64+
<Animated.View layout={layout} entering={FadeIn} exiting={FadeOut}>
65+
<SettGroup title='Actions'>
66+
<SettOption
67+
title='View on Map'
68+
Icon={<RoundedIcon Icon={MapsLocation02SolidIcon} className='bg-green-500' />}
69+
onPress={() =>
70+
Linking.openURL(`https://www.google.com/maps/search/?api=1&query=${latitude},${longitude}`)
71+
}
72+
arrow
73+
/>
74+
<SettOption
75+
title='Share Location'
76+
Icon={<RoundedIcon Icon={Sent02Icon} className='bg-blue-500' />}
77+
onPress={() =>
78+
Share.share({
79+
message: shareMessage(latitude || 0, longitude || 0, timestamp),
80+
})
81+
}
82+
arrow
83+
/>
84+
</SettGroup>
85+
</Animated.View>
7286
)}
7387
</Gap12>
7488
</>

src/screens/LocationNotes/lib.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { getLatitude, getLongitude } from '@utils/utils'
12
import { PermissionsAndroid, Platform } from 'react-native'
23
import Geolocation from 'react-native-geolocation-service'
34

@@ -22,6 +23,19 @@ export const fetchLocation = async (): Promise<Geolocation.GeoPosition> => {
2223
})
2324
}
2425

26+
export const watchLocation = async (
27+
onSuccess: (position: Geolocation.GeoPosition) => void,
28+
onError: (error: LocationError) => void,
29+
options: Geolocation.GeoWatchOptions = {},
30+
): Promise<number> => {
31+
const hasPermission = await requestPermissions()
32+
if (!hasPermission) {
33+
onError({ code: 1, message: 'Location permission not granted' })
34+
return -1
35+
}
36+
return Geolocation.watchPosition(onSuccess, onError, options)
37+
}
38+
2539
export const requestPermissions = async (): Promise<boolean> => {
2640
try {
2741
if (Platform.OS === 'ios') {
@@ -44,3 +58,9 @@ export const requestPermissions = async (): Promise<boolean> => {
4458
return false
4559
}
4660
}
61+
62+
export function shareMessage(latitude: number, longitude: number, timestamp?: number) {
63+
const coords = `${getLatitude(latitude)}, ${getLongitude(longitude)}`
64+
65+
return `📍 I've marked this location ${coords} and want to share it with you!\n\nView on Google Maps: https://www.google.com/maps/search/?api=1&query=${latitude},${longitude}`
66+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import SettWrapper from '@components/Settings/SettWrapper'
2+
import { watchLocation } from '@screens/LocationNotes/lib'
3+
import LocationDetails from '@screens/LocationNotes/LocationDetails'
4+
import { Bold, SemiBold } from '@utils/fonts'
5+
import { useEffect, useState } from 'react'
6+
import { View } from 'react-native'
7+
import Geolocation from 'react-native-geolocation-service'
8+
9+
export default function LocationSpeed() {
10+
const [pos, setPos] = useState<Geolocation.GeoPosition | null>(null)
11+
12+
useEffect(() => {
13+
let watchId: number
14+
15+
const startLocationWatch = async () => {
16+
watchId = await watchLocation(
17+
(position) => {
18+
console.log(position)
19+
setPos(position)
20+
},
21+
(err) => {
22+
console.log(err)
23+
},
24+
{
25+
enableHighAccuracy: true,
26+
interval: 1000,
27+
},
28+
)
29+
}
30+
31+
startLocationWatch()
32+
33+
return () => {
34+
if (watchId !== undefined && watchId !== -1) {
35+
Geolocation.clearWatch(watchId)
36+
Geolocation.stopObserving()
37+
}
38+
}
39+
}, [])
40+
41+
let speed = pos?.coords.speed || 0
42+
let s = speed < 10 ? speed.toFixed(1) : speed.toFixed(0)
43+
44+
return (
45+
<SettWrapper title='Location Speed'>
46+
<View className='py-20 pb-16'>
47+
<Bold className='text pl-12 text-center' style={{ fontSize: 100, lineHeight: 100 }}>
48+
{s}
49+
<Bold style={{ fontSize: 30 }}>m/s</Bold>
50+
</Bold>
51+
<SemiBold className='text text-center text-xl'>Your Speed</SemiBold>
52+
</View>
53+
<LocationDetails data={pos} />
54+
</SettWrapper>
55+
)
56+
}

src/screens/Try/TyrItOut.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ const tools: Group[] = [
6060
{ title: 'Your Age', Icon: Calendar03SolidIcon, to: 'YourAge', className: 'bg-rose-500' },
6161
{ title: 'Random Color', Icon: PaintBoardSolidIcon, to: 'RandomColor', className: 'bg-orange-500' },
6262
{ title: 'Gradient Colors', Icon: ColorsSolidIcon, className: 'bg-green-500' },
63-
{ title: 'Location Speed Meter', Icon: Rocket01Icon, className: 'bg-yellow-500' },
63+
{ title: 'Location Speed Meter', Icon: Rocket01Icon, className: 'bg-yellow-500', to: 'LocationSpeed' },
6464
{ title: 'Location Notes', Icon: MapsLocation02SolidIcon, className: 'bg-accent', to: 'LocationNotes' },
6565
{ title: 'Random Password', Icon: LockPasswordSolidIcon, className: 'bg-slate-500', to: 'RandomPassword' },
6666
],
@@ -162,4 +162,4 @@ export default function TyrItOut({ navigation }: NavProp) {
162162

163163
function delayedFadeAnimation(search: string, i: number) {
164164
return FadeIn.duration(250).delay(search.trim().length === 0 ? Math.min((i + 1) * 25, 500) : 20)
165-
}
165+
}

0 commit comments

Comments
 (0)