diff --git a/.editorconfig copy b/.editorconfig copy new file mode 100755 index 000000000..cb8b48c70 --- /dev/null +++ b/.editorconfig copy @@ -0,0 +1,9 @@ +# Editor configuration, see http://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true \ No newline at end of file diff --git a/android/app/build.gradle b/android/app/build.gradle index 52b4693c4..e20961056 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -260,6 +260,7 @@ dependencies { //noinspection GradleDynamicVersion implementation "com.facebook.react:react-native:+" // From node_modules + implementation project(':react-native-keychain') implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0" implementation "androidx.core:core-splashscreen:1.0.0" diff --git a/android/app/src/main/java/com/templewallet/MainApplication.java b/android/app/src/main/java/com/templewallet/MainApplication.java index 9ce3fa3ff..423381e21 100644 --- a/android/app/src/main/java/com/templewallet/MainApplication.java +++ b/android/app/src/main/java/com/templewallet/MainApplication.java @@ -9,6 +9,8 @@ import com.facebook.react.ReactPackage; import com.facebook.react.config.ReactFeatureFlags; import com.facebook.soloader.SoLoader; +import com.oblador.keychain.KeychainPackage; +import com.oblador.keychain.KeychainModuleBuilder; import com.templewallet.newarchitecture.MainApplicationReactNativeHost; import java.lang.reflect.InvocationTargetException; import java.util.List; @@ -28,6 +30,11 @@ public boolean getUseDeveloperSupport() { protected List getPackages() { @SuppressWarnings("UnnecessaryLocalVariable") List packages = new PackageList(this).getPackages(); + packages.add( + new KeychainPackage( + new KeychainModuleBuilder().withoutWarmUp() + ) + ); // Packages that cannot be autolinked yet can be added manually here, for example: // packages.add(new MyReactNativePackage()); return packages; diff --git a/android/settings.gradle b/android/settings.gradle index 77c00b895..c25f09fe6 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -1,6 +1,10 @@ rootProject.name = 'TempleWallet' apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings) include ':app' + +include ':react-native-keychain' +project(':react-native-keychain').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-keychain/android') + includeBuild('../node_modules/react-native-gradle-plugin') if (settings.hasProperty("newArchEnabled") && settings.newArchEnabled == "true") { diff --git a/react-native.config.js b/react-native.config.js index 4ab64639f..95a6cb84f 100644 --- a/react-native.config.js +++ b/react-native.config.js @@ -1,3 +1,10 @@ module.exports = { - assets: ['./src/assets/fonts/'] + assets: ['./src/assets/fonts/'], + dependencies: { + 'react-native-keychain': { + platforms: { + android: null + } + } + } }; diff --git a/src/app/app.tsx b/src/app/app.tsx index bd076f4b2..1824a0699 100644 --- a/src/app/app.tsx +++ b/src/app/app.tsx @@ -1,7 +1,6 @@ import { AnalyticsProvider } from '@segment/analytics-react-native'; import React from 'react'; import { LogBox } from 'react-native'; -import { hide } from 'react-native-bootsplash'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { SafeAreaProvider } from 'react-native-safe-area-context'; import { enableScreens } from 'react-native-screens'; @@ -9,9 +8,8 @@ import { Provider } from 'react-redux'; import { PersistGate } from 'redux-persist/integration/react'; import { BiometryAvailabilityProvider } from '../biometry/biometry-availability.provider'; -import { HIDE_SPLASH_SCREEN_TIMEOUT } from '../config/animation'; import { HideBalanceProvider } from '../hooks/hide-balance/hide-balance.provider'; -import { useDelayedEffect } from '../hooks/use-delayed-effect.hook'; +import { HideBootsplashProvider } from '../hooks/use-hide-bootsplash'; import { RootStackScreen } from '../navigator/root-stack'; import { AppLockContextProvider } from '../shelter/app-lock/app-lock'; import { persistor, store } from '../store/store'; @@ -24,27 +22,25 @@ initSentry(); enableScreens(); LogBox.ignoreAllLogs(); -export const App = () => { - useDelayedEffect(HIDE_SPLASH_SCREEN_TIMEOUT, () => void hide({ fade: true }), []); - - return ( - - - - - - - - +export const App = () => ( + + + + + + + + + - - - - - - - - - - ); -}; + + + + + + + + + + +); diff --git a/src/hooks/use-delayed-effect.hook.ts b/src/hooks/use-delayed-effect.hook.ts deleted file mode 100644 index 6cebc1bce..000000000 --- a/src/hooks/use-delayed-effect.hook.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { DependencyList, EffectCallback, useEffect } from 'react'; - -export const useDelayedEffect = (ms: number, effect: EffectCallback, deps?: DependencyList) => { - useEffect(() => { - const timeoutId = setTimeout(effect, ms); - - return () => clearTimeout(timeoutId); - }, deps); -}; diff --git a/src/hooks/use-hide-bootsplash.tsx b/src/hooks/use-hide-bootsplash.tsx new file mode 100644 index 000000000..e258f8494 --- /dev/null +++ b/src/hooks/use-hide-bootsplash.tsx @@ -0,0 +1,23 @@ +import React, { createContext, FC, useContext, useEffect, useState } from 'react'; +import { hide } from 'react-native-bootsplash'; + +import { HIDE_SPLASH_SCREEN_TIMEOUT } from '../config/animation'; + +const context = createContext(true); + +const Provider = context.Provider; + +export const HideBootsplashProvider: FC = ({ children }) => { + const [atBootsplash, setAtBootsplash] = useState(true); + + useEffect(() => { + (async () => { + await hide({ fade: true }); + setTimeout(() => void setAtBootsplash(false), HIDE_SPLASH_SCREEN_TIMEOUT); + })(); + }, []); + + return {children}; +}; + +export const useAtBootsplash = () => useContext(context); diff --git a/src/screens/enter-password/enter-password.tsx b/src/screens/enter-password/enter-password.tsx index faa0f8192..1a5c8dafc 100644 --- a/src/screens/enter-password/enter-password.tsx +++ b/src/screens/enter-password/enter-password.tsx @@ -1,5 +1,5 @@ import { Formik } from 'formik'; -import React from 'react'; +import React, { useEffect } from 'react'; import { Text, View } from 'react-native'; import { useBiometryAvailability } from '../../biometry/use-biometry-availability.hook'; @@ -13,10 +13,9 @@ import { InsetSubstitute } from '../../components/inset-substitute/inset-substit import { Label } from '../../components/label/label'; import { Quote } from '../../components/quote/quote'; import { ScreenContainer } from '../../components/screen-container/screen-container'; -import { HIDE_SPLASH_SCREEN_TIMEOUT } from '../../config/animation'; import { MAX_PASSWORD_ATTEMPTS } from '../../config/security'; import { FormPasswordInput } from '../../form/form-password-input'; -import { useDelayedEffect } from '../../hooks/use-delayed-effect.hook'; +import { useAtBootsplash } from '../../hooks/use-hide-bootsplash'; import { usePasswordLock } from '../../hooks/use-password-lock.hook'; import { useResetDataHandler } from '../../hooks/use-reset-data-handler.hook'; import { OverlayEnum } from '../../navigator/enums/overlay.enum'; @@ -36,6 +35,8 @@ import { useEnterPasswordStyles } from './enter-password.styles'; export const EnterPassword = () => { const styles = useEnterPasswordStyles(); + const atBootsplash = useAtBootsplash(); + const { unlock, unlockWithBiometry } = useAppLock(); const { biometryType } = useBiometryAvailability(); @@ -51,9 +52,10 @@ export const EnterPassword = () => { usePageAnalytic(OverlayEnum.EnterPassword); - useDelayedEffect(HIDE_SPLASH_SCREEN_TIMEOUT, () => void (isBiometryAvailable && unlockWithBiometry()), [ - isBiometryAvailable - ]); + useEffect( + () => void (!atBootsplash && isBiometryAvailable && unlockWithBiometry()), + [isBiometryAvailable, atBootsplash] + ); return ( diff --git a/src/shelter/app-lock/app-lock.tsx b/src/shelter/app-lock/app-lock.tsx index 73c0adb14..a966a9831 100644 --- a/src/shelter/app-lock/app-lock.tsx +++ b/src/shelter/app-lock/app-lock.tsx @@ -46,7 +46,7 @@ export const AppLockContextProvider: FC = ({ children }) => { return undefined; }) - .catch(() => undefined); + .catch(error => void console.error(error)); isDefined(password) && unlock(password); }, [unlock]); diff --git a/src/shelter/shelter.ts b/src/shelter/shelter.ts index 05f8ad8c7..546e5b607 100644 --- a/src/shelter/shelter.ts +++ b/src/shelter/shelter.ts @@ -185,7 +185,7 @@ export class Shelter { static disableBiometryPassword$ = () => from(Keychain.resetGenericPassword(getKeychainOptions(PASSWORD_STORAGE_KEY))); - static getBiometryPassword = () => Keychain.getGenericPassword(biometryKeychainOptions); + static getBiometryPassword = async () => Keychain.getGenericPassword(biometryKeychainOptions); static isPasswordCorrect$ = (password: string) => hashPassword$(password).pipe(map(passwordHash => passwordHash === Shelter._passwordHash$.getValue()));