Skip to content

Commit

Permalink
fix(mobile): add hints for import wallet
Browse files Browse the repository at this point in the history
  • Loading branch information
bogoslavskiy committed Nov 30, 2023
1 parent 92652d4 commit 18ad8cc
Show file tree
Hide file tree
Showing 3 changed files with 209 additions and 62 deletions.
20 changes: 18 additions & 2 deletions packages/shared/hooks/useRecoveryPhraseInputs.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { useAnimatedStyle } from 'react-native-reanimated';
import { bip39 } from '@tonkeeper/core/src/bip39';
import { LayoutChangeEvent } from 'react-native';
import { useCallback, useRef } from 'react';
import { bip39 } from '@tonkeeper/core/src/bip39';
import {
useReanimatedKeyboardHeight,
ScreenScrollViewRef,
Expand All @@ -11,6 +13,8 @@ export function useRecoveryPhraseInputs() {
const positions = useRef<{ [key in string]: number }>({});
const refs = useRef<{ [key in string]: InputRef }>({});
const scrollViewRef = useRef<ScreenScrollViewRef>(null);
const safeArea = useSafeAreaInsets();
const focusedIndex = useRef<number>(-1);

const deferredScrollToInput = useRef<(() => void) | null>(null);
const keyboard = useReanimatedKeyboardHeight({
Expand Down Expand Up @@ -84,6 +88,8 @@ export function useRecoveryPhraseInputs() {
} else {
scrollToInput(index);
}

focusedIndex.current = index;
},
[scrollToInput],
);
Expand All @@ -95,6 +101,8 @@ export function useRecoveryPhraseInputs() {
if (value && value.length > 0 && !bip39.map[value]) {
input.markAsInvalid();
}

focusedIndex.current = -1;
},
[],
);
Expand Down Expand Up @@ -124,8 +132,16 @@ export function useRecoveryPhraseInputs() {
[],
);

const keyboardSpacerStyle = useAnimatedStyle(
() => ({
height: keyboard.height.value - safeArea.bottom + 32,
}),
[keyboard.height],
);

return {
keyboardSpacerStyle: keyboard.spacerStyle,
keyboardSpacerStyle,
currentIndex: focusedIndex,
refs: refs.current,
scrollViewRef,
setRef,
Expand Down
237 changes: 180 additions & 57 deletions packages/shared/screens/setup/pages/SetupRecoveryPhrasePage.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,27 @@
import { Screen, Button, Spacer, Steezy, Text, View, Input } from '@tonkeeper/uikit';
import { useRecoveryPhraseInputs } from '../../../hooks/useRecoveryPhraseInputs';
import { InputNumberPrefix } from '../../../components/InputNumberPrefix';
import { SearchIndexer } from '@tonkeeper/core/src/utils/SearchIndexer';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { memo, useCallback, useEffect, useState } from 'react';

import { bip39 } from '@tonkeeper/core/src/bip39';
import Animated from 'react-native-reanimated';
import Animated, { useAnimatedStyle } from 'react-native-reanimated';
import { Pressable } from 'react-native';
import {
KeyboardAccessoryView,
Screen,
Button,
Spacer,
Steezy,
Text,
View,
Input,
useReanimatedKeyboardHeight,
} from '@tonkeeper/uikit';

const inputsCount = Array(24).fill(0);
const indexedWords = new SearchIndexer(bip39.list);

// const fullMatch = hints.some((item) => item === options.query);
interface SetupPhrasePageProps {
onNext: (phrase: string, config?: string) => void;
loading: boolean;
Expand All @@ -17,8 +30,10 @@ interface SetupPhrasePageProps {
export const SetupRecoveryPhrasePage = memo<SetupPhrasePageProps>((props) => {
const { onNext, loading } = props;
const inputs = useRecoveryPhraseInputs();
const safeArea = useSafeAreaInsets();

const [isConfigInputShown, setConfigInputShown] = useState(false);
const [hints, setHints] = useState<string[]>([]);
const [isRestoring, setRestoring] = useState(false);
const [config, setConfig] = useState('');

Expand Down Expand Up @@ -85,79 +100,187 @@ export const SetupRecoveryPhrasePage = memo<SetupPhrasePageProps>((props) => {
[handleContinue],
);

const handleChangeText = useCallback(
(inputIndex: number) => (text: string) => {
inputs.onChangeText(inputIndex);

const hints = indexedWords
.search(text, 6)
.sort((a, b) => {
const preparedQuery = text.toLowerCase().replace(/\s/g, '');
const aMatch = a.toLowerCase().replace(/\s/g, '').includes(preparedQuery);
const bMatch = b.toLowerCase().replace(/\s/g, '').includes(preparedQuery);
if (aMatch && !bMatch) {
return -1;
}
if (bMatch && !aMatch) {
return 1;
}

return 0;
})
.slice(0, 3);

setHints(hints);
},
[inputs.onChangeText],
);

const handleHintPress = useCallback(
(hint: string) => () => {
console.log(inputs.currentIndex);
inputs.getRef(inputs.currentIndex.current)?.setValue(hint);
inputs.getRef(inputs.currentIndex.current + 1)?.focus();
setHints([]);
},
[],
);

const keyboard = useReanimatedKeyboardHeight();

const keyboardSpacerStyle = useAnimatedStyle(
() => ({
height: keyboard.height.value - safeArea.bottom + 32 + (hints.length ? 52 : 0),
}),
[keyboard.height, hints.length],
);

return (
<Screen.ScrollView ref={inputs.scrollViewRef} keyboardShouldPersistTaps="handled">
<View style={styles.info}>
{/* <TapGestureHandler numberOfTaps={5} onActivated={handleShowConfigInput}> */}
<Pressable onPress={handleShowConfigInput}>
<Text type="h2" textAlign="center">
Enter recovery phrase
<>
<Screen.ScrollView ref={inputs.scrollViewRef} keyboardShouldPersistTaps="handled">
<View style={styles.info}>
{/* <TapGestureHandler numberOfTaps={5} onActivated={handleShowConfigInput}> */}
<Pressable onPress={handleShowConfigInput}>
<Text type="h2" textAlign="center">
Enter recovery phrase
</Text>
</Pressable>
{/* </TapGestureHandler> */}
<Spacer y={4} />
<Text type="body1" textAlign="center" color="textSecondary">
When you created this wallet, you got a 24-word recovery phrase. Enter it to
restore access to your wallet.
</Text>
</View>
<View style={styles.content}>
{isConfigInputShown && (
<Input
onChangeText={handleConfigChange}
placeholder="Put config here"
value={config}
indentBottom
multiline
/>
)}
{inputsCount.map((_, index) => (
<Input
key={`input-${index}`}
renderToHardwareTextureAndroid
leftContent={<InputNumberPrefix index={index} />}
returnKeyType={index === 23 ? 'done' : 'next'}
onSubmitEditing={handleInputSubmit(index)}
onChangeText={handleChangeText(index)}
onLayout={inputs.setPosition(index)}
onFocus={inputs.onFocus(index)}
onBlur={inputs.onBlur(index)}
ref={inputs.setRef(index)}
disableAutoMarkValid
style={styles.input}
indentBottom
keyboardType="ascii-capable"
textContentType="none"
autoCapitalize="none"
autoComplete="off"
autoCorrect={false}
/>
))}
</View>
<View style={[styles.footer, { paddingBottom: safeArea.bottom }]}>
<Button onPress={handleContinue} title="Continue" loading={loading} />
</View>

<Animated.View style={keyboardSpacerStyle} />
</Screen.ScrollView>
<KeyboardAccessoryView
style={styles.hintsContainer}
hidden={hints.length === 0}
height={52}
>
{hints.length === 3 && (
<>
<Pressable onPress={handleHintPress(hints[2])}>
<View style={styles.hint}>
<Text type="label2" textAlign="center">
{hints[2]}
</Text>
</View>
</Pressable>
<View style={styles.hintSeparator} />
</>
)}
<Pressable onPress={handleHintPress(hints[0])}>
<View style={[styles.hint, styles.highlightedHint]}>
<Text type="label2" textAlign="center">
{hints[0]}
</Text>
</View>
</Pressable>
{/* </TapGestureHandler> */}
<Spacer y={4} />
<Text type="body1" textAlign="center" color="textSecondary">
When you created this wallet, you got a 24-word recovery phrase. Enter it to
restore access to your wallet.
</Text>
</View>
<View style={styles.content}>
{isConfigInputShown && (
<Input
onChangeText={handleConfigChange}
placeholder="Put config here"
value={config}
indentBottom
multiline
/>
{hints.length === 3 && (
<>
<View style={styles.hintSeparator} />
<Pressable onPress={handleHintPress(hints[1])}>
<View style={styles.hint}>
<Text type="label2" textAlign="center">
{hints[1]}
</Text>
</View>
</Pressable>
</>
)}
{inputsCount.map((_, index) => (
<Input
key={`input-${index}`}
renderToHardwareTextureAndroid
leftContent={<InputNumberPrefix index={index} />}
returnKeyType={index === 23 ? 'done' : 'next'}
onSubmitEditing={handleInputSubmit(index)}
onChangeText={inputs.onChangeText(index)}
onLayout={inputs.setPosition(index)}
onFocus={inputs.onFocus(index)}
onBlur={inputs.onBlur(index)}
ref={inputs.setRef(index)}
disableAutoMarkValid
style={styles.input}
indentBottom
keyboardType="ascii-capable"
textContentType="none"
autoCapitalize="none"
autoComplete="off"
autoCorrect={false}
/>
))}
</View>
<View style={styles.footer}>
<Button onPress={handleContinue} title="Continue" loading={loading} />
</View>
<Animated.View style={inputs.keyboardSpacerStyle} />
</Screen.ScrollView>
</KeyboardAccessoryView>
</>
);
});

const styles = Steezy.create({
const styles = Steezy.create(({ colors }) => ({
info: {
marginTop: 24,
paddingHorizontal: 32,
paddingBottom: 16,
},
content: {
paddingHorizontal: 32,
paddingVertical: 16,
paddingTop: 16,
},
footer: {
paddingTop: 16,
paddingHorizontal: 32,
paddingBottom: 80,
marginBottom: 32,
},
input: {
paddingLeft: 50,
},
});
hintsContainer: {
backgroundColor: colors.backgroundContentTint,
height: 52,
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
padding: 8,
},
hint: {
padding: 8,
width: 113.33,
borderRadius: 18,
},
highlightedHint: {
backgroundColor: colors.backgroundContentAttention,
},
hintSeparator: {
width: 1,
borderRadius: 1,
height: 20,
backgroundColor: colors.iconTertiary,
marginHorizontal: 8,
},
}));
14 changes: 11 additions & 3 deletions packages/uikit/src/components/KeyboardAccessoryView.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Animated, { useAnimatedStyle } from 'react-native-reanimated';
import Animated, { useAnimatedStyle, withTiming } from 'react-native-reanimated';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { useReanimatedKeyboardHeight } from '../utils/keyboard';
import { StyleProp } from '@bogoslavskiy/react-native-steezy';
Expand All @@ -9,11 +9,12 @@ import { memo } from 'react';
interface KeyboardAccessoryViewProps {
style?: StyleProp<ViewStyle>;
children: React.ReactNode;
hidden?: boolean;
height: number;
}

export const KeyboardAccessoryView = memo<KeyboardAccessoryViewProps>((props) => {
const { children, style, height } = props;
const { children, style, height, hidden } = props;
const keyboard = useReanimatedKeyboardHeight();
const safeArea = useSafeAreaInsets();

Expand All @@ -24,8 +25,15 @@ export const KeyboardAccessoryView = memo<KeyboardAccessoryViewProps>((props) =>
[keyboard.height, height, safeArea.bottom],
);

const opacityStyle = useAnimatedStyle(
() => ({
opacity: withTiming(!hidden ? 1 : 0, { duration: 100 }),
}),
[hidden],
);

return (
<Animated.View style={[styles.keyboardAccessory, heightStyle]}>
<Animated.View style={[styles.keyboardAccessory, heightStyle, opacityStyle]}>
<View style={style}>{children}</View>
</Animated.View>
);
Expand Down

0 comments on commit 18ad8cc

Please sign in to comment.