diff --git a/ios/Podfile.lock b/ios/Podfile.lock index a85de240a..109a1773f 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -841,7 +841,7 @@ SPEC CHECKSUMS: BEMCheckBox: 5ba6e37ade3d3657b36caecc35c8b75c6c2b1a4e boost: 57d2868c099736d80fcd648bf211b4431e51a558 BVLinearGradient: 34a999fda29036898a09c6a6b728b0b4189e1a44 - Charts: ce0768268078eee0336f122c3c4ca248e4e204c5 + Charts: 354f86803d11d9c35de280587fef50d1af063978 DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54 EXApplication: d8f53a7eee90a870a75656280e8d4b85726ea903 EXBarCodeScanner: 8e23fae8d267dbef9f04817833a494200f1fce35 @@ -861,9 +861,9 @@ SPEC CHECKSUMS: FBLazyVector: f1897022b53abf1469d6ad692ee2c69f57d967f3 FBReactNativeSpec: 627fd07f1b9d498c9fa572e76d7f1a6b1ee9a444 fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9 - glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b + glog: 791fe035093b84822da7f0870421a25839ca7870 helium-react-native-sdk: 32c0a7e3abc733a7f3d291013b2db31475fc6980 - hermes-engine: 0784cadad14b011580615c496f77e0ae112eed75 + hermes-engine: 7a53ccac09146018a08239c5425625fdb79a6162 libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 MapboxCommon: fdf7fd31c90b7b607cd9c63e37797f023c01d860 MapboxCoreMaps: 24270c7c6b8cb71819fc2f3c549db9620ee4d019 @@ -871,7 +871,7 @@ SPEC CHECKSUMS: MapboxMobileEvents: de50b3a4de180dd129c326e09cd12c8adaaa46d6 MultiplatformBleAdapter: 5a6a897b006764392f9cef785e4360f54fb9477d OneSignalXCFramework: 81ceac017a290f23793443323090cfbe888f74ea - RCT-Folly: 424b8c9a7a0b9ab2886ffe9c3b041ef628fd4fb1 + RCT-Folly: 85766c3226c7ec638f05ad7cb3cf6a268d6c4241 RCTRequired: bd6045fbd511da5efe6db89eecb21e4e36bd7cbf RCTTypeSafety: c06d9f906faa69dd1c88223204c3a24767725fd8 React: b9ea33557ef1372af247f95d110fbdea114ed3b2 diff --git a/src/features/collectables/AntennaSetupScreen.tsx b/src/features/collectables/AntennaSetupScreen.tsx new file mode 100644 index 000000000..cd9911835 --- /dev/null +++ b/src/features/collectables/AntennaSetupScreen.tsx @@ -0,0 +1,206 @@ +import React, { useEffect, useState, useMemo, useCallback, memo } from 'react' +import BackScreen from '@components/BackScreen' +import { ReAnimatedBox } from '@components/AnimatedBox' +import Box from '@components/Box' +import ButtonPressable from '@components/ButtonPressable' +import CircleLoader from '@components/CircleLoader' +import SafeAreaBox from '@components/SafeAreaBox' +import Text from '@components/Text' +import TextInput from '@components/TextInput' +import useSubmitTxn from '@hooks/useSubmitTxn' +import { RouteProp, useNavigation, useRoute } from '@react-navigation/native' +import { useTranslation } from 'react-i18next' +import { + KeyboardAvoidingView, + Keyboard, + TouchableWithoutFeedback, +} from 'react-native' +import { useEntityKey } from '@hooks/useEntityKey' +import { useIotInfo } from '@hooks/useIotInfo' +import { Edge } from 'react-native-safe-area-context' +import { DelayedFadeIn } from '@components/FadeInOut' +import { + CollectableNavigationProp, + CollectableStackParamList, +} from './collectablesTypes' +import { parseH3BNLocation } from '../../utils/h3' +import * as Logger from '../../utils/logger' + +const BUTTON_HEIGHT = 65 +type Route = RouteProp +const AntennaSetupScreen = () => { + const { t } = useTranslation() + const nav = useNavigation() + const route = useRoute() + const { collectable } = route.params + const entityKey = useEntityKey(collectable) + const iotInfoAcc = useIotInfo(entityKey) + const safeEdges = useMemo(() => ['bottom'] as Edge[], []) + const backEdges = useMemo(() => ['top'] as Edge[], []) + const [hasSetDefaults, setHasSetDefaults] = useState(false) + const [gain, setGain] = useState() + const [elevation, setElevation] = useState() + const [updating, setUpdating] = useState(false) + const [transactionError, setTransactionError] = useState() + const { submitUpdateEntityInfo } = useSubmitTxn() + + const iotLocation = useMemo(() => { + if (!iotInfoAcc?.info?.location) { + return undefined + } + + return parseH3BNLocation(iotInfoAcc.info.location).reverse() + }, [iotInfoAcc]) + + useEffect(() => { + if (!hasSetDefaults && iotInfoAcc?.info) { + if (iotInfoAcc?.info?.gain) { + setGain(`${iotInfoAcc?.info?.gain / 10}`) + } + + if (iotInfoAcc?.info?.elevation) { + setElevation(`${iotInfoAcc?.info?.elevation}`) + } + + setHasSetDefaults(true) + } + }, [iotInfoAcc, setGain, setElevation, hasSetDefaults, setHasSetDefaults]) + + const handleUpdateElevGain = useCallback(async () => { + if (iotLocation && entityKey) { + setTransactionError(undefined) + setUpdating(true) + try { + await submitUpdateEntityInfo({ + type: 'iot', + entityKey, + lng: iotLocation[0], + lat: iotLocation[1], + elevation, + decimalGain: gain, + }) + nav.push('SettingUpAntennaScreen') + } catch (error) { + setUpdating(false) + Logger.error(error) + setTransactionError((error as Error).message) + } + } + }, [ + iotLocation, + entityKey, + elevation, + gain, + setUpdating, + setTransactionError, + submitUpdateEntityInfo, + nav, + ]) + + const showError = useMemo(() => { + if (transactionError) return transactionError + }, [transactionError]) + + return ( + + + Keyboard.dismiss()}> + + + + + {t('antennaSetupScreen.antennaSetup')} + + + {t('antennaSetupScreen.antennaSetupDescription')} + + + + + + + + + {showError && ( + + {showError} + + )} + + + + ) : undefined + } + /> + + + + + + + ) +} + +export default memo(AntennaSetupScreen) diff --git a/src/features/collectables/AssertLocationScreen.tsx b/src/features/collectables/AssertLocationScreen.tsx index 16b6aabc0..a0ab3ce7e 100644 --- a/src/features/collectables/AssertLocationScreen.tsx +++ b/src/features/collectables/AssertLocationScreen.tsx @@ -30,7 +30,12 @@ import React, { useRef, } from 'react' import { useTranslation } from 'react-i18next' -import { Alert, KeyboardAvoidingView } from 'react-native' +import { + Alert, + KeyboardAvoidingView, + Keyboard, + TouchableWithoutFeedback, +} from 'react-native' import { Config } from 'react-native-config' import { Edge } from 'react-native-safe-area-context' import 'text-encoding-polyfill' @@ -141,14 +146,16 @@ const AssertLocationScreen = () => { ]) useEffect(() => { - if (iotInfoAcc?.info?.gain) { - setGain(`${iotInfoAcc?.info?.gain / 10}`) - } + if (!elevGainVisible) { + if (iotInfoAcc?.info?.gain) { + setGain(`${iotInfoAcc?.info?.gain / 10}`) + } - if (iotInfoAcc?.info?.elevation) { - setElevation(`${iotInfoAcc?.info?.elevation}`) + if (iotInfoAcc?.info?.elevation) { + setElevation(`${iotInfoAcc?.info?.elevation}`) + } } - }, [iotInfoAcc, setGain, setElevation]) + }, [iotInfoAcc, elevGainVisible, setGain, setElevation]) const resetGain = useCallback( () => @@ -551,7 +558,7 @@ const AssertLocationScreen = () => { } TrailingComponent={ asserting ? ( - + ) : undefined } /> @@ -574,78 +581,86 @@ const AssertLocationScreen = () => { edges={backEdges} onClose={hideElevGain} > - - - - - {t('assertLocationScreen.antennaSetup')} - - - {t('assertLocationScreen.antennaSetupDescription')} - - - setGain(val), - multiline: true, - value: gain, - returnKeyType: 'next', - keyboardType: 'decimal-pad', - }} - /> - - Keyboard.dismiss()}> + + + + + {t('assertLocationScreen.antennaSetup')} + + + {t('assertLocationScreen.antennaSetupDescription')} + + + + + setElevation(val), - value: elevation, - keyboardType: 'decimal-pad', - }} + )}`} + textInputProps={{ + placeholder: t( + 'assertLocationScreen.elevationPlaceholder', + ), + onChangeText: setElevation, + value: elevation, + keyboardType: 'decimal-pad', + }} + /> + + + + - - - - - - + + + ) : undefined} diff --git a/src/features/collectables/CollectablesNavigator.tsx b/src/features/collectables/CollectablesNavigator.tsx index 5d403de6d..104becc5c 100644 --- a/src/features/collectables/CollectablesNavigator.tsx +++ b/src/features/collectables/CollectablesNavigator.tsx @@ -19,6 +19,8 @@ import ClaimAllRewardsScreen from './ClaimAllRewardsScreen' import ClaimingRewardsScreen from './ClaimingRewardsScreen' import CollectionScreen from './CollectionScreen' import NftDetailsScreen from './NftDetailsScreen' +import AntennaSetupScreen from './AntennaSetupScreen' +import SettingUpAntennaScreen from './SettingUpAntennaScreen' const CollectablesStack = createStackNavigator() @@ -41,6 +43,14 @@ const CollectablesStackScreen = () => { name="AssertLocationScreen" component={AssertLocationScreen} /> + + { const copyText = useCopyText() const { collectable } = route.params + const entityKey = useEntityKey(collectable) + const iotInfoAcc = useIotInfo(entityKey) + const pendingIotRewards = collectable && collectable.pendingRewards && @@ -75,6 +80,13 @@ const HotspotDetailsScreen = () => { }) }, [collectable, navigation]) + const handleAntennaSetup = useCallback(() => { + setOptionsOpen(false) + navigation.navigate('AntennaSetupScreen', { + collectable, + }) + }, [collectable, navigation]) + const handleClaimRewards = useCallback(() => { navigation.navigate('ClaimRewardsScreen', { hotspot: collectable, @@ -123,6 +135,15 @@ const HotspotDetailsScreen = () => { selected={false} hasPressedState={false} /> + {iotInfoAcc?.info?.location && ( + + )} { /> ), - [handleSend, handleAssertLocation, handleCopyAddress, t], + [ + handleSend, + handleAssertLocation, + handleAntennaSetup, + handleCopyAddress, + iotInfoAcc, + t, + ], ) return ( diff --git a/src/features/collectables/SettingUpAntennaScreen.tsx b/src/features/collectables/SettingUpAntennaScreen.tsx new file mode 100644 index 000000000..b43693ee2 --- /dev/null +++ b/src/features/collectables/SettingUpAntennaScreen.tsx @@ -0,0 +1,196 @@ +import React, { memo, useCallback } from 'react' +import Box from '@components/Box' +import { useAccountStorage } from '@storage/AccountStorageProvider' +import { useNavigation } from '@react-navigation/native' +import { TabBarNavigationProp } from 'src/navigation/rootTypes' +import { ReAnimatedBox } from '@components/AnimatedBox' +import { DelayedFadeIn } from '@components/FadeInOut' +import BackArrow from '@assets/images/backArrow.svg' +import AccountIcon from '@components/AccountIcon' +import { useSafeAreaInsets } from 'react-native-safe-area-context' +import { useTranslation } from 'react-i18next' +import { useSelector } from 'react-redux' +import Animated, { FadeIn, FadeOut } from 'react-native-reanimated' +import Text from '@components/Text' +import ButtonPressable from '@components/ButtonPressable' +import IndeterminateProgressBar from '@components/IndeterminateProgressBar' +import { parseTransactionError } from '@utils/solanaUtils' +import { useBN } from '@hooks/useBN' +import { useCurrentWallet } from '@hooks/useCurrentWallet' +import { useSolOwnedAmount } from '@helium/helium-react-hooks' +import { RootState } from '../../store/rootReducer' + +const SettingUpAntennaScreen = () => { + const { currentAccount } = useAccountStorage() + const navigation = useNavigation() + const wallet = useCurrentWallet() + const solBalance = useBN(useSolOwnedAmount(wallet).amount) + const { bottom } = useSafeAreaInsets() + + const { t } = useTranslation() + const solanaPayment = useSelector( + (reduxState: RootState) => reduxState.solana.payment, + ) + + const onReturn = useCallback(() => { + // Reset Collectables stack to first screen + navigation.reset({ + index: 0, + routes: [{ name: 'Collectables' }], + }) + }, [navigation]) + + if (!currentAccount) { + return null + } + + return ( + + + + + + + {solanaPayment && !solanaPayment.error && !solanaPayment.loading && ( + + + {t('antennaSetupScreen.settingUpComplete')} + + + {t('antennaSetupScreen.settingUpCompleteBody')} + + + )} + + {solanaPayment?.error && ( + + + {t('collectablesScreen.rewardsError')} + + + {parseTransactionError( + solBalance, + solanaPayment?.error?.message, + )} + + + )} + + {!solanaPayment && ( + + + {t('antennaSetupScreen.settingUpError')} + + + )} + + {solanaPayment && solanaPayment.loading && ( + + + {t('antennaSetupScreen.settingUp')} + + + {t('antennaSetupScreen.settingUpBody')} + + + + + + )} + + + + } + /> + + + + ) +} + +export default memo(SettingUpAntennaScreen) diff --git a/src/features/collectables/collectablesTypes.ts b/src/features/collectables/collectablesTypes.ts index 19a499c71..0b4e65a8b 100644 --- a/src/features/collectables/collectablesTypes.ts +++ b/src/features/collectables/collectablesTypes.ts @@ -18,14 +18,16 @@ export type CollectableStackParamList = { AssertLocationScreen: { collectable: HotspotWithPendingRewards } + AntennaSetupScreen: { + collectable: HotspotWithPendingRewards + } + SettingUpAntennaScreen: undefined PaymentScreen: undefined | PaymentRouteParam - ClaimRewardsScreen: { hotspot: HotspotWithPendingRewards } ClaimAllRewardsScreen: undefined ClaimingRewardsScreen: undefined - CollectionScreen: { collection: Collectable[] } @@ -41,7 +43,6 @@ export type CollectableStackParamList = { TransferCompleteScreen: { collectable: CompressedNFT | Collectable } - AddNewContact: undefined PaymentQrScanner: undefined AddressBookNavigator: undefined diff --git a/src/hooks/useIotInfo.ts b/src/hooks/useIotInfo.ts index f067aa22a..4a605a942 100644 --- a/src/hooks/useIotInfo.ts +++ b/src/hooks/useIotInfo.ts @@ -1,11 +1,9 @@ import { IdlAccounts } from '@coral-xyz/anchor' -import { UseAccountState } from '@helium/account-fetch-cache-hooks' import { iotInfoKey, rewardableEntityConfigKey, } from '@helium/helium-entity-manager-sdk' -import { useIdlAccount } from '@helium/helium-react-hooks' -import { IDL } from '@helium/idls/lib/esm/helium_entity_manager' +import { useAnchorAccount } from '@helium/helium-react-hooks' import { HeliumEntityManager } from '@helium/idls/lib/types/helium_entity_manager' import { PublicKey } from '@solana/web3.js' import { IOT_SUB_DAO_KEY } from '@utils/constants' @@ -16,16 +14,11 @@ export type IotHotspotInfoV0 = pubKey: PublicKey } -export const useIotInfo = ( - entityKey: string | undefined, -): UseAccountState | undefined => { +export const useIotInfo = (entityKey: string | undefined) => { const [iotConfigKey] = rewardableEntityConfigKey(IOT_SUB_DAO_KEY, 'IOT') const [iotInfo] = iotInfoKey(iotConfigKey, entityKey || '') + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - return useIdlAccount( - iotInfo, - IDL as HeliumEntityManager, - type, - ) + return useAnchorAccount(iotInfo, type) } diff --git a/src/hooks/useMobileInfo.ts b/src/hooks/useMobileInfo.ts index 4cdd65706..40272d8d5 100644 --- a/src/hooks/useMobileInfo.ts +++ b/src/hooks/useMobileInfo.ts @@ -1,11 +1,9 @@ import { IdlAccounts } from '@coral-xyz/anchor' -import { UseAccountState } from '@helium/account-fetch-cache-hooks' import { mobileInfoKey, rewardableEntityConfigKey, } from '@helium/helium-entity-manager-sdk' -import { useIdlAccount } from '@helium/helium-react-hooks' -import { IDL } from '@helium/idls/lib/esm/helium_entity_manager' +import { useAnchorAccount } from '@helium/helium-react-hooks' import { HeliumEntityManager } from '@helium/idls/lib/types/helium_entity_manager' import { PublicKey } from '@solana/web3.js' import { MOBILE_SUB_DAO_KEY } from '@utils/constants' @@ -16,19 +14,14 @@ export type MobileHotspotInfoV0 = pubKey: PublicKey } -export const useMobileInfo = ( - entityKey: string | undefined, -): UseAccountState | undefined => { +export const useMobileInfo = (entityKey: string | undefined) => { const [mobileConfigKey] = rewardableEntityConfigKey( MOBILE_SUB_DAO_KEY, 'MOBILE', ) const [mobileInfo] = mobileInfoKey(mobileConfigKey, entityKey || '') + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - return useIdlAccount( - mobileInfo, - IDL as HeliumEntityManager, - type, - ) + return useAnchorAccount(mobileInfo, type) } diff --git a/src/locales/en.ts b/src/locales/en.ts index 4aaaa8184..c6b1a3d4e 100644 --- a/src/locales/en.ts +++ b/src/locales/en.ts @@ -324,6 +324,21 @@ export default { locationNotFound: 'Location not found, Please try again.', mobileTitle: 'MOBILE', }, + antennaSetupScreen: { + title: 'Antenna Setup', + antennaSetup: 'Antenna Setup', + antennaSetupDescription: + 'Submit gain and elevation details for your Hotspot', + gainPlaceholder: 'TX / RX Gain (dBi)', + elevationPlaceholder: 'Elevation (meters)', + submit: 'Update Antenna', + settingUp: 'Setting up your antenna...', + settingUpBody: 'Please wait while we update your Antenna!', + settingUpError: 'Antenna Setup failed. Please try again later.', + settingUpComplete: 'Antenna Setup!', + settingUpCompleteBody: + 'We’ve updated the gain and elevation of your antenna.', + }, swapsScreen: { title: 'Swap my Tokens', swapTokens: 'Swap Tokens',