Skip to content

Commit 2e1628e

Browse files
committed
feat: Enhance Coordinate Notes functionality; add update and delete operations, improve UI components, and integrate location mapping
1 parent 2837de4 commit 2e1628e

File tree

6 files changed

+194
-56
lines changed

6 files changed

+194
-56
lines changed

.github/copilot-instructions.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
I am using bun package manager
2-
Do not add unnecessary comments for my understanding.
3-
Icons are used from hugeicons if you want to add extra icons to the library, you can add it running `ic-old iconName` command.
4-
`iconName` is from the hugeicons website.
1+
- I am using bun package manager
2+
3+
- Do not add unnecessary comments for my understanding.
4+
5+
- Icons are used from hugeicons if you want to add extra icons to the library, you can add it running `ic-old iconName` command.
6+
`iconName` is from the hugeicons website.
7+
8+
- No need to fix formatting related issues.

src/screens/CoordinateNotes/CoordinateNote.tsx

Lines changed: 94 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,19 @@
1+
import popupStore from '@/zustand/popupStore'
2+
import { Home01SolidIcon, Location01Icon, MapsLocation02SolidIcon } from '@assets/icons/icons'
3+
import Btn from '@components/Button'
4+
import { Gap12 } from '@components/Gap'
5+
import { Input } from '@components/Input'
6+
import RoundedIcon from '@components/RoundedIcon'
7+
import SettGroup from '@components/Settings/SettGroup'
8+
import { SettOption } from '@components/Settings/SettOption'
9+
import SettText from '@components/Settings/SettText'
110
import SettWrapper from '@components/Settings/SettWrapper'
211
import { RouteProp } from '@react-navigation/native'
3-
import { LocationNote } from '@screens/CoordinateNotes/coordinateNotesStore'
4-
import { Medium } from '@utils/fonts'
12+
import { coordinateNotesStore, LocationNote } from '@screens/CoordinateNotes/coordinateNotesStore'
513
import type { StackNav } from '@utils/types'
14+
import { useCallback, useEffect, useState } from 'react'
15+
import { BackHandler, View } from 'react-native'
16+
import LocationDetails from './LocationDetails'
617

