Skip to content

Commit

Permalink
Show hotspot location in list and detail screen.
Browse files Browse the repository at this point in the history
  • Loading branch information
matthewcarlreetz committed Sep 18, 2023
1 parent ea4b51a commit 9179f7c
Show file tree
Hide file tree
Showing 12 changed files with 271 additions and 34 deletions.
2 changes: 1 addition & 1 deletion src/features/collectables/AntennaSetupScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ const AntennaSetupScreen = () => {
return undefined
}

return parseH3BNLocation(iotInfoAcc.info.location).reverse()
return parseH3BNLocation(iotInfoAcc.info.location)?.reverse()
}, [iotInfoAcc])

useEffect(() => {
Expand Down
4 changes: 2 additions & 2 deletions src/features/collectables/AssertLocationScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,15 +84,15 @@ const AssertLocationScreen = () => {
return undefined
}

return parseH3BNLocation(iotInfoAcc.info.location).reverse()
return parseH3BNLocation(iotInfoAcc.info.location)?.reverse()
}, [iotInfoAcc])

const mobileLocation = useMemo(() => {
if (!mobileInfoAcc?.info?.location) {
return undefined
}

return parseH3BNLocation(mobileInfoAcc.info.location).reverse()
return parseH3BNLocation(mobileInfoAcc.info.location)?.reverse()
}, [mobileInfoAcc])

