From 948b472150189df7511042fb450b6679318f0210 Mon Sep 17 00:00:00 2001 From: Max Voloshinskii Date: Tue, 30 Apr 2024 00:06:14 +0300 Subject: [PATCH 1/3] fix(mobile): Copy/paste mnemonic improvements --- .../ImportWalletForm/ImportWalletForm.tsx | 55 ++++++++++++++++++- .../components/ImportWalletForm/InputItem.tsx | 2 + 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/packages/mobile/src/shared/components/ImportWalletForm/ImportWalletForm.tsx b/packages/mobile/src/shared/components/ImportWalletForm/ImportWalletForm.tsx index 84ee7f8b9..dafd54159 100644 --- a/packages/mobile/src/shared/components/ImportWalletForm/ImportWalletForm.tsx +++ b/packages/mobile/src/shared/components/ImportWalletForm/ImportWalletForm.tsx @@ -1,7 +1,9 @@ import React, { FC, useCallback, useMemo, useRef, useState } from 'react'; import Animated, { useAnimatedScrollHandler, + useAnimatedStyle, useSharedValue, + withTiming, } from 'react-native-reanimated'; import { TapGestureHandler } from 'react-native-gesture-handler'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; @@ -9,21 +11,22 @@ import { useDispatch } from 'react-redux'; import { deviceHeight, isAndroid, ns, parseLockupConfig } from '$utils'; import { InputItem } from './InputItem'; -import { Button, Input, NavBarHelper, Text } from '$uikit'; +import { Input, NavBarHelper, Text, Button } from '$uikit'; import * as S from './ImportWalletForm.style'; import { useReanimatedKeyboardHeight } from '$hooks/useKeyboardHeight'; import { ImportWalletFormProps } from './ImportWalletForm.interface'; import { useInputsRegistry } from './useInputRegistry'; import { WordHintsPopup, WordHintsPopupRef } from './WordHintsPopup'; -import { Keyboard } from 'react-native'; +import { Keyboard, KeyboardAvoidingView } from 'react-native'; import { wordlist } from '$libs/Ton/mnemonic/wordlist'; import { Toast } from '$store'; import { t } from '@tonkeeper/shared/i18n'; +import { Steezy, Button as ButtonNew } from '@tonkeeper/uikit'; +import Clipboard from '@react-native-community/clipboard'; export const ImportWalletForm: FC = (props) => { const { onWordsFilled } = props; - const { bottom: bottomInset } = useSafeAreaInsets(); const dispatch = useDispatch(); const inputsRegistry = useInputsRegistry(); @@ -36,6 +39,7 @@ export const ImportWalletForm: FC = (props) => { const [isConfigInputShown, setConfigInputShown] = useState(false); const [config, setConfig] = useState(''); const [isRestoring, setRestoring] = useState(false); + const hasTouchedInputs = useSharedValue(false); const deferredScrollToInput = useRef<((offset: number) => void) | null>(null); const { keyboardHeight } = useReanimatedKeyboardHeight({ @@ -57,8 +61,17 @@ export const ImportWalletForm: FC = (props) => { setConfig(text); }, []); + const pasteButtonStyle = useAnimatedStyle( + () => ({ + opacity: withTiming(hasTouchedInputs.value ? 0 : 1, { duration: 200 }), + }), + [], + ); + const handleMultipleWords = useCallback((index: number, text: string) => { + hasTouchedInputs.value = true; const words = text + .replace(/\r\n|\r|\n/g, ' ') .split(' ') .map((word) => word.trim()) .filter((word) => word.length > 0); @@ -77,6 +90,16 @@ export const ImportWalletForm: FC = (props) => { } }, []); + const handlePasteButton = useCallback(async () => { + if (hasTouchedInputs.value) { + return; + } + const maybePhrase = await Clipboard.getString(); + if (maybePhrase.replace(/\r\n|\r|\n/g, ' ').split(' ').length === 24) { + handleMultipleWords(0, maybePhrase); + } + }, [handleMultipleWords]); + const handleSpace = useCallback((index: number) => { if (index === 24) { return; @@ -185,6 +208,7 @@ export const ImportWalletForm: FC = (props) => { const handleChangeText = useCallback( (index: number) => (text: string) => { + hasTouchedInputs.value = true; const overlap = 10; const offsetTop = inputsRegistry.getPosition(index) + S.INPUT_HEIGHT - overlap; const contentWidth = isAndroid ? 0 : inputsRegistry.getContentWidth(index); @@ -269,6 +293,31 @@ export const ImportWalletForm: FC = (props) => { + + + + + ); }; + +const styles = Steezy.create({ + pasteButtonContainer: { + position: 'absolute', + left: 0, + right: 0, + alignItems: 'center', + }, +}); diff --git a/packages/mobile/src/shared/components/ImportWalletForm/InputItem.tsx b/packages/mobile/src/shared/components/ImportWalletForm/InputItem.tsx index ca060eb61..90902c002 100644 --- a/packages/mobile/src/shared/components/ImportWalletForm/InputItem.tsx +++ b/packages/mobile/src/shared/components/ImportWalletForm/InputItem.tsx @@ -52,6 +52,8 @@ export const InputItem = forwardRef((props, ref) = const handleChangeText = useCallback( (text) => { let newText = text.trim(); + newText = newText.replace(/\r\n|\r|\n/g, ' '); + if (newText.split(' ').length > 1) { onMultipleWords(index, newText); } else if (text.slice(-1) === ' ') { From bc0b82f35a79a54611c5c4651e6bbe48ed3b5b00 Mon Sep 17 00:00:00 2001 From: Max Voloshinskii Date: Tue, 30 Apr 2024 00:09:34 +0300 Subject: [PATCH 2/3] bump(mobile): 4.4.0 --- packages/mobile/android/app/build.gradle | 2 +- packages/mobile/ios/ton_keeper.xcodeproj/project.pbxproj | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/mobile/android/app/build.gradle b/packages/mobile/android/app/build.gradle index 8c0ffa385..86deb18c9 100644 --- a/packages/mobile/android/app/build.gradle +++ b/packages/mobile/android/app/build.gradle @@ -92,7 +92,7 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode 433 - versionName "4.3.0" + versionName "4.4.0" missingDimensionStrategy 'react-native-camera', 'general' missingDimensionStrategy 'store', 'play' } diff --git a/packages/mobile/ios/ton_keeper.xcodeproj/project.pbxproj b/packages/mobile/ios/ton_keeper.xcodeproj/project.pbxproj index ed578fecd..6aef0df6f 100644 --- a/packages/mobile/ios/ton_keeper.xcodeproj/project.pbxproj +++ b/packages/mobile/ios/ton_keeper.xcodeproj/project.pbxproj @@ -1298,7 +1298,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 4.3.0; + MARKETING_VERSION = 4.4.0; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", @@ -1332,7 +1332,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 4.3.0; + MARKETING_VERSION = 4.4.0; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", From 96a4c40c545b6523382d5561c92f32bbb9008d11 Mon Sep 17 00:00:00 2001 From: Max Voloshinskii Date: Tue, 30 Apr 2024 16:25:04 +0300 Subject: [PATCH 3/3] fix(mobile): Small sizes variant for BackupPhraseScreen --- .../BackupPhraseScreen/BackupPhraseScreen.tsx | 81 +++++++++++++--- .../ImportWalletForm/ImportWalletForm.tsx | 93 +++++++++---------- 2 files changed, 115 insertions(+), 59 deletions(-) diff --git a/packages/mobile/src/screens/BackupPhraseScreen/BackupPhraseScreen.tsx b/packages/mobile/src/screens/BackupPhraseScreen/BackupPhraseScreen.tsx index 9b56fb282..ad4336492 100644 --- a/packages/mobile/src/screens/BackupPhraseScreen/BackupPhraseScreen.tsx +++ b/packages/mobile/src/screens/BackupPhraseScreen/BackupPhraseScreen.tsx @@ -1,12 +1,59 @@ -import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import { Button, Screen, Spacer, Text, copyText } from '@tonkeeper/uikit'; +import { Button, Screen, Spacer, Text, copyText, Steezy } from '@tonkeeper/uikit'; import { useParams } from '@tonkeeper/router/src/imperative'; -import { View, StyleSheet } from 'react-native'; +import { View, StyleSheet, ViewStyle } from 'react-native'; import { memo, useCallback, useMemo } from 'react'; import { useNavigation } from '@tonkeeper/router'; import { t } from '@tonkeeper/shared/i18n'; import { useWalletSetup } from '@tonkeeper/shared/hooks'; import { MainStackRouteNames } from '$navigation'; +import { deviceHeight } from '@tonkeeper/uikit'; +import { TTextTypes } from '@tonkeeper/uikit/src/components/Text/TextStyles'; +import { + CreatedStyles, + ExtractMediaVars, +} from '@bogoslavskiy/react-native-steezy/dist/types'; + +export interface Sizes { + title: TTextTypes; + caption: TTextTypes; + index: TTextTypes; + word: TTextTypes; + styles: CreatedStyles< + StyleSheet.NamedStyles<{ line: ViewStyle }> & ExtractMediaVars<{ isTablet: unknown }> + >; +} + +const defaultSizes: Sizes = { + title: 'h2', + caption: 'body1', + index: 'body2', + word: 'body1', + styles: Steezy.create({ + line: { + width: 151, + flexDirection: 'row', + marginBottom: 8, + height: 24, + }, + }), +}; + +const smallSizes: Sizes = { + title: 'h3', + caption: 'body2', + index: 'body3', + word: 'body2', + styles: Steezy.create({ + line: { + width: 151, + flexDirection: 'row', + marginBottom: 4, + height: 18, + }, + }), +}; + +const sizesConfig = deviceHeight >= 650 ? defaultSizes : smallSizes; function getRandIndexes(length: number, indexes: number[] = []) { if (indexes.length === length) { @@ -23,7 +70,6 @@ function getRandIndexes(length: number, indexes: number[] = []) { export const BackupPhraseScreen = memo(() => { const params = useParams<{ mnemonic: string; isBackupAgain?: boolean }>(); - const safeArea = useSafeAreaInsets(); const nav = useNavigation(); const mnemonic = params.mnemonic!; @@ -43,11 +89,11 @@ export const BackupPhraseScreen = memo(() => { - + {t('recovery_phrase.title')} - + {t('recovery_phrase.caption')} @@ -56,21 +102,32 @@ export const BackupPhraseScreen = memo(() => { {leftColumn.map((word, index) => ( - - + + {index + 1}. - {word} + {word} ))} {rightColumn.map((word, index) => ( - - + + {index + 1 + 12}. - {word} + {word} ))} diff --git a/packages/mobile/src/shared/components/ImportWalletForm/ImportWalletForm.tsx b/packages/mobile/src/shared/components/ImportWalletForm/ImportWalletForm.tsx index dafd54159..308467c6b 100644 --- a/packages/mobile/src/shared/components/ImportWalletForm/ImportWalletForm.tsx +++ b/packages/mobile/src/shared/components/ImportWalletForm/ImportWalletForm.tsx @@ -1,5 +1,6 @@ import React, { FC, useCallback, useMemo, useRef, useState } from 'react'; import Animated, { + FadeOut, useAnimatedScrollHandler, useAnimatedStyle, useSharedValue, @@ -21,7 +22,7 @@ import { Keyboard, KeyboardAvoidingView } from 'react-native'; import { wordlist } from '$libs/Ton/mnemonic/wordlist'; import { Toast } from '$store'; import { t } from '@tonkeeper/shared/i18n'; -import { Steezy, Button as ButtonNew } from '@tonkeeper/uikit'; +import { Steezy, Button as ButtonNew, View } from '@tonkeeper/uikit'; import Clipboard from '@react-native-community/clipboard'; export const ImportWalletForm: FC = (props) => { @@ -39,7 +40,7 @@ export const ImportWalletForm: FC = (props) => { const [isConfigInputShown, setConfigInputShown] = useState(false); const [config, setConfig] = useState(''); const [isRestoring, setRestoring] = useState(false); - const hasTouchedInputs = useSharedValue(false); + const [hasTouchedInputs, setHasTouchedInputs] = useState(false); const deferredScrollToInput = useRef<((offset: number) => void) | null>(null); const { keyboardHeight } = useReanimatedKeyboardHeight({ @@ -61,44 +62,42 @@ export const ImportWalletForm: FC = (props) => { setConfig(text); }, []); - const pasteButtonStyle = useAnimatedStyle( - () => ({ - opacity: withTiming(hasTouchedInputs.value ? 0 : 1, { duration: 200 }), - }), - [], - ); - - const handleMultipleWords = useCallback((index: number, text: string) => { - hasTouchedInputs.value = true; - const words = text - .replace(/\r\n|\r|\n/g, ' ') - .split(' ') - .map((word) => word.trim()) - .filter((word) => word.length > 0); - - let cursor = index; - for (const word of words) { - inputsRegistry.getRef(cursor)?.setValue(word); - cursor += 1; - if (cursor === 24) { - break; + const handleMultipleWords = useCallback( + (index: number, text: string) => { + if (!hasTouchedInputs) { + setHasTouchedInputs(true); + } + const words = text + .replace(/\r\n|\r|\n/g, ' ') + .split(' ') + .map((word) => word.trim()) + .filter((word) => word.length > 0); + + let cursor = index; + for (const word of words) { + inputsRegistry.getRef(cursor)?.setValue(word); + cursor += 1; + if (cursor === 24) { + break; + } } - } - if (cursor > 0) { - inputsRegistry.getRef(cursor - 1)?.focus(); - } - }, []); + if (cursor > 0) { + inputsRegistry.getRef(cursor - 1)?.focus(); + } + }, + [hasTouchedInputs, inputsRegistry], + ); const handlePasteButton = useCallback(async () => { - if (hasTouchedInputs.value) { - return; + if (!hasTouchedInputs) { + setHasTouchedInputs(true); } const maybePhrase = await Clipboard.getString(); if (maybePhrase.replace(/\r\n|\r|\n/g, ' ').split(' ').length === 24) { handleMultipleWords(0, maybePhrase); } - }, [handleMultipleWords]); + }, [hasTouchedInputs, handleMultipleWords]); const handleSpace = useCallback((index: number) => { if (index === 24) { @@ -208,7 +207,9 @@ export const ImportWalletForm: FC = (props) => { const handleChangeText = useCallback( (index: number) => (text: string) => { - hasTouchedInputs.value = true; + if (!hasTouchedInputs) { + setHasTouchedInputs(true); + } const overlap = 10; const offsetTop = inputsRegistry.getPosition(index) + S.INPUT_HEIGHT - overlap; const contentWidth = isAndroid ? 0 : inputsRegistry.getContentWidth(index); @@ -225,7 +226,7 @@ export const ImportWalletForm: FC = (props) => { }, }); }, - [], + [hasTouchedInputs], ); const scrollHandler = useAnimatedScrollHandler({ @@ -294,20 +295,18 @@ export const ImportWalletForm: FC = (props) => { - - - + + {!hasTouchedInputs && ( + + + + )} + );