Skip to content

Commit

Permalink
US-1204 RIF Wallet - Create (init) global state loading component (#389)
Browse files Browse the repository at this point in the history
* chore: improve locked/unlocked behaviour + loading

* feat: rn-placeholder while waiting on the balances or prices

* fix: useStateSubsription test

* chore: remove unused variables

* refactor: IUsdPriceState rename

* fix: BitcoinCardComponent endless loading

* refactor: remove useEffect, space instead of 1

* fix: onScreenLock does not exist error

* fix: loop when deleting master key

* chore: remove unnecessary navigation to CreateKeysUX

Co-authored-by: Alexander Evchenko <[email protected]>
  • Loading branch information
TravellerOnTheRun and Alexander Evchenko authored Jan 17, 2023
1 parent 9b50934 commit dad59be
Show file tree
Hide file tree
Showing 27 changed files with 405 additions and 305 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
"react-redux": "^8.0.5",
"readable-stream": "1.0.33",
"redux-persist": "^6.0.0",
"rn-placeholder": "^3.0.3",
"socket.io-client": "^4.4.1",
"stream-browserify": "^1.0.0",
"url-parse": "^1.5.9",
Expand Down
1 change: 1 addition & 0 deletions src/components/PinManager/PinContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useState, useMemo } from 'react'

import PinScreen from './PinScreen'
import { PinContainerType } from './PinScreen/PinScreen'

Expand Down
4 changes: 2 additions & 2 deletions src/components/typography/CustomText.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { ReactNode } from 'react'
import { Text, TextStyle } from 'react-native'
import { Text, TextProps, TextStyle } from 'react-native'

interface CustomTextType {
font: string
}