const sameLocation = useMemo(() => {
Expand Down
56 changes: 38 additions & 18 deletions src/features/collectables/HotspotCompressedListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { useMint } from '@helium/helium-react-hooks'
import { Mints } from '../../utils/constants'
import { removeDashAndCapitalize } from '../../utils/hotspotNftsUtils'
import { HotspotWithPendingRewards } from '../../types/solana'
import useHotspotLocation from '../../hooks/useHotspotLocation'

export type HotspotListItemProps = {
hotspot: HotspotWithPendingRewards
Expand All @@ -32,6 +33,17 @@ const HotspotListItem = ({
content: { metadata },
} = hotspot

const eccCompact = useMemo(() => {
if (!metadata || !metadata?.attributes?.length) {
return undefined
}

return metadata.attributes.find((attr) => attr.trait_type === 'ecc_compact')
?.value
}, [metadata])

const streetAddress = useHotspotLocation(eccCompact)

const { info: iotMint } = useMint(IOT_MINT)
const { info: mobileMint } = useMint(MOBILE_MINT)

Expand Down Expand Up @@ -65,15 +77,6 @@ const HotspotListItem = ({
return formatLargeNumber(new BigNumber(num))
}, [hotspot, mobileMint])

const eccCompact = useMemo(() => {
if (!metadata || !metadata?.attributes?.length) {
return undefined
}

return metadata.attributes.find((attr) => attr.trait_type === 'ecc_compact')
?.value
}, [metadata])

const hasIotRewards = useMemo(
() => pendingIotRewards && pendingIotRewards.gt(new BN(0)),
[pendingIotRewards],
Expand Down Expand Up @@ -102,22 +105,39 @@ const HotspotListItem = ({
cache: 'force-cache',
}}
/>
<Box marginStart="m" marginVertical="s" flex={1}>
<Box
marginStart="m"
marginVertical="s"
flex={1}
justifyContent="center"
>
{metadata?.name && (
<Text textAlign="left" variant="subtitle2" adjustsFontSizeToFit>
<Text
textAlign="left"
variant="subtitle2"
numberOfLines={1}
adjustsFontSizeToFit
>
{removeDashAndCapitalize(metadata.name)}
</Text>
)}

<Box flexGrow={1} />

<Box flexDirection="row" marginEnd="s" alignItems="flex-end">
<Text variant="subtitle3" color="secondaryText">
{eccCompact ? ellipsizeAddress(eccCompact) : ''}
{streetAddress && (
<Text variant="body2" numberOfLines={1} adjustsFontSizeToFit>
{streetAddress}
</Text>
</Box>
)}

<Text
variant="subtitle3"
color="secondaryText"
numberOfLines={1}
adjustsFontSizeToFit
>
{eccCompact ? ellipsizeAddress(eccCompact) : ''}
</Text>
</Box>
<Box marginVertical="s" marginEnd="s">
<Box marginVertical="s" marginHorizontal="s">
{!!hasMobileRewards && (
<Box
marginBottom="s"
Expand Down
17 changes: 16 additions & 1 deletion src/features/collectables/HotspotDetailsScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import { ellipsizeAddress } from '@utils/accountUtils'
import { toNumber } from '@helium/spl-utils'
import { useEntityKey } from '@hooks/useEntityKey'
import { useIotInfo } from '@hooks/useIotInfo'
import { FadeIn } from 'react-native-reanimated'
import useHotspotLocation from '@hooks/useHotspotLocation'
import { ww } from '../../utils/layout'
import {
CollectableNavigationProp,
Expand All @@ -47,6 +49,7 @@ const HotspotDetailsScreen = () => {
const { collectable } = route.params
const entityKey = useEntityKey(collectable)
const iotInfoAcc = useIotInfo(entityKey)
const streetAddress = useHotspotLocation(entityKey)

const pendingIotRewards =
collectable &&
Expand Down Expand Up @@ -211,7 +214,19 @@ const HotspotDetailsScreen = () => {
}}
/>
</Box>
<Box marginTop="l" marginBottom="m">
{streetAddress && (
<ReAnimatedBox entering={FadeIn}>
<Text variant="body1" marginTop="l" textAlign="center">
{streetAddress || ' '}
</Text>
</ReAnimatedBox>
)}
{!streetAddress && (
<Text variant="body1" marginTop="l" textAlign="center">
{' '}
</Text>
)}
<Box marginTop="m">
<Text variant="body1" marginBottom="ms">
{t('collectablesScreen.hotspots.pendingRewardsTitle')}
</Text>
Expand Down
9 changes: 5 additions & 4 deletions src/features/collectables/HotspotList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,10 @@ import { formatLargeNumber } from '../../utils/accountUtils'
import HotspotCompressedListItem from './HotspotCompressedListItem'
import HotspotListItem from './HotspotListItem'
import { NFTSkeleton } from './NftListItem'
import { CollectableNavigationProp } from './collectablesTypes'

export const DEFAULT_PAGE_AMOUNT = 20
import {
CollectableNavigationProp,
DEFAULT_PAGE_AMOUNT,
} from './collectablesTypes'

function RewardItem({
mint,
Expand Down Expand Up @@ -301,7 +302,7 @@ const HotspotList = () => {
hotspot={item}
onPress={handleNavigateToCollectable}
key={item.id}
marginBottom="xs"
marginBottom="s"
/>
)
}
Expand Down
2 changes: 2 additions & 0 deletions src/features/collectables/collectablesTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,5 @@ export type CollectableStackParamList = {

export type CollectableNavigationProp =
StackNavigationProp<CollectableStackParamList>

export const DEFAULT_PAGE_AMOUNT = 20
79 changes: 79 additions & 0 deletions src/hooks/useHotspotLocation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { useEffect, useMemo } from 'react'
import { useSelector } from 'react-redux'
import {
iotInfoKey,
mobileInfoKey,
rewardableEntityConfigKey,
} from '@helium/helium-entity-manager-sdk'
import { useAsync } from 'react-async-hook'
import { useAppDispatch } from '../store/store'
import { getGeocodedAddress } from '../store/slices/locationSlice'
import { RootState } from '../store/rootReducer'
import { useSolana } from '../solana/SolanaProvider'
import { IOT_SUB_DAO_KEY, MOBILE_SUB_DAO_KEY } from '../utils/constants'

const useHotspotLocation = (entityKey?: string) => {
const dispatch = useAppDispatch()
const { locations } = useSelector((state: RootState) => state.location)
const { hemProgram } = useSolana()

const { result: location } = useAsync(async () => {
const [iotConfigKey] = rewardableEntityConfigKey(IOT_SUB_DAO_KEY, 'IOT')
const [iotKey] = iotInfoKey(iotConfigKey, entityKey || '')
const iotInfo = await hemProgram?.account.iotHotspotInfoV0.fetch(iotKey)
if (iotInfo?.location) {
const loc = iotInfo.location
if (loc && loc.toString('hex') !== '0') {
return loc
}
}

const [mobileConfigKey] = rewardableEntityConfigKey(
MOBILE_SUB_DAO_KEY,
'MOBILE',
)
const [mobileKey] = mobileInfoKey(mobileConfigKey, entityKey || '')
const mobileInfo = await hemProgram?.account.mobileHotspotInfoV0.fetch(
mobileKey,
)

if (mobileInfo?.location) {
const loc = mobileInfo.location
if (loc && loc.toString('hex') !== '0') {
return loc
}
}
}, [])

useEffect(() => {
if (!entityKey || !location) return
const geo = locations[location?.toString('hex') || '']
if (geo) return

dispatch(getGeocodedAddress({ location }))
}, [dispatch, entityKey, location, locations])

const streetAddress = useMemo(() => {
const geo = locations[location?.toString('hex') || '']
if (!geo || !geo.features?.length) return ''

const feature = geo.features[0]

const place = feature.context?.find((c) => c.id.includes('place'))
const region = feature.context?.find((c) => c.id.includes('region'))

if (place && region) {
return `${place.text}, ${region.text}`
}

if (place) {
return place
}

return region
}, [location, locations])

return streetAddress
}

export default useHotspotLocation
57 changes: 52 additions & 5 deletions src/hooks/useReverseGeo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,8 @@ import { useAsync } from 'react-async-hook'

export const useReverseGeo = (coords: number[] | undefined) =>
useAsync(async () => {
if (!coords) return ''

const response = await axios.get(
`https://api.mapbox.com/geocoding/v5/mapbox.places/${coords[0]},${coords[1]}.json?access_token=${Config.MAPBOX_ACCESS_TOKEN}`,
)
const response = await reverseGeoCode(coords)
if (!response) return ''

const placeName = response.data.features[0].place_name
const parts = placeName.split(',')
Expand All @@ -18,3 +15,53 @@ export const useReverseGeo = (coords: number[] | undefined) =>

return `${address}, ${city}, ${state}`
}, [coords])

export const reverseGeoCode = async (coords: number[] | undefined) => {
if (!coords) return

return axios.get<undefined, { data: Geo }>(
`https://api.mapbox.com/geocoding/v5/mapbox.places/${coords[0]},${coords[1]}.json?access_token=${Config.MAPBOX_ACCESS_TOKEN}`,
)
}

export interface Geo {
type: string
query: number[]
features: Feature[]
attribution: string
}

export interface Feature {
id: string
type: string
place_type: string[]
relevance: number
properties: Properties
text: string
place_name: string
center: number[]
geometry: Geometry
address?: string
context?: Context[]
bbox?: number[]
}

export interface Properties {
accuracy?: string
mapbox_id: string
wikidata?: string
short_code?: string
}

export interface Geometry {
type: string
coordinates: number[]
}

export interface Context {
id: string
mapbox_id: string
text: string
wikidata?: string
short_code?: string
}
4 changes: 4 additions & 0 deletions src/store/rootReducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ import authReducer, { name as authSliceName } from './slices/authSlice'
import hotspotReducer, {
name as hotspotSliceName,
} from './slices/hotspotsSlice'
import locationReducer, {
name as locationSliceName,
} from './slices/locationSlice'
import browserReducer, { name as browserSliceName } from './slices/browserSlice'

const solanaConfig = {
Expand Down Expand Up @@ -53,6 +56,7 @@ const reducer = combineReducers({
[appSliceName]: appReducer,
[hotspotSliceName]: hotspotReducer,
[browserSliceName]: browserReducer,
[locationSliceName]: locationReducer,
})

export const rootReducer = (state: RootState, action: AnyAction) => {
Expand Down
2 changes: 1 addition & 1 deletion src/store/slices/hotspotsSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { AnchorProvider } from '@coral-xyz/anchor'
import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit'
import { Cluster, PublicKey } from '@solana/web3.js'
import { CompressedNFT, HotspotWithPendingRewards } from 'src/types/solana'
import { DEFAULT_PAGE_AMOUNT } from '../../features/collectables/HotspotList'
import { CSAccount } from '../../storage/cloudStorage'
import * as solUtils from '../../utils/solanaUtils'
import { DEFAULT_PAGE_AMOUNT } from '../../features/collectables/collectablesTypes'

export type WalletHotspots = {
hotspots: CompressedNFT[]
Expand Down
Loading

0 comments on commit 9179f7c

Please sign in to comment.