718
type ParamList = {
819
CoordinateNote: CoordinateNoteParamList
@@ -18,12 +29,91 @@ type CoordinateNoteProps = {
1829
}
1930

2031
export default function CoordinateNote({ navigation, route }: CoordinateNoteProps) {
32+
const alert = popupStore((store) => store.alert)
2133
const data = route.params.data
34+
const [name, setName] = useState<string>(data.title || '')
35+
const [description, setDescription] = useState<string>(data.description || '')
36+
const updateNote = coordinateNotesStore((state) => state.updateNote)
37+
const deleteNote = coordinateNotesStore((state) => state.deleteNote)
38+
39+
const handleSave = useCallback(() => {
40+
if (!name || name.trim().length === 0) return
41+
updateNote({
42+
title: name.trim(),
43+
description: description.trim(),
44+
location: data.location,
45+
})
46+
// eslint-disable-next-line react-hooks/exhaustive-deps
47+
}, [data.location, description, name])
48+
49+
const handleRemove = useCallback(() => {
50+
alert('Delete this note?', 'Are you sure you want to delete this note? This action cannot be undone.', [
51+
{ text: 'Cancel' },
52+
{
53+
text: 'Delete',
54+
onPress: () => {
55+
deleteNote(data.location.timestamp)
56+
navigation.goBack()
57+
},
58+
},
59+
])
60+
// eslint-disable-next-line react-hooks/exhaustive-deps
61+
}, [])
62+
63+
const onBackPress = useCallback(() => {
64+
handleSave()
65+
return false
66+
}, [handleSave])
67+
68+
useEffect(() => {
69+
const backHandler = BackHandler.addEventListener('hardwareBackPress', onBackPress)
70+
return () => backHandler.remove()
71+
}, [onBackPress])
72+
73+
useEffect(() => {
74+
const timer = setTimeout(handleSave, 500)
75+
return () => clearTimeout(timer)
76+
}, [handleSave])
2277

2378
return (
2479
<>
25-
<SettWrapper navigation={navigation} title={data.title}>
26-
<Medium>{JSON.stringify(data, null, 2)}</Medium>
80+
<SettWrapper navigation={navigation} title={name || 'Location Note'}>
81+
<Gap12 className='mt-3'>
82+
<SettGroup title='Location Name'>
83+
<Input
84+
Icon={<RoundedIcon Icon={Location01Icon} />}
85+
placeholder='Enter a name for this location'
86+
value={name}
87+
onChangeText={setName}
88+
/>
89+
</SettGroup>
90+
<SettGroup title='Description'>
91+
<Input
92+
placeholder='This is a input field with multiple lines. You can type as much as you want.'
93+
multiline
94+
numberOfLines={10}
95+
value={description}
96+
onChangeText={setDescription}
97+
Icon={<RoundedIcon Icon={MapsLocation02SolidIcon} className='bg-slate-500' />}
98+
/>
99+
</SettGroup>
100+
<SettGroup title='Tag'>
101+
<SettOption
102+
title=''
103+
placeholder='Add a tag'
104+
Icon={<RoundedIcon Icon={Home01SolidIcon} className='bg-rose-500' />}
105+
onPress={() => navigation.navigate('LocationTags')}
106+
arrow
107+
/>
108+
</SettGroup>
109+
<LocationDetails data={data.location} />
110+
<SettText>
111+
Deleting this note will remove it from the list of coordinate notes. This action cannot be undone.
112+
</SettText>
113+
<View className='mt-5 px-5'>
114+
<Btn title={'Delete Note'} className='bg-red-500' onPress={handleRemove} />
115+
</View>
116+
</Gap12>
27117
</SettWrapper>
28118
</>
29119
)

src/screens/CoordinateNotes/CoordinateNotes.tsx

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { SettOption } from '@components/Settings/SettOption'
77
import SettText from '@components/Settings/SettText'
88
import SettWrapper from '@components/Settings/SettWrapper'
99
import { useNavigation } from '@react-navigation/native'
10-
import { coordinateNotesStore } from '@screens/CoordinateNotes/coordinateNotesStore'
10+
import { coordinateNotesStore, LocationNote } from '@screens/CoordinateNotes/coordinateNotesStore'
1111
import fabStyles from '@screens/Home/styles/fabStyles'
1212
import type { NavProp, StackNav } from '@utils/types'
1313
import Animated, { ZoomIn, ZoomOut } from 'react-native-reanimated'
@@ -20,30 +20,38 @@ export default function CoordinateNotes({ navigation }: NavProp) {
2020
<>
2121
<SettWrapper navigation={navigation} title='Coordinate Notes'>
2222
<NoNotes notes={notes} />
23-
<Gap12>
24-
<SettText className='mt-3.5'>
25-
{notes.length === 0
26-
? ''
27-
: `Found ${notes.length} note${notes.length > 1 ? 's' : ''}. Tap on a note to view details.`}
28-
</SettText>
29-
<SettGroup title='Notes'>
30-
{notes.map((item) => (
31-
<SettOption
32-
key={item.location.timestamp}
33-
title={item.title}
34-
Icon={<RoundedIcon Icon={Location01Icon} />}
35-
arrow
36-
onPress={() => navigation.navigate('CoordinateNote', { data: item })}
37-
/>
38-
))}
39-
</SettGroup>
40-
</Gap12>
23+
<NotesList notes={notes} />
4124
</SettWrapper>
4225
<FabButton />
4326
</>
4427
)
4528
}
4629

30+
function NotesList({ notes }: { notes: LocationNote[] }) {
31+
const navigation = useNavigation<StackNav>()
32+
if (!notes || notes.length === 0) return null
33+
return (
34+
<Gap12>
35+
<SettText className='mt-3.5'>
36+
{notes.length === 0
37+
? ''
38+
: `Found ${notes.length} note${notes.length > 1 ? 's' : ''}. Tap on a note to view details.`}
39+
</SettText>
40+
<SettGroup title='Notes'>
41+
{notes.map((item) => (
42+
<SettOption
43+
key={item.location.timestamp}
44+
title={item.title}
45+
Icon={<RoundedIcon Icon={Location01Icon} />}
46+
arrow
47+
onPress={() => navigation.navigate('CoordinateNote', { data: item })}
48+
/>
49+
))}
50+
</SettGroup>
51+
</Gap12>
52+
)
53+
}
54+
4755
export const FabButton = () => {
4856
const navigation = useNavigation<StackNav>()
4957
const bottom = useSafeAreaInsets().bottom

src/screens/CoordinateNotes/LocationDetails.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
EarthIcon,
44
LatitudeIcon,
55
LongitudeIcon,
6+
MapsLocation02SolidIcon,
67
Rocket01Icon,
78
Timer02Icon,
89
} from '@assets/icons/icons'
@@ -12,6 +13,7 @@ import SettGroup from '@components/Settings/SettGroup'
1213
import { SettOption } from '@components/Settings/SettOption'
1314
import { Txt } from '@components/Text'
1415
import { getLatitude, getLongitude } from '@utils/utils'
16+
import { Linking } from 'react-native'
1517
import { GeoPosition } from 'react-native-geolocation-service'
1618

1719
export default function LocationDetails({ data }: { data: GeoPosition | undefined }) {
@@ -56,6 +58,18 @@ export default function LocationDetails({ data }: { data: GeoPosition | undefine
5658
Right={<Txt skeleton={timestamp === undefined}>{new Date(timestamp || 0).toLocaleString()}</Txt>}
5759
/>
5860
</SettGroup>
61+
{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>
72+
)}
5973
</Gap12>
6074
</>
6175
)

src/screens/CoordinateNotes/NewCoordinateNotes.tsx

Lines changed: 34 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,64 @@
11
import { Home01SolidIcon, Location01Icon, MapsLocation02SolidIcon } from '@assets/icons/icons'
2-
import Btn, { BtnTransparent } from '@components/Button'
2+
import { BtnTransparent } from '@components/Button'
33
import { Gap12 } from '@components/Gap'
44
import { Input } from '@components/Input'
55
import RoundedIcon from '@components/RoundedIcon'
6-
import { PaddingBottom } from '@components/SafePadding'
76
import SettGroup from '@components/Settings/SettGroup'
87
import { SettOption } from '@components/Settings/SettOption'
98
import SettWrapper from '@components/Settings/SettWrapper'
109
import { coordinateNotesStore } from '@screens/CoordinateNotes/coordinateNotesStore'
1110
import { fetchLocation } from '@screens/CoordinateNotes/lib'
1211
import { useQuery } from '@tanstack/react-query'
1312
import { NavProp } from '@utils/types'
14-
import { useState } from 'react'
15-
import { View } from 'react-native'
13+
import { useCallback, useEffect, useState } from 'react'
14+
import { BackHandler, View } from 'react-native'
1615
import { GeoPosition } from 'react-native-geolocation-service'
1716
import LocationDetails from './LocationDetails'
1817

1918
export default function NewCoordinateNotes({ navigation }: NavProp) {
2019
const [name, setName] = useState<string>('')
2120
const [description, setDescription] = useState<string>('')
22-
const newNote = coordinateNotesStore((state) => state.newNote)
21+
const updateNote = coordinateNotesStore((state) => state.updateNote)
2322

24-
const { data, isFetching, refetch, isPending } = useQuery({
23+
const { data, isFetching, refetch } = useQuery({
2524
queryFn: fetchLocation,
2625
queryKey: ['currentLocation'],
2726
})
2827

29-
const isDisabled = name?.trim().length === 0 || isFetching || isPending
30-
31-
function handleSave() {
32-
console.log('Save Location Note')
33-
console.log(name, description, data)
34-
newNote({
35-
title: name,
36-
description,
28+
const handleSave = useCallback(() => {
29+
if (!name || name.trim().length === 0) return
30+
updateNote({
31+
title: name.trim(),
32+
description: description.trim(),
3733
location: data as GeoPosition,
3834
})
39-
navigation.goBack()
40-
}
35+
// eslint-disable-next-line react-hooks/exhaustive-deps
36+
}, [data, description, name])
37+
38+
const onBackPress = useCallback(() => {
39+
handleSave()
40+
return false
41+
}, [handleSave])
42+
43+
useEffect(() => {
44+
const backHandler = BackHandler.addEventListener('hardwareBackPress', onBackPress)
45+
return () => backHandler.remove()
46+
}, [onBackPress])
47+
48+
useEffect(() => {
49+
const timer = setTimeout(handleSave, 500)
50+
return () => clearTimeout(timer)
51+
}, [handleSave])
4152

4253
return (
43-
<SettWrapper
44-
className='flex-1'
45-
title={'New Location Note'}
46-
>
54+
<SettWrapper className='flex-1' title={name || 'New Location Note'}>
4755
<Gap12 className='mt-3'>
4856
<SettGroup title='Location Name'>
4957
<Input
50-
Icon={<RoundedIcon Icon={Location01Icon} className='bg-blue-500' />}
58+
Icon={<RoundedIcon Icon={Location01Icon} />}
5159
placeholder='Enter a name for this location'
5260
value={name}
5361
onChangeText={setName}
54-
autoFocus
5562
/>
5663
</SettGroup>
5764
<SettGroup title='Description'>
@@ -74,14 +81,12 @@ export default function NewCoordinateNotes({ navigation }: NavProp) {
7481
/>
7582
</SettGroup>
7683
<LocationDetails data={data} />
77-
<BtnTransparent
78-
title={isFetching ? 'Fetching...' : 'Refetch Location'}
79-
onPress={() => refetch()}
80-
disabled={isFetching}
81-
/>
8284
<View className='px-5'>
83-
<Btn title={'Save Location Note'} onPress={handleSave} disabled={isDisabled} />
84-
<PaddingBottom />
85+
<BtnTransparent
86+
title={isFetching ? 'Fetching...' : 'Refetch Location'}
87+
onPress={() => refetch()}
88+
disabled={isFetching}
89+
/>
8590
</View>
8691
</Gap12>
8792
</SettWrapper>

src/screens/CoordinateNotes/coordinateNotesStore.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,16 @@ type CoordinateNotesStore = {
1212
notes: LocationNote[]
1313
setNotes: (notes: LocationNote[]) => void
1414
newNote: (note: LocationNote) => void
15+
updateNote: (note: LocationNote) => void
16+
deleteNote: (timestamp: number) => void
1517
}
1618

1719
export const coordinateNotesStore = create<CoordinateNotesStore>((set) => ({
1820
notes: getNotes(),
1921
setNotes: (notes: LocationNote[]) => setNotes(notes, set),
2022
newNote: (note: LocationNote) => newNote(note, set),
23+
updateNote: (note: LocationNote) => updateNote(note, set),
24+
deleteNote: (timestamp: number) => deleteNote(timestamp, set),
2125
}))
2226

2327
type Set = (fn: (state: CoordinateNotesStore) => CoordinateNotesStore) => void
@@ -32,6 +36,19 @@ function newNote(note: LocationNote, set: Set) {
3236
setNotes(notes, set)
3337
}
3438

39+
function updateNote(note: LocationNote, set: Set) {
40+
const notes = getNotes()
41+
const index = notes.findIndex((n: LocationNote) => n.location.timestamp === note.location.timestamp)
42+
index !== -1 ? (notes[index] = note) : notes.push(note)
43+
setNotes(notes, set)
44+
}
45+
46+
function deleteNote(timestamp: number, set: Set) {
47+
const notes = getNotes()
48+
const filteredNotes = notes.filter((note: LocationNote) => note.location.timestamp !== timestamp)
49+
setNotes(filteredNotes, set)
50+
}
51+
3552
function setNotes(notes: LocationNote[], set: Set) {
3653
S.set('CoordinateNotes', JSON.stringify(notes))
3754
set((state) => ({ ...state, notes }))

0 commit comments

Comments
 (0)