diff --git a/src/assets/BackedUpCloud.png b/src/assets/BackedUpCloud.png new file mode 100644 index 00000000000..10fb59ccd3a Binary files /dev/null and b/src/assets/BackedUpCloud.png differ diff --git a/src/assets/BackedUpCloud@2x.png b/src/assets/BackedUpCloud@2x.png new file mode 100644 index 00000000000..559629b7903 Binary files /dev/null and b/src/assets/BackedUpCloud@2x.png differ diff --git a/src/assets/BackedUpCloud@3x.png b/src/assets/BackedUpCloud@3x.png new file mode 100644 index 00000000000..f163e91ca3a Binary files /dev/null and b/src/assets/BackedUpCloud@3x.png differ diff --git a/src/assets/BackupWarning.png b/src/assets/BackupWarning.png new file mode 100644 index 00000000000..26b2efcec4d Binary files /dev/null and b/src/assets/BackupWarning.png differ diff --git a/src/assets/BackupWarning@2x.png b/src/assets/BackupWarning@2x.png new file mode 100644 index 00000000000..5323a789229 Binary files /dev/null and b/src/assets/BackupWarning@2x.png differ diff --git a/src/assets/BackupWarning@3x.png b/src/assets/BackupWarning@3x.png new file mode 100644 index 00000000000..c0653184df5 Binary files /dev/null and b/src/assets/BackupWarning@3x.png differ diff --git a/src/assets/CloudBackupWarning.png b/src/assets/CloudBackupWarning.png new file mode 100644 index 00000000000..049461c7af3 Binary files /dev/null and b/src/assets/CloudBackupWarning.png differ diff --git a/src/assets/CloudBackupWarning@2x.png b/src/assets/CloudBackupWarning@2x.png new file mode 100644 index 00000000000..f1894f9f5d7 Binary files /dev/null and b/src/assets/CloudBackupWarning@2x.png differ diff --git a/src/assets/CloudBackupWarning@3x.png b/src/assets/CloudBackupWarning@3x.png new file mode 100644 index 00000000000..4133d3d62e0 Binary files /dev/null and b/src/assets/CloudBackupWarning@3x.png differ diff --git a/src/assets/CreateNewWallet.png b/src/assets/CreateNewWallet.png new file mode 100644 index 00000000000..ccc74c01d1d Binary files /dev/null and b/src/assets/CreateNewWallet.png differ diff --git a/src/assets/CreateNewWallet@2x.png b/src/assets/CreateNewWallet@2x.png new file mode 100644 index 00000000000..387f14bfa46 Binary files /dev/null and b/src/assets/CreateNewWallet@2x.png differ diff --git a/src/assets/CreateNewWallet@3x.png b/src/assets/CreateNewWallet@3x.png new file mode 100644 index 00000000000..f40a606b06b Binary files /dev/null and b/src/assets/CreateNewWallet@3x.png differ diff --git a/src/assets/ImportSecretPhraseOrPrivateKey.png b/src/assets/ImportSecretPhraseOrPrivateKey.png new file mode 100644 index 00000000000..44c0a0ea59e Binary files /dev/null and b/src/assets/ImportSecretPhraseOrPrivateKey.png differ diff --git a/src/assets/ImportSecretPhraseOrPrivateKey@2x.png b/src/assets/ImportSecretPhraseOrPrivateKey@2x.png new file mode 100644 index 00000000000..475ee5bba99 Binary files /dev/null and b/src/assets/ImportSecretPhraseOrPrivateKey@2x.png differ diff --git a/src/assets/ImportSecretPhraseOrPrivateKey@3x.png b/src/assets/ImportSecretPhraseOrPrivateKey@3x.png new file mode 100644 index 00000000000..60ac917f099 Binary files /dev/null and b/src/assets/ImportSecretPhraseOrPrivateKey@3x.png differ diff --git a/src/assets/ManuallyBackedUp.png b/src/assets/ManuallyBackedUp.png new file mode 100644 index 00000000000..5345ded5449 Binary files /dev/null and b/src/assets/ManuallyBackedUp.png differ diff --git a/src/assets/ManuallyBackedUp@2x.png b/src/assets/ManuallyBackedUp@2x.png new file mode 100644 index 00000000000..c2a53439487 Binary files /dev/null and b/src/assets/ManuallyBackedUp@2x.png differ diff --git a/src/assets/ManuallyBackedUp@3x.png b/src/assets/ManuallyBackedUp@3x.png new file mode 100644 index 00000000000..6b34943018a Binary files /dev/null and b/src/assets/ManuallyBackedUp@3x.png differ diff --git a/src/assets/PairHardwareWallet.png b/src/assets/PairHardwareWallet.png new file mode 100644 index 00000000000..52110f7bcaf Binary files /dev/null and b/src/assets/PairHardwareWallet.png differ diff --git a/src/assets/PairHardwareWallet@2x.png b/src/assets/PairHardwareWallet@2x.png new file mode 100644 index 00000000000..57479c7e16e Binary files /dev/null and b/src/assets/PairHardwareWallet@2x.png differ diff --git a/src/assets/PairHardwareWallet@3x.png b/src/assets/PairHardwareWallet@3x.png new file mode 100644 index 00000000000..3aa3176162b Binary files /dev/null and b/src/assets/PairHardwareWallet@3x.png differ diff --git a/src/assets/WalletsAndBackup.png b/src/assets/WalletsAndBackup.png new file mode 100644 index 00000000000..ab941378faf Binary files /dev/null and b/src/assets/WalletsAndBackup.png differ diff --git a/src/assets/WalletsAndBackup@2x.png b/src/assets/WalletsAndBackup@2x.png new file mode 100644 index 00000000000..ddb2902f597 Binary files /dev/null and b/src/assets/WalletsAndBackup@2x.png differ diff --git a/src/assets/WalletsAndBackup@3x.png b/src/assets/WalletsAndBackup@3x.png new file mode 100644 index 00000000000..a0c0a72c63f Binary files /dev/null and b/src/assets/WalletsAndBackup@3x.png differ diff --git a/src/assets/watchWallet.png b/src/assets/watchWallet.png new file mode 100644 index 00000000000..11660b91e67 Binary files /dev/null and b/src/assets/watchWallet.png differ diff --git a/src/assets/watchWallet@2x.png b/src/assets/watchWallet@2x.png new file mode 100644 index 00000000000..f240e0340d6 Binary files /dev/null and b/src/assets/watchWallet@2x.png differ diff --git a/src/assets/watchWallet@3x.png b/src/assets/watchWallet@3x.png new file mode 100644 index 00000000000..1a0dba1b90f Binary files /dev/null and b/src/assets/watchWallet@3x.png differ diff --git a/src/components/PromoSheet.tsx b/src/components/PromoSheet.tsx index 454a275f328..8c4a93a7c22 100644 --- a/src/components/PromoSheet.tsx +++ b/src/components/PromoSheet.tsx @@ -88,7 +88,6 @@ export function PromoSheet({ const contentHeight = deviceHeight - (!isSmallPhone ? sharedCoolModalTopOffset : 0); return ( - // @ts-ignore { return ( }> - {items.map((item: AddWalletItem) => ( - + {items.map((item: AddWalletItem, index: number) => ( + ))} ); diff --git a/src/components/add-wallet/AddWalletRow.tsx b/src/components/add-wallet/AddWalletRow.tsx index 81d476899cb..307453d8d71 100644 --- a/src/components/add-wallet/AddWalletRow.tsx +++ b/src/components/add-wallet/AddWalletRow.tsx @@ -1,28 +1,14 @@ +import React from 'react'; +import { ImageSourcePropType } from 'react-native'; + import { Box, Stack, Text, useForegroundColor } from '@/design-system'; -import { IS_ANDROID, IS_TEST } from '@/env'; import styled from '@/styled-thing'; -import { useTheme } from '@/theme'; -import React from 'react'; -import { GradientText, Text as RNText } from '../text'; import { Icon } from '../icons'; -import ConditionalWrap from 'conditional-wrap'; import { deviceUtils } from '@/utils'; import { ButtonPressAnimation } from '../animations'; - -const RainbowText = styled(GradientText).attrs(({ theme: { colors } }: any) => ({ - angle: false, - colors: colors.gradients.rainbow, - end: { x: 0, y: 0.5 }, - start: { x: 1, y: 0.5 }, - steps: [0, 0.774321, 1], -}))({}); - -const TextIcon = styled(RNText).attrs({ - size: 29, - weight: 'medium', -})({ - marginVertical: IS_ANDROID ? -10 : 0, -}); +import { ImgixImage } from '../images'; +import { Source } from 'react-native-fast-image'; +import { TextColor } from '@/design-system/color/palettes'; const CaretIcon = styled(Icon).attrs(({ color }: { color: string }) => ({ name: 'caret', @@ -34,7 +20,8 @@ const CaretIcon = styled(Icon).attrs(({ color }: { color: string }) => ({ export type AddWalletItem = { title: string; description: string; - icon: string; + descriptionColor?: TextColor; + icon: string | ImageSourcePropType; iconColor?: string; testID?: string; onPress: () => void; @@ -46,19 +33,17 @@ type AddWalletRowProps = { }; export const AddWalletRow = ({ content, totalHorizontalInset }: AddWalletRowProps) => { - const { colors } = useTheme(); const labelQuaternary = useForegroundColor('labelQuaternary'); - const { title, description, icon, iconColor, testID, onPress } = content; + const { title, description, icon, descriptionColor, testID, onPress } = content; // device width - 2 * total horizontal inset from device boundaries - caret column width (30) const contentWidth = deviceUtils.dimensions.width - 2 * totalHorizontalInset - 30; - const shouldUseRainbowText = !iconColor && !(IS_ANDROID && IS_TEST); + const size = 64; return ( - - {children}} - > - {icon} - - ( - - - {children} - - - )} - > - - {title} - - - + + + + {title} + + {description} diff --git a/src/components/asset-list/RecyclerAssetList2/index.tsx b/src/components/asset-list/RecyclerAssetList2/index.tsx index cc27e52e3b7..69fb257fb5c 100644 --- a/src/components/asset-list/RecyclerAssetList2/index.tsx +++ b/src/components/asset-list/RecyclerAssetList2/index.tsx @@ -193,23 +193,6 @@ function NavbarOverlay({ accentColor, position }: { accentColor?: string; positi [handlePressConnectedApps, handlePressQRCode, handlePressSettings] ); - const handlePressMenuItemAndroid = React.useCallback( - (buttonIndex: number) => { - switch (buttonIndex) { - case 0: - handlePressSettings(); - break; - case 1: - handlePressQRCode(); - break; - case 2: - handlePressConnectedApps(); - break; - } - }, - [handlePressConnectedApps, handlePressQRCode, handlePressSettings] - ); - return ( item?.actionTitle)} cancelButtonIndex={menuConfig.menuItems.length - 1} - onPressActionSheet={handlePressMenuItemAndroid} + onPressActionSheet={(buttonIndex: number) => { + handlePressMenuItem({ nativeEvent: { actionKey: menuConfig.menuItems[buttonIndex]?.actionKey } }); + }} > diff --git a/src/components/backup/AddWalletToCloudBackupStep.tsx b/src/components/backup/AddWalletToCloudBackupStep.tsx new file mode 100644 index 00000000000..62f92a99e2f --- /dev/null +++ b/src/components/backup/AddWalletToCloudBackupStep.tsx @@ -0,0 +1,123 @@ +import React, { useCallback } from 'react'; +import { Bleed, Box, Inline, Inset, Separator, Stack, Text } from '@/design-system'; +import * as lang from '@/languages'; +import { ImgixImage } from '../images'; +import WalletsAndBackupIcon from '@/assets/WalletsAndBackup.png'; +import { Source } from 'react-native-fast-image'; +import { cloudPlatform } from '@/utils/platform'; +import { ButtonPressAnimation } from '../animations'; +import Routes from '@/navigation/routesNames'; +import { useNavigation } from '@/navigation'; +import { useWallets } from '@/hooks'; +import { WalletCountPerType, useVisibleWallets } from '@/screens/SettingsSheet/useVisibleWallets'; +import { format } from 'date-fns'; +import { useCreateBackup } from './useCreateBackup'; +import { login } from '@/handlers/cloudBackup'; + +const imageSize = 72; + +export default function AddWalletToCloudBackupStep() { + const { goBack } = useNavigation(); + const { wallets, selectedWallet } = useWallets(); + + const walletTypeCount: WalletCountPerType = { + phrase: 0, + privateKey: 0, + }; + + const { lastBackupDate } = useVisibleWallets({ wallets, walletTypeCount }); + + const { onSubmit } = useCreateBackup({ + walletId: selectedWallet.id, + navigateToRoute: { + route: Routes.SETTINGS_SHEET, + params: { + screen: Routes.SETTINGS_SECTION_BACKUP, + }, + }, + }); + + const potentiallyLoginAndSubmit = useCallback(async () => { + await login(); + return onSubmit({}); + }, [onSubmit]); + + const onMaybeLater = useCallback(() => goBack(), [goBack]); + + return ( + + + + + + {lang.t(lang.l.back_up.cloud.add_wallet_to_cloud_backups)} + + + + + + + + + potentiallyLoginAndSubmit().then(success => success && goBack())}> + + + + + 􀎽{' '} + {lang.t(lang.l.back_up.cloud.back_to_cloud_platform_now, { + cloudPlatform, + })} + + + + + + + + + + + + + + + + {lang.t(lang.l.back_up.cloud.mayber_later)} + + + + + + + + + + + {lastBackupDate && ( + + + + + {lang.t(lang.l.back_up.cloud.latest_backup, { + date: format(lastBackupDate, "M/d/yy 'at' h:mm a"), + })} + + + + + )} + + ); +} diff --git a/src/components/backup/BackupChooseProviderStep.tsx b/src/components/backup/BackupChooseProviderStep.tsx new file mode 100644 index 00000000000..da9520335df --- /dev/null +++ b/src/components/backup/BackupChooseProviderStep.tsx @@ -0,0 +1,216 @@ +import React from 'react'; +import { useCreateBackup } from '@/components/backup/useCreateBackup'; +import { Bleed, Box, Inline, Inset, Separator, Stack, Text } from '@/design-system'; +import * as lang from '@/languages'; +import { ImgixImage } from '../images'; +import WalletsAndBackupIcon from '@/assets/WalletsAndBackup.png'; +import ManuallyBackedUpIcon from '@/assets/ManuallyBackedUp.png'; +import Caret from '@/assets/family-dropdown-arrow.png'; +import { Source } from 'react-native-fast-image'; +import { cloudPlatform } from '@/utils/platform'; +import { useTheme } from '@/theme'; +import { ButtonPressAnimation } from '../animations'; +import { useNavigation } from '@/navigation'; +import Routes from '@/navigation/routesNames'; +import { SETTINGS_BACKUP_ROUTES } from '@/screens/SettingsSheet/components/Backups/routes'; +import { useWallets } from '@/hooks'; +import walletTypes from '@/helpers/walletTypes'; +import walletBackupTypes from '@/helpers/walletBackupTypes'; +import { IS_ANDROID } from '@/env'; +import { GoogleDriveUserData, getGoogleAccountUserData, isCloudBackupAvailable, login } from '@/handlers/cloudBackup'; +import { WrappedAlert as Alert } from '@/helpers/alert'; +import { RainbowError, logger } from '@/logger'; +import { Linking } from 'react-native'; + +const imageSize = 72; + +export default function BackupSheetSectionNoProvider() { + const { colors } = useTheme(); + const { navigate, goBack } = useNavigation(); + const { selectedWallet } = useWallets(); + + const { onSubmit, loading } = useCreateBackup({ + walletId: selectedWallet.id, + navigateToRoute: { + route: Routes.SETTINGS_SHEET, + params: { + screen: Routes.SETTINGS_SECTION_BACKUP, + }, + }, + }); + + const onCloudBackup = async () => { + if (loading !== 'none') { + return; + } + // NOTE: On Android we need to make sure the user is signed into a Google account before trying to backup + // otherwise we'll fake backup and it's confusing... + if (IS_ANDROID) { + try { + await login(); + getGoogleAccountUserData().then((accountDetails: GoogleDriveUserData | undefined) => { + if (!accountDetails) { + Alert.alert(lang.t(lang.l.back_up.errors.no_account_found)); + return; + } + }); + } catch (e) { + Alert.alert(lang.t(lang.l.back_up.errors.no_account_found)); + logger.error(e as RainbowError); + } + } else { + const isAvailable = await isCloudBackupAvailable(); + if (!isAvailable) { + Alert.alert( + lang.t(lang.l.modal.back_up.alerts.cloud_not_enabled.label), + lang.t(lang.l.modal.back_up.alerts.cloud_not_enabled.description), + [ + { + onPress: () => { + Linking.openURL('https://support.apple.com/en-us/HT204025'); + }, + text: lang.t(lang.l.modal.back_up.alerts.cloud_not_enabled.show_me), + }, + { + style: 'cancel', + text: lang.t(lang.l.modal.back_up.alerts.cloud_not_enabled.no_thanks), + }, + ] + ); + return; + } + } + + onSubmit({}); + }; + + const onManualBackup = async () => { + const title = + selectedWallet?.imported && selectedWallet.type === walletTypes.privateKey ? selectedWallet.addresses[0].label : selectedWallet.name; + + goBack(); + navigate(Routes.SETTINGS_SHEET, { + screen: SETTINGS_BACKUP_ROUTES.SECRET_WARNING, + params: { + isBackingUp: true, + title, + backupType: walletBackupTypes.manual, + walletId: selectedWallet.id, + }, + }); + }; + + return ( + + + + {lang.t(lang.l.back_up.cloud.how_would_you_like_to_backup)} + + + + + + + + {/* replace this with BackUpMenuButton */} + + + + + + + + + + + {lang.t(lang.l.back_up.cloud.cloud_backup)} + + + + {lang.t(lang.l.back_up.cloud.recommended_for_beginners)} + {' '} + {lang.t(lang.l.back_up.cloud.choose_backup_cloud_description, { + cloudPlatform, + })} + + + + + + + + + + + + + + + + + + + + + + + + + + + + {lang.t(lang.l.back_up.cloud.manual_backup)} + + + {lang.t(lang.l.back_up.cloud.choose_backup_manual_description)} + + + + + + + + + + + + + + ); +} diff --git a/src/components/backup/BackupCloudStep.js b/src/components/backup/BackupCloudStep.js deleted file mode 100644 index 1b5a94c3145..00000000000 --- a/src/components/backup/BackupCloudStep.js +++ /dev/null @@ -1,307 +0,0 @@ -import { useRoute } from '@react-navigation/native'; -import { captureMessage } from '@sentry/react-native'; -import * as lang from '@/languages'; -import React, { useCallback, useEffect, useRef, useState } from 'react'; -import { InteractionManager, Keyboard } from 'react-native'; -import { passwordStrength } from 'check-password-strength'; -import { isSamsungGalaxy } from '../../helpers/samsung'; -import { saveBackupPassword } from '../../model/backup'; -import { cloudPlatform } from '../../utils/platform'; -import { DelayedAlert } from '../alerts'; -import { PasswordField } from '../fields'; -import { Centered, ColumnWithMargins } from '../layout'; -import { GradientText, Text } from '../text'; -import BackupSheetKeyboardLayout from './BackupSheetKeyboardLayout'; -import { analytics } from '@/analytics'; -import { cloudBackupPasswordMinLength, isCloudBackupPasswordValid } from '@/handlers/cloudBackup'; -import showWalletErrorAlert from '@/helpers/support'; -import { useDimensions, useMagicAutofocus, useRouteExistsInNavigationState, useWalletCloudBackup, useWallets } from '@/hooks'; -import { useNavigation } from '@/navigation'; -import Routes from '@/navigation/routesNames'; -import styled from '@/styled-thing'; -import { padding } from '@/styles'; -import logger from '@/utils/logger'; - -const DescriptionText = styled(Text).attrs(({ isTinyPhone, theme: { colors } }) => ({ - align: 'center', - color: colors.alpha(colors.blueGreyDark, 0.5), - lineHeight: 'looser', - size: isTinyPhone ? 'lmedium' : 'large', -}))({}); - -const ImportantText = styled(DescriptionText).attrs(({ theme: { colors } }) => ({ - color: colors.alpha(colors.blueGreyDark, 0.6), - weight: 'medium', -}))({}); - -const Masthead = styled(Centered).attrs({ - direction: 'column', -})(({ isTallPhone, isTinyPhone }) => ({ - ...padding.object(isTinyPhone ? 0 : 9, isTinyPhone ? 10 : 50, isTallPhone ? 39 : 19), - flexShrink: 0, -})); - -const MastheadIcon = styled(GradientText).attrs(({ theme: { colors } }) => ({ - align: 'center', - angle: false, - colors: colors.gradients.rainbow, - end: { x: 0, y: 0.5 }, - size: 43, - start: { x: 1, y: 0.5 }, - steps: [0, 0.774321, 1], - weight: 'medium', -}))({}); - -const Title = styled(Text).attrs(({ isTinyPhone }) => ({ - size: isTinyPhone ? 'large' : 'big', - weight: 'bold', -}))(({ isTinyPhone }) => ({ - ...(isTinyPhone ? padding.object(0) : padding.object(15, 0, 12)), -})); - -const samsungGalaxy = (android && isSamsungGalaxy()) || false; - -export default function BackupCloudStep() { - const { isTallPhone, isTinyPhone } = useDimensions(); - const currentlyFocusedInput = useRef(); - const { goBack } = useNavigation(); - const { params } = useRoute(); - const walletCloudBackup = useWalletCloudBackup(); - const { selectedWallet, isDamaged } = useWallets(); - const [validPassword, setValidPassword] = useState(false); - const [isKeyboardOpen, setIsKeyboardOpen] = useState(false); - const [passwordFocused, setPasswordFocused] = useState(true); - const [password, setPassword] = useState(''); - const [confirmPassword, setConfirmPassword] = useState(''); - const { navigate } = useNavigation(); - const keyboardShowListener = useRef(null); - const keyboardHideListener = useRef(null); - - useEffect(() => { - const keyboardDidShow = () => { - setIsKeyboardOpen(true); - }; - - const keyboardDidHide = () => { - setIsKeyboardOpen(false); - }; - keyboardShowListener.current = Keyboard.addListener('keyboardDidShow', keyboardDidShow); - keyboardHideListener.current = Keyboard.addListener('keyboardDidHide', keyboardDidHide); - if (isDamaged) { - showWalletErrorAlert(); - captureMessage('Damaged wallet preventing cloud backup'); - goBack(); - } - return () => { - keyboardShowListener.current?.remove(); - keyboardHideListener.current?.remove(); - }; - }, [goBack, isDamaged]); - - const isSettingsRoute = useRouteExistsInNavigationState(Routes.SETTINGS_SHEET); - - const walletId = params?.walletId || selectedWallet.id; - - const [label, setLabel] = useState( - !validPassword - ? `􀙶 ${lang.t(lang.l.back_up.confirm_password.add_to_cloud_platform, { - cloudPlatformName: cloudPlatform, - })}` - : `􀎽 ${lang.t(lang.l.back_up.confirm_password.confirm_backup)}` - ); - const passwordRef = useRef(); - const confirmPasswordRef = useRef(); - - useEffect(() => { - setTimeout(() => { - passwordRef.current?.focus(); - }, 1); - analytics.track('Choose Password Step', { - category: 'backup', - label: cloudPlatform, - }); - }, []); - - const { handleFocus } = useMagicAutofocus(passwordRef); - - const onPasswordFocus = useCallback( - target => { - handleFocus(target); - setPasswordFocused(true); - currentlyFocusedInput.current = passwordRef.current; - }, - [handleFocus] - ); - - const onConfirmPasswordFocus = useCallback( - target => { - handleFocus(target); - currentlyFocusedInput.current = confirmPasswordRef.current; - }, - [handleFocus] - ); - - const onPasswordBlur = useCallback(() => { - setPasswordFocused(false); - }, []); - - const onPasswordSubmit = useCallback(() => { - confirmPasswordRef.current?.focus(); - }, []); - - useEffect(() => { - let passwordIsValid = false; - if (password === confirmPassword && isCloudBackupPasswordValid(password)) { - passwordIsValid = true; - } - - let newLabel = ''; - if (passwordIsValid) { - newLabel = `􀎽 ${lang.t(lang.l.back_up.cloud.password.confirm_backup)}`; - } else if (password.length < cloudBackupPasswordMinLength) { - newLabel = lang.t('back_up.cloud.password.minimum_characters', { - minimumLength: cloudBackupPasswordMinLength, - }); - } else if ( - // TODO FIXME This branch of the if/else will never execute - // eslint-disable-next-line no-dupe-else-if - password !== '' && - password.length < cloudBackupPasswordMinLength && - !passwordRef.current?.isFocused() - ) { - newLabel = lang.t(lang.l.back_up.cloud.password.use_a_longer_password); - } else if ( - isCloudBackupPasswordValid(password) && - isCloudBackupPasswordValid(confirmPassword) && - confirmPassword.length >= password.length && - password !== confirmPassword - ) { - newLabel = lang.t(lang.l.back_up.cloud.password.passwords_dont_match); - } else if (password.length >= cloudBackupPasswordMinLength && !passwordFocused) { - newLabel = lang.t(lang.l.back_up.cloud.password.confirm_password); - } else if (password.length >= cloudBackupPasswordMinLength && passwordFocused) { - const passInfo = passwordStrength(password); - switch (passInfo.id) { - case 0: - case 1: - newLabel = `💩 ${lang.t(lang.l.back_up.cloud.password.strength.level1)}`; - break; - case 2: - newLabel = `👌 ${lang.t(lang.l.back_up.cloud.password.strength.level2)}`; - break; - case 3: - newLabel = `💪 ${lang.t(lang.l.back_up.cloud.password.strength.level3)}`; - break; - case 4: - newLabel = `🏰️ ${lang.t(lang.l.back_up.cloud.password.strength.level4)}`; - break; - default: - } - } - - setValidPassword(passwordIsValid); - setLabel(newLabel); - }, [confirmPassword, password, passwordFocused]); - - const onPasswordChange = useCallback(({ nativeEvent: { text: inputText } }) => { - setPassword(inputText); - }, []); - - const onConfirmPasswordChange = useCallback(({ nativeEvent: { text: inputText } }) => { - setConfirmPassword(inputText); - }, []); - - const onError = useCallback( - msg => { - setTimeout(onPasswordSubmit, 1000); - DelayedAlert({ title: msg }, 500); - }, - [onPasswordSubmit] - ); - - const onSuccess = useCallback(async () => { - logger.log('BackupCloudStep:: saving backup password'); - await saveBackupPassword(password); - if (!isSettingsRoute) { - DelayedAlert({ title: lang.t(lang.l.cloud.backup_success) }, 1000); - } - // This means the user set a new password - // and it was the first wallet backed up - analytics.track('Backup Complete', { - category: 'backup', - label: cloudPlatform, - }); - goBack(); - }, [goBack, isSettingsRoute, password]); - - const onConfirmBackup = useCallback(async () => { - analytics.track('Tapped "Confirm Backup"'); - - await walletCloudBackup({ - onError, - onSuccess, - password, - walletId, - }); - }, [onError, onSuccess, password, walletCloudBackup, walletId]); - - const showExplainerConfirmation = useCallback(async () => { - android && Keyboard.dismiss(); - navigate(Routes.EXPLAIN_SHEET, { - onClose: () => { - InteractionManager.runAfterInteractions(() => { - setTimeout(() => { - onConfirmBackup(); - }, 300); - }); - }, - type: 'backup', - }); - }, [navigate, onConfirmBackup]); - - const onConfirmPasswordSubmit = useCallback(() => { - validPassword && showExplainerConfirmation(); - }, [showExplainerConfirmation, validPassword]); - - return ( - - - {(isTinyPhone || samsungGalaxy) && isKeyboardOpen ? null : 􀌍} - {lang.t(lang.l.back_up.cloud.password.choose_a_password)} - - {lang.t(lang.l.back_up.cloud.password.a_password_youll_remember)} -   - {lang.t(lang.l.back_up.cloud.password.it_cant_be_recovered)} - - - - - = password.length && confirmPassword !== password - } - isValid={validPassword} - onChange={onConfirmPasswordChange} - onFocus={onConfirmPasswordFocus} - onSubmitEditing={onConfirmPasswordSubmit} - password={confirmPassword} - placeholder={lang.t(lang.l.back_up.cloud.password.confirm_placeholder)} - ref={confirmPasswordRef} - /> - - - ); -} diff --git a/src/components/backup/BackupCloudStep.tsx b/src/components/backup/BackupCloudStep.tsx new file mode 100644 index 00000000000..e839d9323c0 --- /dev/null +++ b/src/components/backup/BackupCloudStep.tsx @@ -0,0 +1,256 @@ +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import { RouteProp, useRoute } from '@react-navigation/native'; +import { Source } from 'react-native-fast-image'; +import { KeyboardArea } from 'react-native-keyboard-area'; + +import * as lang from '@/languages'; +import { sharedCoolModalTopOffset } from '@/navigation/config'; +import { cloudPlatform } from '@/utils/platform'; +import { PasswordField } from '@/components/fields'; +import { Text } from '@/components/text'; +import WalletAndBackup from '@/assets/WalletsAndBackup.png'; +import { analytics } from '@/analytics'; +import { cloudBackupPasswordMinLength, isCloudBackupPasswordValid } from '@/handlers/cloudBackup'; +import { useDimensions, useMagicAutofocus, useWallets } from '@/hooks'; +import styled from '@/styled-thing'; +import { padding } from '@/styles'; +import { Box, Inset, Stack } from '@/design-system'; +import { ImgixImage } from '../images'; +import { IS_ANDROID } from '@/env'; +import { RainbowButton } from '../buttons'; +import RainbowButtonTypes from '../buttons/rainbow-button/RainbowButtonTypes'; +import { usePasswordValidation } from './usePasswordValidation'; +import { TextInput } from 'react-native'; +import { useTheme } from '@/theme'; +import { useNavigation } from '@/navigation'; +import Routes from '@/navigation/routesNames'; +import { SETTINGS_BACKUP_ROUTES } from '@/screens/SettingsSheet/components/Backups/routes'; +import walletTypes from '@/helpers/walletTypes'; + +type BackupCloudStepParams = { + BackupCloudStep: { + isFromWalletReadyPrompt?: boolean; + walletId?: string; + onSuccess: (password: string) => Promise; + onCancel: () => Promise; + }; +}; + +type NativeEvent = { + nativeEvent: { + text: string; + }; +}; + +export function BackupCloudStep() { + const { isDarkMode } = useTheme(); + const { goBack } = useNavigation(); + const { width: deviceWidth, height: deviceHeight } = useDimensions(); + const { params } = useRoute>(); + const [password, setPassword] = useState(''); + const [confirmPassword, setConfirmPassword] = useState(''); + + const { onSuccess, onCancel, isFromWalletReadyPrompt = false } = params; + + const { validPassword, label, labelColor } = usePasswordValidation(password, confirmPassword); + + const currentlyFocusedInput = useRef(null); + const passwordRef = useRef(null); + const confirmPasswordRef = useRef(null); + + useEffect(() => { + setTimeout(() => { + passwordRef.current?.focus(); + }, 1); + analytics.track('Choose Password Step', { + category: 'backup', + label: cloudPlatform, + }); + }, []); + + const { handleFocus } = useMagicAutofocus(passwordRef); + + const onTextInputFocus = useCallback( + (target: any, isConfirm = false) => { + const ref = isConfirm ? confirmPasswordRef.current : passwordRef.current; + handleFocus(target); + currentlyFocusedInput.current = ref; + }, + [handleFocus] + ); + + const onPasswordSubmit = useCallback(() => { + confirmPasswordRef.current?.focus(); + }, []); + + const onPasswordChange = useCallback(({ nativeEvent: { text: inputText } }: NativeEvent) => { + setPassword(inputText); + setConfirmPassword(''); + }, []); + + const onConfirmPasswordChange = useCallback(({ nativeEvent: { text: inputText } }: NativeEvent) => { + setConfirmPassword(inputText); + }, []); + + const onSuccessAndNavigateBack = useCallback( + async (password: string) => { + if (!isFromWalletReadyPrompt) { + goBack(); + } + + onSuccess(password); + }, + [goBack, isFromWalletReadyPrompt, onSuccess] + ); + + useEffect(() => { + return () => { + if (!password) { + onCancel(); + } + }; + }, []); + + return ( + + + + + + + {lang.t(lang.l.back_up.cloud.password.choose_a_password)} + + {lang.t(lang.l.back_up.cloud.password.a_password_youll_remember_part_one)} +   + {lang.t(lang.l.back_up.cloud.password.not)} +   + {lang.t(lang.l.back_up.cloud.password.a_password_youll_remember_part_two)} + + + + + onTextInputFocus(target)} + onSubmitEditing={onPasswordSubmit} + password={password} + placeholder={lang.t(lang.l.back_up.cloud.password.backup_password)} + ref={passwordRef} + returnKeyType="next" + textContentType="newPassword" + /> + {isCloudBackupPasswordValid(password) && ( + = password.length && confirmPassword !== password + } + isValid={validPassword} + onChange={onConfirmPasswordChange} + onFocus={(target: any) => onTextInputFocus(target, true)} + onSubmitEditing={() => onSuccessAndNavigateBack(password)} + password={confirmPassword} + placeholder={lang.t(lang.l.back_up.cloud.password.confirm_placeholder)} + ref={confirmPasswordRef} + /> + )} + + {label} + + + + + {validPassword && ( + onSuccessAndNavigateBack(password)} + /> + )} + + {!validPassword && ( + + + {`􀎽 ${lang.t(lang.l.back_up.cloud.back_up_to_platform, { + cloudPlatformName: cloudPlatform, + })}`} + + + )} + + {IS_ANDROID ? : null} + + + + ); +} + +export default BackupCloudStep; + +const DescriptionText = styled(Text).attrs(({ theme: { colors }, color }: any) => ({ + align: 'left', + color: color || colors.alpha(colors.blueGreyDark, 0.5), + lineHeight: 'looser', + size: 'lmedium', + weight: 'medium', +}))({}); + +const KeyboardSizeView = styled(KeyboardArea)({ + backgroundColor: ({ theme: { colors } }: any) => colors.transparent, +}); + +const ImportantText = styled(DescriptionText).attrs(({ theme: { colors } }: any) => ({ + color: colors.red, + weight: 'bold', +}))({}); + +const Masthead = styled(Box).attrs({ + direction: 'column', +})({ + ...padding.object(0, 0, 16), + gap: 8, + flexShrink: 0, +}); + +const Title = styled(Text).attrs({ + size: 'big', + weight: 'heavy', +})({ + ...padding.object(12, 0, 0), +}); + +const ButtonText = styled(Text).attrs(({ theme: { colors }, color }: any) => ({ + align: 'center', + letterSpacing: 'rounded', + color: color || colors.alpha(colors.blueGreyDark, 0.5), + size: 'larger', + weight: 'heavy', + numberOfLines: 1, +}))({}); diff --git a/src/components/backup/BackupConfirmPasswordStep.js b/src/components/backup/BackupConfirmPasswordStep.js deleted file mode 100644 index e8d9cc9a5d3..00000000000 --- a/src/components/backup/BackupConfirmPasswordStep.js +++ /dev/null @@ -1,175 +0,0 @@ -import { useRoute } from '@react-navigation/native'; -import lang from 'i18n-js'; -import React, { useCallback, useEffect, useRef, useState } from 'react'; -import { Keyboard } from 'react-native'; -import { isSamsungGalaxy } from '../../helpers/samsung'; -import { saveBackupPassword } from '../../model/backup'; -import { cloudPlatform } from '../../utils/platform'; -import { DelayedAlert } from '../alerts'; -import { PasswordField } from '../fields'; -import { Centered, Column } from '../layout'; -import { GradientText, Text } from '../text'; -import BackupSheetKeyboardLayout from './BackupSheetKeyboardLayout'; -import { analytics } from '@/analytics'; -import { cloudBackupPasswordMinLength, isCloudBackupPasswordValid } from '@/handlers/cloudBackup'; -import { useBooleanState, useDimensions, useRouteExistsInNavigationState, useWalletCloudBackup, useWallets } from '@/hooks'; -import { useNavigation } from '@/navigation'; -import Routes from '@/navigation/routesNames'; -import styled from '@/styled-thing'; -import { margin, padding } from '@/styles'; -import logger from '@/utils/logger'; - -const DescriptionText = styled(Text).attrs(({ theme: { colors } }) => ({ - align: 'center', - color: colors.alpha(colors.blueGreyDark, 0.5), - lineHeight: 'looser', - size: 'large', -}))({ - ...padding.object(0, 50), -}); - -const Masthead = styled(Centered).attrs({ - direction: 'column', -})({ - ...padding.object(24, 0, 42), - flexShrink: 0, -}); - -const MastheadIcon = styled(GradientText).attrs({ - align: 'center', - angle: false, - colors: ['#FFB114', '#FF54BB', '#00F0FF'], - end: { x: 0, y: 0 }, - letterSpacing: 'roundedTight', - size: 52, - start: { x: 1, y: 1 }, - steps: [0, 0.5, 1], - weight: 'bold', -})({}); - -const Title = styled(Text).attrs({ - size: 'big', - weight: 'bold', -})({ - ...margin.object(15, 0, 12), -}); - -const samsungGalaxy = (android && isSamsungGalaxy()) || false; - -export default function BackupConfirmPasswordStep() { - const { isTinyPhone } = useDimensions(); - const { params } = useRoute(); - const { goBack } = useNavigation(); - const walletCloudBackup = useWalletCloudBackup(); - const [isKeyboardOpen, setIsKeyboardOpen] = useState(false); - const [validPassword, setValidPassword] = useState(false); - const [passwordFocused, setPasswordFocused, setPasswordBlurred] = useBooleanState(true); - const [password, setPassword] = useState(''); - const [label, setLabel] = useState(`􀎽 ${lang.t('back_up.confirm_password.confirm_backup')}`); - const passwordRef = useRef(); - const keyboardShowListener = useRef(null); - const keyboardHideListener = useRef(null); - const { selectedWallet } = useWallets(); - const walletId = params?.walletId || selectedWallet.id; - - const isSettingsRoute = useRouteExistsInNavigationState(Routes.SETTINGS_SHEET); - - useEffect(() => { - const keyboardDidShow = () => { - setIsKeyboardOpen(true); - }; - - const keyboardDidHide = () => { - setIsKeyboardOpen(false); - }; - keyboardShowListener.current = Keyboard.addListener('keyboardDidShow', keyboardDidShow); - keyboardHideListener.current = Keyboard.addListener('keyboardDidHide', keyboardDidHide); - return () => { - keyboardShowListener.current?.remove(); - keyboardHideListener.current?.remove(); - }; - }, []); - - useEffect(() => { - analytics.track('Confirm Password Step', { - category: 'backup', - label: cloudPlatform, - }); - }, []); - - useEffect(() => { - let passwordIsValid = false; - - if (isCloudBackupPasswordValid(password)) { - passwordIsValid = true; - setLabel( - `􀑙 ${lang.t('back_up.confirm_password.add_to_cloud_platform', { - cloudPlatformName: cloudPlatform, - })}` - ); - } - setValidPassword(passwordIsValid); - }, [password, passwordFocused]); - - const onPasswordChange = useCallback(({ nativeEvent: { text: inputText } }) => { - setPassword(inputText); - }, []); - - const onError = useCallback(msg => { - passwordRef.current?.focus(); - DelayedAlert({ title: msg }, 500); - }, []); - - const onSuccess = useCallback(async () => { - logger.log('BackupConfirmPasswordStep:: saving backup password'); - await saveBackupPassword(password); - if (!isSettingsRoute) { - DelayedAlert({ title: lang.t('cloud.backup_success') }, 1000); - } - // This means the user didn't have the password saved - // and at least an other wallet already backed up - analytics.track('Backup Complete via Confirm Step', { - category: 'backup', - label: cloudPlatform, - }); - goBack(); - }, [goBack, isSettingsRoute, password]); - - const onSubmit = useCallback(async () => { - if (!validPassword) return; - analytics.track('Tapped "Restore from cloud"'); - await walletCloudBackup({ - onError, - onSuccess, - password, - walletId, - }); - }, [onError, onSuccess, password, validPassword, walletCloudBackup, walletId]); - - return ( - - - {(isTinyPhone || samsungGalaxy) && isKeyboardOpen ? null : 􀙶} - {lang.t('back_up.confirm_password.enter_backup_password')} - - {lang.t('back_up.confirm_password.enter_backup_description', { - cloudPlatformName: cloudPlatform, - })} - - - - - - - ); -} diff --git a/src/components/backup/BackupManualStep.js b/src/components/backup/BackupManualStep.js deleted file mode 100644 index 8aecf52cd31..00000000000 --- a/src/components/backup/BackupManualStep.js +++ /dev/null @@ -1,137 +0,0 @@ -import { useRoute } from '@react-navigation/native'; -import lang from 'i18n-js'; -import React, { Fragment, useCallback, useEffect, useState } from 'react'; -import { View } from 'react-native'; -import { useTheme } from '../../theme/ThemeContext'; -import { Column, Row } from '../layout'; -import { SheetActionButton } from '../sheet'; -import { Nbsp, Text } from '../text'; -import { analytics } from '@/analytics'; -import WalletTypes from '@/helpers/walletTypes'; -import { useDimensions, useWalletManualBackup, useWallets } from '@/hooks'; -import { useNavigation } from '@/navigation'; -import styled from '@/styled-thing'; -import { padding } from '@/styles'; -import { SecretDisplaySection } from '@/components/secret-display/SecretDisplaySection'; - -const Content = styled(Column).attrs({ - align: 'center', - justify: 'start', -})({ - flexGrow: 1, - flexShrink: 0, - paddingTop: ({ isTallPhone, isSmallPhone }) => (android ? 30 : isTallPhone ? 45 : isSmallPhone ? 10 : 25), -}); - -const Footer = styled(Column).attrs({ - justify: 'center', -})({ - ...padding.object(0, 15, 21), - - marginBottom: android ? 30 : 0, - width: '100%', -}); - -const Masthead = styled(Column).attrs({ - align: 'center', - justify: 'start', -})({}); - -const MastheadDescription = styled(Text).attrs(({ theme: { colors } }) => ({ - align: 'center', - color: colors.alpha(colors.blueGreyDark, 0.6), - lineHeight: 'looser', - size: 'lmedium', -}))({ - maxWidth: 291, -}); - -const MastheadIcon = styled(Text).attrs({ - align: 'center', - color: 'appleBlue', - size: 21, - weight: 'heavy', -})({}); - -const MastheadTitle = styled(Text).attrs({ - align: 'center', - size: 'larger', - weight: 'bold', -})({ - ...padding.object(8), -}); - -const MastheadTitleRow = styled(Row).attrs({ - align: 'center', - justify: 'start', -})({ - paddingTop: 18, -}); - -export default function BackupManualStep() { - const { isTallPhone, isSmallPhone } = useDimensions(); - const { goBack } = useNavigation(); - const { selectedWallet } = useWallets(); - const { onManuallyBackupWalletId } = useWalletManualBackup(); - const { params } = useRoute(); - const walletId = params?.walletId || selectedWallet.id; - const { colors } = useTheme(); - - const [type, setType] = useState(null); - const [secretLoaded, setSecretLoaded] = useState(false); - - const onComplete = useCallback(() => { - analytics.track(`Tapped "I've saved the secret"`, { - type, - }); - onManuallyBackupWalletId(walletId); - analytics.track('Backup Complete', { - category: 'backup', - label: 'manual', - }); - goBack(); - }, [goBack, onManuallyBackupWalletId, type, walletId]); - - useEffect(() => { - analytics.track('Manual Backup Step', { - category: 'backup', - label: 'manual', - }); - }, []); - - return ( - - - - 􀉆 - {lang.t('back_up.manual.label')} - - - - {type === WalletTypes.privateKey ? lang.t('back_up.manual.pkey.these_keys') : lang.t('back_up.manual.seed.these_keys')} - - - {type === WalletTypes.privateKey ? lang.t('back_up.manual.pkey.save_them') : lang.t('back_up.manual.seed.save_them')} - - - - - -
- {secretLoaded && ( - - - - )} -
-
- ); -} diff --git a/src/components/backup/BackupManuallyStep.tsx b/src/components/backup/BackupManuallyStep.tsx new file mode 100644 index 00000000000..211eda2d196 --- /dev/null +++ b/src/components/backup/BackupManuallyStep.tsx @@ -0,0 +1,98 @@ +import React, { useCallback } from 'react'; +import { Bleed, Box, Inline, Inset, Separator, Stack, Text } from '@/design-system'; +import * as lang from '@/languages'; +import { ImgixImage } from '../images'; +import ManuallyBackedUpIcon from '@/assets/ManuallyBackedUp.png'; +import { Source } from 'react-native-fast-image'; +import { ButtonPressAnimation } from '../animations'; +import { useNavigation } from '@/navigation'; +import Routes from '@/navigation/routesNames'; +import { useWallets } from '@/hooks'; +import walletTypes from '@/helpers/walletTypes'; +import { SETTINGS_BACKUP_ROUTES } from '@/screens/SettingsSheet/components/Backups/routes'; +import walletBackupTypes from '@/helpers/walletBackupTypes'; + +const imageSize = 72; + +export default function BackupManuallyStep() { + const { navigate, goBack } = useNavigation(); + const { selectedWallet } = useWallets(); + + const onManualBackup = async () => { + const title = + selectedWallet?.imported && selectedWallet.type === walletTypes.privateKey ? selectedWallet.addresses[0].label : selectedWallet.name; + + goBack(); + navigate(Routes.SETTINGS_SHEET, { + screen: SETTINGS_BACKUP_ROUTES.SECRET_WARNING, + params: { + isBackingUp: true, + title, + backupType: walletBackupTypes.manual, + walletId: selectedWallet.id, + }, + }); + }; + + const onMaybeLater = useCallback(() => goBack(), [goBack]); + + return ( + + + + + + {lang.t(lang.l.back_up.manual.backup_manually_now)} + + + + + + + + + + + + + + {lang.t(lang.l.back_up.manual.back_up_now)} + + + + + + + + + + + + + + + + {lang.t(lang.l.back_up.manual.already_backed_up)} + + + + + + + + + + + ); +} diff --git a/src/components/backup/BackupRainbowButton.tsx b/src/components/backup/BackupRainbowButton.tsx new file mode 100644 index 00000000000..614153a2767 --- /dev/null +++ b/src/components/backup/BackupRainbowButton.tsx @@ -0,0 +1,121 @@ +import MaskedView from '@react-native-masked-view/masked-view'; +import React from 'react'; +import { useTheme } from '@/theme'; +import { ButtonPressAnimation } from '@/components/animations'; +import { RowWithMargins } from '@/components/layout'; +import { Text } from '@/components/text'; +import RainbowButtonTypes from '@/components/buttons/rainbow-button/RainbowButtonTypes'; +import { useDimensions } from '@/hooks'; +import styled from '@/styled-thing'; +import { shadow } from '@/styles'; +import ShadowView from '@/react-native-shadow-stack/ShadowView'; +import BackupRainbowButtonBackground from './BackupRainbowButtonBackground'; +import { View } from 'react-native'; + +const ButtonContainer = styled(MaskedView).attrs({ + pointerEvents: 'none', +})(({ width, height }: any) => ({ + height, + width, +})); + +const ButtonContent = styled(RowWithMargins).attrs({ + align: 'center', + margin: -2.5, +})({ + alignSelf: 'center', + bottom: 2, + height: '100%', +}); + +const ButtonLabel = styled(Text).attrs(({ disabled, type, theme: { colors, isDarkMode } }: any) => ({ + align: type === RainbowButtonTypes.addCash ? 'left' : 'center', + color: isDarkMode && disabled ? colors.white : colors.whiteLabel, + letterSpacing: type === RainbowButtonTypes.addCash ? 'roundedTight' : 'rounded', + size: type === RainbowButtonTypes.small ? 'large' : 'larger', + weight: type === RainbowButtonTypes.small ? 'bold' : 'heavy', + numberOfLines: 1, +}))({}); + +const OuterButton = styled(View)(({ height, width, isDarkMode, disabled, strokeWidth, theme: { colors } }: any) => ({ + ...shadow.buildAsObject(0, 5, 15, colors.shadow), + backgroundColor: colors.dark, + borderRadius: height / 2 + strokeWidth, + height, + shadowOpacity: isDarkMode && disabled ? 0 : isDarkMode ? 0.1 : 0.4, + width, +})); + +const Shadow = styled(ShadowView)(({ height, strokeWidth, isDarkMode, disabled, width, theme: { colors } }: any) => ({ + ...shadow.buildAsObject(0, 10, 30, colors.shadow, 1), + backgroundColor: colors.white, + borderRadius: height / 2 + strokeWidth, + height, + opacity: isDarkMode && disabled ? 0 : android ? 1 : 0.2, + position: 'absolute', + width, +})); + +type BackupRainbowButtonProps = { + disabled?: boolean; + height?: number; + label?: string; + onPress?: () => void; + strokeWidth?: number; + width?: number; + overflowMargin?: number; + skipTopMargin?: boolean; +}; + +const BackupRainbowButton = ({ + disabled = false, + height = 56, + label = 'Press me', + onPress, + strokeWidth = 1, + width, + overflowMargin = 35, + skipTopMargin = true, + ...props +}: BackupRainbowButtonProps) => { + const { isDarkMode } = useTheme(); + + const { width: deviceWidth } = useDimensions(); + const maxButtonWidth = deviceWidth - 30; + + const btnStrokeWidth = disabled ? 0.5 : strokeWidth; + const btnWidth = width || maxButtonWidth; + + const outerButtonMask = ( + + ); + + return ( + + + + + + + {label} + + + + + ); +}; + +export default BackupRainbowButton; diff --git a/src/components/buttons/rainbow-button/RainbowButtonBackground.js b/src/components/backup/BackupRainbowButtonBackground.tsx similarity index 73% rename from src/components/buttons/rainbow-button/RainbowButtonBackground.js rename to src/components/backup/BackupRainbowButtonBackground.tsx index 7b415585ae7..75059a18ecc 100644 --- a/src/components/buttons/rainbow-button/RainbowButtonBackground.js +++ b/src/components/backup/BackupRainbowButtonBackground.tsx @@ -1,25 +1,28 @@ +/* eslint-disable no-nested-ternary */ import MaskedView from '@react-native-masked-view/masked-view'; import React from 'react'; import { View } from 'react-native'; import RadialGradient from 'react-native-radial-gradient'; -import { darkModeThemeColors } from '../../../styles/colors'; -import { useTheme } from '../../../theme/ThemeContext'; -import RainbowButtonTypes from './RainbowButtonTypes'; +import { darkModeThemeColors } from '@/styles/colors'; +import { useTheme } from '@/theme'; +import RainbowButtonTypes from '@/components/buttons/rainbow-button/RainbowButtonTypes'; import styled from '@/styled-thing'; import { margin } from '@/styles'; import { magicMemo } from '@/utils'; -const RainbowGradientColorsFactory = darkMode => ({ +const RainbowGradientColorsFactory = (darkMode: boolean) => ({ inner: { - addCash: ['#FFB114', '#FF54BB', '#00F0FF'], default: darkModeThemeColors.gradients.rainbow, + backup: ['#14C7FF', '#7654FF', '#930AFF'], + disabledBackup: darkModeThemeColors.transparent, disabled: darkMode ? [darkModeThemeColors.blueGreyDark20, darkModeThemeColors.blueGreyDark20, darkModeThemeColors.blueGreyDark20] : ['#B0B3B9', '#B0B3B9', '#B0B3B9'], }, outer: { - addCash: ['#F5AA13', '#F551B4', '#00E6F5'], default: ['#F5AA13', '#F551B4', '#799DD5'], + backup: ['#14C7FF', '#7654FF', '#930AFF'], + disabledBackup: darkModeThemeColors.transparent, disabled: darkMode ? [darkModeThemeColors.blueGreyDark20, darkModeThemeColors.blueGreyDark20, darkModeThemeColors.blueGreyDark20] : ['#A5A8AE', '#A5A8AE', '#A5A8AE'], @@ -29,7 +32,7 @@ const RainbowGradientColorsFactory = darkMode => ({ const RainbowGradientColorsDark = RainbowGradientColorsFactory(true); const RainbowGradientColorsLight = RainbowGradientColorsFactory(false); -const RainbowButtonGradient = styled(RadialGradient).attrs(({ type, width }) => ({ +const RainbowButtonGradient = styled(RadialGradient).attrs(({ type, width }: any) => ({ radius: width, stops: type === RainbowButtonTypes.addCash ? [0, 0.544872, 1] : [0, 0.774321, 1], }))({ @@ -37,7 +40,7 @@ const RainbowButtonGradient = styled(RadialGradient).attrs(({ type, width }) => transform: [{ scaleY: 0.7884615385 }], }); -const InnerButton = styled(View)(({ strokeWidth, height, width, theme: { colors } }) => ({ +const InnerButton = styled(View)(({ strokeWidth, height, width, theme: { colors } }: any) => ({ ...margin.object(strokeWidth), backgroundColor: colors.dark, borderRadius: height / 2 - strokeWidth, @@ -45,25 +48,27 @@ const InnerButton = styled(View)(({ strokeWidth, height, width, theme: { colors width: width - strokeWidth * 2, })); -const InnerGradient = styled(RainbowButtonGradient).attrs(({ disabled, type, gradientColors }) => ({ +const InnerGradient = styled(RainbowButtonGradient).attrs(({ disabled, type, gradientColors }: any) => ({ colors: disabled - ? gradientColors.inner.disabled + ? type === RainbowButtonTypes.backup + ? gradientColors.inner.disabledBackup + : gradientColors.inner.disabled : type === RainbowButtonTypes.addCash ? gradientColors.inner.addCash : gradientColors.inner.default, -}))(({ width, height }) => ({ +}))(({ width, height }: any) => ({ height: width, top: -(width - height) / 2, width, })); -const OuterGradient = styled(RainbowButtonGradient).attrs(({ disabled, type, gradientColors }) => ({ +const OuterGradient = styled(RainbowButtonGradient).attrs(({ disabled, type, gradientColors }: any) => ({ colors: disabled ? gradientColors.outer.disabled : type === RainbowButtonTypes.addCash ? gradientColors.outer.addCash : gradientColors.outer.default, -}))(({ width, height }) => ({ +}))(({ width, height }: any) => ({ height: width * 2, left: -width / 2, top: -(width - height / 2), @@ -71,15 +76,23 @@ const OuterGradient = styled(RainbowButtonGradient).attrs(({ disabled, type, gra })); const WrapperView = android - ? styled.View({ - height: ({ height }) => height, + ? styled(View)({ + height: ({ height }: any) => height, overflow: 'hidden', position: 'absolute', - width: ({ width }) => width, + width: ({ width }: any) => width, }) - : ({ children }) => children; + : ({ children }: any) => children; -const RainbowButtonBackground = ({ disabled, height, strokeWidth, type, width }) => { +type RainbowButtonBackgroundProps = { + disabled: boolean; + height: number; + strokeWidth: number; + type: RainbowButtonTypes; + width: number; +}; + +const RainbowButtonBackground = ({ disabled, height, strokeWidth, type, width }: RainbowButtonBackgroundProps) => { const { isDarkMode } = useTheme(); const gradientColors = isDarkMode ? RainbowGradientColorsDark : RainbowGradientColorsLight; diff --git a/src/components/backup/BackupSheet.tsx b/src/components/backup/BackupSheet.tsx new file mode 100644 index 00000000000..be2bd57ebd8 --- /dev/null +++ b/src/components/backup/BackupSheet.tsx @@ -0,0 +1,58 @@ +import { RouteProp, useRoute } from '@react-navigation/native'; +import React, { useCallback } from 'react'; +import { BackupCloudStep, RestoreCloudStep } from '.'; +import WalletBackupStepTypes from '@/helpers/walletBackupStepTypes'; +import BackupChooseProviderStep from '@/components/backup/BackupChooseProviderStep'; +import { BackgroundProvider } from '@/design-system'; +import { SimpleSheet } from '@/components/sheet/SimpleSheet'; +import AddWalletToCloudBackupStep from '@/components/backup/AddWalletToCloudBackupStep'; +import BackupManuallyStep from './BackupManuallyStep'; +import { getHeightForStep } from '@/navigation/config'; +import { CloudBackupProvider } from './CloudBackupProvider'; + +type BackupSheetParams = { + BackupSheet: { + longFormHeight?: number; + missingPassword?: boolean; + step?: string; + walletId?: string; + nativeScreen?: boolean; + }; +}; + +export default function BackupSheet() { + const { params: { step = WalletBackupStepTypes.no_provider } = {} } = useRoute>(); + + const renderStep = useCallback(() => { + switch (step) { + case WalletBackupStepTypes.backup_now_to_cloud: + return ; + case WalletBackupStepTypes.backup_now_manually: + return ; + case WalletBackupStepTypes.backup_cloud: + return ; + case WalletBackupStepTypes.restore_from_backup: + return ; + case WalletBackupStepTypes.no_provider: + default: + return ; + } + }, [step]); + + return ( + + + {({ backgroundColor }) => ( + + {renderStep()} + + )} + + + ); +} diff --git a/src/components/backup/BackupSheetKeyboardLayout.js b/src/components/backup/BackupSheetKeyboardLayout.js deleted file mode 100644 index ef9386b8fe8..00000000000 --- a/src/components/backup/BackupSheetKeyboardLayout.js +++ /dev/null @@ -1,43 +0,0 @@ -import { useRoute } from '@react-navigation/native'; -import React from 'react'; -import { KeyboardArea } from 'react-native-keyboard-area'; -import { RainbowButton } from '../buttons'; -import { Column } from '../layout'; -import { SheetHandleFixedToTopHeight } from '../sheet'; -import KeyboardTypes from '@/helpers/keyboardTypes'; -import { useDimensions, useKeyboardHeight } from '@/hooks'; -import { sharedCoolModalTopOffset } from '@/navigation/config'; -import styled from '@/styled-thing'; -import { padding } from '@/styles'; - -const Footer = styled(Column)(({ isTallPhone }) => ({ - ...padding.object(20, 15, isTallPhone ? 65 : 50), - flexShrink: 0, - width: '100%', -})); - -const KeyboardSizeView = styled(KeyboardArea)({ - backgroundColor: ({ theme: { colors } }) => colors.transparent, -}); - -export default function BackupSheetKeyboardLayout({ children, footerButtonDisabled, footerButtonLabel, onSubmit, type }) { - const { params: { nativeScreen } = {} } = useRoute(); - const { height: deviceHeight, isTallPhone } = useDimensions(); - const keyboardHeight = useKeyboardHeight({ - keyboardType: KeyboardTypes.password, - }); - - const platformKeyboardHeight = android ? (type === 'restore' ? -10 : -30) : keyboardHeight; - - const sheetRegionAboveKeyboardHeight = deviceHeight - platformKeyboardHeight - sharedCoolModalTopOffset - SheetHandleFixedToTopHeight; - - return ( - - {children} -
- -
- {android ? : null} -
- ); -} diff --git a/src/components/backup/BackupSheetKeyboardLayout.tsx b/src/components/backup/BackupSheetKeyboardLayout.tsx new file mode 100644 index 00000000000..41ab5848657 --- /dev/null +++ b/src/components/backup/BackupSheetKeyboardLayout.tsx @@ -0,0 +1,62 @@ +import React, { PropsWithChildren } from 'react'; +import { KeyboardArea } from 'react-native-keyboard-area'; +import { RainbowButton } from '../buttons'; +import { Column } from '../layout'; +import styled from '@/styled-thing'; +import { padding } from '@/styles'; +import { Box } from '@/design-system'; +import { useDimensions } from '@/hooks'; +import { sharedCoolModalTopOffset } from '@/navigation/config'; +import RainbowButtonTypes from '../buttons/rainbow-button/RainbowButtonTypes'; + +const Footer = styled(Column)({ + ...padding.object(0, 24, 0), + flexShrink: 0, + justifyContent: 'flex-end', + width: '100%', + position: 'absolute', + bottom: 0, +}); + +const KeyboardSizeView = styled(KeyboardArea)({ + backgroundColor: ({ theme: { colors } }: any) => colors.transparent, +}); + +type BackupSheetKeyboardLayoutProps = PropsWithChildren<{ + footerButtonDisabled: boolean; + footerButtonLabel: string; + onSubmit: () => void; + type: 'backup' | 'restore'; +}>; + +type BackupSheetKeyboardLayout = { + BackupSheetKeyboardLayout: { + params: { + nativeButton?: boolean; + }; + }; +}; + +const MIN_HEIGHT = 740; + +export default function BackupSheetKeyboardLayout({ + children, + footerButtonDisabled, + footerButtonLabel, + onSubmit, +}: BackupSheetKeyboardLayoutProps) { + const { height: deviceHeight } = useDimensions(); + + const isSmallPhone = deviceHeight < MIN_HEIGHT; + const contentHeight = deviceHeight - (!isSmallPhone ? sharedCoolModalTopOffset : 0) - 100; + + return ( + + {children} +
+ +
+ {android ? : null} +
+ ); +} diff --git a/src/components/backup/BackupSheetSection.js b/src/components/backup/BackupSheetSection.tsx similarity index 59% rename from src/components/backup/BackupSheetSection.js rename to src/components/backup/BackupSheetSection.tsx index a2fa56d8412..cb658f224bd 100644 --- a/src/components/backup/BackupSheetSection.js +++ b/src/components/backup/BackupSheetSection.tsx @@ -1,21 +1,18 @@ import React, { Fragment, useEffect } from 'react'; import { useTheme } from '../../theme/ThemeContext'; -import Divider from '../Divider'; import { RainbowButton } from '../buttons'; import { Column, ColumnWithMargins } from '../layout'; import { SheetActionButton } from '../sheet'; import { Text } from '../text'; import { analytics } from '@/analytics'; -import BackupIcon from '@/assets/backupIcon.png'; -import BackupIconDark from '@/assets/backupIconDark.png'; -import { ImgixImage } from '@/components/images'; import styled from '@/styled-thing'; import { padding } from '@/styles'; +import { Bleed, Separator } from '@/design-system'; const Footer = styled(ColumnWithMargins).attrs({ margin: 19, })({ - ...padding.object(19, 15, 32), + ...padding.object(32, 15, 32), width: '100%', }); @@ -23,28 +20,26 @@ const Masthead = styled(Column).attrs({ align: 'center', justify: 'start', })({ + ...padding.object(32, 24, 40), flex: 1, - paddingTop: 8, }); -const MastheadDescription = styled(Text).attrs(({ theme: { colors } }) => ({ - align: 'center', - color: colors.alpha(colors.blueGreyDark, 0.5), - lineHeight: 'looser', - size: 'large', -}))({ ...padding.object(12, 42, 30) }); +type MaybePromise = T | Promise; -const MastheadIcon = styled(ImgixImage).attrs({ - resizeMode: ImgixImage.resizeMode.contain, -})({ - height: 74, - marginBottom: -1, - width: 75, - size: 75, -}); +type BackupSheetSectionProps = { + headerIcon?: React.ReactNode; + onPrimaryAction: () => MaybePromise; + onSecondaryAction: () => void; + primaryButtonTestId: string; + primaryLabel: string; + secondaryButtonTestId: string; + secondaryLabel: string; + titleText: string; + type: string; +}; export default function BackupSheetSection({ - descriptionText, + headerIcon, onPrimaryAction, onSecondaryAction, primaryButtonTestId, @@ -53,8 +48,8 @@ export default function BackupSheetSection({ secondaryLabel, titleText, type, -}) { - const { colors, isDarkMode } = useTheme(); +}: BackupSheetSectionProps) { + const { colors } = useTheme(); useEffect(() => { analytics.track('BackupSheet shown', { category: 'backup', @@ -65,13 +60,14 @@ export default function BackupSheetSection({ return ( - - + {headerIcon} + {titleText} - {descriptionText} - + + +