export interface TextType {
export interface TextType extends TextProps {
children: ReactNode
style?: TextStyle
accessibilityLabel?: string
Expand Down
6 changes: 2 additions & 4 deletions src/components/typography/RegularText.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { fonts } from '../../styles/fonts'
import { fonts } from 'src/styles/fonts'
import { CustomText, TextType } from './CustomText'

const RegularText = ({ children, ...props }: TextType) => (
export const RegularText = ({ children, ...props }: TextType) => (
<CustomText font={fonts.regular} {...props}>
{children}
</CustomText>
)

export default RegularText
2 changes: 1 addition & 1 deletion src/components/typography/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const Paragraph = ({ children, testID, style }: Props) => (
</Text>
)

export { default as RegularText } from './RegularText'
export { RegularText } from './RegularText'
export { default as MediumText } from './MediumText'
export { default as SemiBoldText } from './SemiBoldText'

Expand Down
78 changes: 36 additions & 42 deletions src/core/Core.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback, useEffect, useState } from 'react'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { StatusBar, StyleSheet, View } from 'react-native'
import { useSafeAreaInsets } from 'react-native-safe-area-context'

Expand All @@ -21,32 +21,38 @@ import {
import { WalletConnectProviderElement } from 'screens/walletConnect/WalletConnectContext'
import { useRifSockets } from 'src/subscriptions/useRifSockets'
import { LoadingScreen } from 'components/loading/LoadingScreen'
import { useSetGlobalError } from 'components/GlobalErrorHandler'
import { Cover } from './components/Cover'
import { RequestPIN } from './components/RequestPIN'
import { useBitcoinCore } from './hooks/bitcoin/useBitcoinCore'
import { useStateSubscription } from './hooks/useStateSubscription'
import { useAppDispatch, useAppSelector } from 'store/storeUtils'
import {
closeRequest,
removeKeysFromState,
resetKeysAndPin,
selectKMS,
selectRequests,
selectSelectedWallet,
selectSettingsIsLoading,
selectTopColor,
selectWallets,
setChainId,
unlockApp,
} from 'store/slices/settingsSlice'
import { hasKeys, hasPin } from 'storage/MainStorage'
import {
hasKeys as hasKeysInStorage,
hasPin as hasPinInStorage,
} from 'storage/MainStorage'
import { BitcoinProvider } from 'core/hooks/bitcoin/BitcoinContext'

export const navigationContainerRef =
createNavigationContainerRef<RootStackParamList>()

export const Core = () => {
const { hasKeys, hasPin } = useMemo(
() => ({
hasKeys: hasKeysInStorage(),
hasPin: hasPinInStorage(),
}),
[],
)
const dispatch = useAppDispatch()

const selectedWallet = useAppSelector(selectSelectedWallet)
Expand All @@ -59,31 +65,22 @@ export const Core = () => {
const topColor = useAppSelector(selectTopColor)

const BitcoinCore = useBitcoinCore()
const onScreenLock = () => dispatch(removeKeysFromState())

const { unlocked, setUnlocked, active } = useStateSubscription(onScreenLock)
const { unlocked, active } = useStateSubscription()

const [currentScreen, setCurrentScreen] = useState<string>(
rootStackRouteNames.Home,
)
const handleScreenChange = (newState: NavigationState | undefined) => {
if (newState && newState.routes[newState.index]) {
setCurrentScreen(newState.routes[newState.index].name)
} else {
setCurrentScreen(rootStackRouteNames.Home)
}
}

const setGlobalError = useSetGlobalError()

const onScreenUnlock = async () => {
try {
await dispatch(unlockApp())
setUnlocked(true)
} catch (err) {
setGlobalError(err.toString())
}
}
const handleScreenChange = useCallback(
(newState: NavigationState | undefined) => {
if (newState && newState.routes[newState.index]) {
setCurrentScreen(newState.routes[newState.index].name)
} else {
setCurrentScreen(rootStackRouteNames.Home)
}
},
[],
)

const retrieveChainId = useCallback(
async (wallet: RIFWallet) => {
Expand Down Expand Up @@ -113,7 +110,7 @@ export const Core = () => {
}
}, [selectedWallet, retrieveChainId, wallets])

if (settingsIsLoading) {
if (settingsIsLoading && !unlocked) {
return <LoadingScreen />
}

Expand All @@ -125,13 +122,8 @@ export const Core = () => {
},
})

if (hasKeys() && hasPin() && !unlocked) {
return (
<RequestPIN
unlock={onScreenUnlock}
resetKeysAndPin={() => dispatch(resetKeysAndPin())}
/>
)
if (hasKeys && hasPin && !unlocked) {
return <RequestPIN />
}

return (
Expand All @@ -143,14 +135,16 @@ export const Core = () => {
onStateChange={handleScreenChange}
ref={navigationContainerRef}>
<WalletConnectProviderElement>
<RootNavigationComponent currentScreen={currentScreen} />

{requests.length !== 0 && (
<ModalComponent
closeModal={() => dispatch(closeRequest())}
request={requests[0]}
/>
)}
<>
<RootNavigationComponent currentScreen={currentScreen} />

{requests.length !== 0 && (
<ModalComponent
closeModal={() => dispatch(closeRequest())}
request={requests[0]}
/>
)}
</>
</WalletConnectProviderElement>
</NavigationContainer>
</BitcoinProvider>
Expand Down
4 changes: 2 additions & 2 deletions src/core/CoreWithStore.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Provider } from 'react-redux'
import { PersistGate } from 'redux-persist/integration/react'
import { persistor } from 'src/redux/store'
import { store } from 'store/store'

import { store, persistor } from 'store/store'
import { Core } from './Core'

export const CoreWithStore = () => (
Expand Down
44 changes: 30 additions & 14 deletions src/core/components/RequestPIN.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,45 @@
import { useCallback } from 'react'
import { getPin } from '../../storage/MainStorage'
import { PinContainer } from '../../components/PinManager/PinContainer'
import { useState } from 'react'
import { pinLength } from '../../shared/costants'
import { useCallback, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'

interface Props {
unlock: () => void
resetKeysAndPin: () => void
}
import { getPin } from 'storage/MainStorage'
import { PinContainer } from 'components/PinManager/PinContainer'
import { useSetGlobalError } from 'components/GlobalErrorHandler'
import { pinLength } from 'shared/costants'
import { useAppDispatch } from 'store/storeUtils'
import { resetKeysAndPin, unlockApp } from 'store/slices/settingsSlice'
import { useStateSubscription } from '../hooks/useStateSubscription'

export const RequestPIN = ({ unlock, resetKeysAndPin }: Props) => {
export const RequestPIN = () => {
const storedPin = useMemo(() => getPin(), [])
const { t } = useTranslation
const dispatch = useAppDispatch()
const [resetEnabled, setResetEnabled] = useState<boolean>(false)

const { setUnlocked } = useStateSubscription()

const setGlobalError = useSetGlobalError()

const onScreenUnlock = useCallback(async () => {
try {
await dispatch(unlockApp())
setUnlocked(true)
} catch (err) {
setGlobalError(err instanceof Error ? err.toString() : t('err_unknown'))
}
}, [dispatch, setGlobalError, setUnlocked, t])

const checkPin = useCallback(
(enteredPin: string) => {
try {
const storedPin = getPin()
if (storedPin === enteredPin) {
unlock()
onScreenUnlock()
} else {
setResetEnabled(true)
throw new Error('Pin do not match.')
}
} catch (err) {}
},
[unlock],
[onScreenUnlock, storedPin],
)

return (
Expand All @@ -32,7 +48,7 @@ export const RequestPIN = ({ unlock, resetKeysAndPin }: Props) => {
key={pinLength}
onPinSubmit={checkPin}
resetEnabled={resetEnabled}
resetKeysAndPin={resetKeysAndPin}
resetKeysAndPin={() => dispatch(resetKeysAndPin())}
/>
)
}
46 changes: 25 additions & 21 deletions src/core/hooks/bitcoin/useBitcoinCore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,15 @@ import BIP39 from 'lib/bitcoin/BIP39'
import BitcoinNetwork from 'lib/bitcoin/BitcoinNetwork'
import { BitcoinNetworkWithBIPRequest } from 'lib/bitcoin/types'
import { createAndInitializeBipWithRequest } from 'lib/bitcoin/utils'
import { KeyManagementSystem } from 'lib/core'

import {
BitcoinNetworkStore,
StoredBitcoinNetworkValue,
} from 'storage/BitcoinNetworkStore'
import { bitcoinTestnet } from 'shared/costants'
import { useStoredBitcoinNetworks } from './useStoredBitcoinNetworks'
import { getKeys } from 'storage/MainStorage'
import { useAppDispatch } from 'src/redux/storeUtils'
import { onRequest } from 'src/redux/slices/settingsSlice'
import { useAppDispatch, useAppSelector } from 'store/storeUtils'
import { onRequest, selectKMS } from 'store/slices/settingsSlice'

export interface UseBitcoinCoreResult {
networks: Array<BitcoinNetwork>
Expand All @@ -35,19 +33,14 @@ interface NetworksObject {
* @param request
*/

const keys = getKeys()
let mnemonic: string | null = null

if (keys) {
const { kms } = KeyManagementSystem.fromSerialized(keys)
mnemonic = kms.mnemonic
}

const BIP39Instance = mnemonic ? new BIP39(mnemonic) : null

export const useBitcoinCore = (): UseBitcoinCoreResult => {
const dispatch = useAppDispatch()
const kms = useAppSelector(selectKMS)
const [storedNetworks, refreshStoredNetworks] = useStoredBitcoinNetworks()
const BIP39Instance = useMemo(
() => (kms ? new BIP39(kms.mnemonic) : null),
[kms],
)
const networksObj = useRef<NetworksObject>({})
const storedNetworksValues = useMemo(
() => Object.values(storedNetworks),
Expand Down Expand Up @@ -83,14 +76,12 @@ export const useBitcoinCore = (): UseBitcoinCoreResult => {
)

const transformStoredNetworks = useCallback(
(values: StoredBitcoinNetworkValue[]) => {
if (values.length < 1 || !BIP39Instance) {
(values: StoredBitcoinNetworkValue[], bip39: BIP39) => {
if (values.length < 1) {
onNoNetworksPresent()
return null
}
const networksArr = values.map(item =>
transformNetwork(item, BIP39Instance),
)
const networksArr = values.map(item => transformNetwork(item, bip39))

return { networksArr, networksObj: networksObj.current }
},
Expand All @@ -102,11 +93,24 @@ export const useBitcoinCore = (): UseBitcoinCoreResult => {
onNoNetworksPresent()
return
}
const transformedNetworks = transformStoredNetworks(storedNetworksValues)

if (!BIP39Instance) {
return
}

const transformedNetworks = transformStoredNetworks(
storedNetworksValues,
BIP39Instance,
)
if (transformedNetworks) {
setNetworks(transformedNetworks)
}
}, [storedNetworksValues, onNoNetworksPresent, transformStoredNetworks])
}, [
storedNetworksValues,
onNoNetworksPresent,
transformStoredNetworks,
BIP39Instance,
])

return {
networks: networks.networksArr,
Expand Down
2 changes: 1 addition & 1 deletion src/core/hooks/useAppState.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useEffect, useState } from 'react'
import { AppState } from 'react-native'

export default function useAppState() {
export function useAppState() {
const [appState, setAppState] = useState(AppState.currentState)

useEffect(() => {
Expand Down
7 changes: 5 additions & 2 deletions src/core/hooks/useStateSubscription.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { renderHook } from '@testing-library/react-hooks'
import { AppState } from 'react-native'
import { act } from 'react-test-renderer'

import { createReduxWrapper } from 'testLib/ReduxWrapper'
import { useStateSubscription } from './useStateSubscription'

describe('hook: useStateSubscription', () => {
// in order to test the hook, we should test all the different scenarios in a single test
test('test some different scenarios', () => {
const { result } = renderHook(() => useStateSubscription())
const appStateSpy = jest.spyOn(AppState, 'addEventListener')
const { result } = renderHook(() => useStateSubscription(), {
wrapper: createReduxWrapper().ReduxWrapper,
})

// 1. initial state
act(() => {
Expand Down
Loading

0 comments on commit dad59be

Please sign in to comment.