fix: animations got lost (#8504)
Some checks are pending
Build Release and Upload to TestFlight (iOS) / build (push) Waiting to run
Build Release and Upload to TestFlight (iOS) / testflight-upload (push) Blocked by required conditions
BuildReleaseApk / buildReleaseApk (push) Waiting to run
BuildReleaseApk / browserstack (push) Blocked by required conditions
Some checks are pending
Build Release and Upload to TestFlight (iOS) / build (push) Waiting to run
Build Release and Upload to TestFlight (iOS) / testflight-upload (push) Blocked by required conditions
BuildReleaseApk / buildReleaseApk (push) Waiting to run
BuildReleaseApk / browserstack (push) Blocked by required conditions
This commit is contained in:
parent
f87ffa9633
commit
038cabedaf
@ -45,27 +45,27 @@ export const PasswordInput = forwardRef<PasswordInputHandle, PasswordInputProps>
|
||||
// macOS-style shake animation - quick and snappy
|
||||
Animated.sequence([
|
||||
Animated.timing(shakeAnimation, {
|
||||
toValue: 20,
|
||||
duration: 80,
|
||||
easing: Easing.linear,
|
||||
toValue: 10,
|
||||
duration: 50,
|
||||
easing: Easing.out(Easing.quad),
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
Animated.timing(shakeAnimation, {
|
||||
toValue: -20,
|
||||
duration: 80,
|
||||
easing: Easing.linear,
|
||||
toValue: -10,
|
||||
duration: 50,
|
||||
easing: Easing.out(Easing.quad),
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
Animated.timing(shakeAnimation, {
|
||||
toValue: 20,
|
||||
duration: 80,
|
||||
easing: Easing.linear,
|
||||
toValue: 8,
|
||||
duration: 45,
|
||||
easing: Easing.out(Easing.quad),
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
Animated.timing(shakeAnimation, {
|
||||
toValue: 0,
|
||||
duration: 80,
|
||||
easing: Easing.linear,
|
||||
duration: 45,
|
||||
easing: Easing.out(Easing.quad),
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
]).start(() => {
|
||||
|
||||
@ -175,8 +175,6 @@ const ToolTipMenu = (props: ToolTipMenuProps) => {
|
||||
<Pressable
|
||||
android_ripple={enableAndroidRipple ? { color: '#d9d9d9', foreground: true } : undefined}
|
||||
style={({ pressed }) => {
|
||||
// Keep visual feedback on Android by default. iOS context-menu preview
|
||||
// already applies a system press effect; opt in when needed.
|
||||
const shouldApplyPressedStyle =
|
||||
pressed && ((Platform.OS === 'android' && enableAndroidRipple) || (Platform.OS === 'ios' && enableIOSPressOpacity));
|
||||
return StyleSheet.flatten([styles.pressable, style, buttonStyle, shouldApplyPressedStyle && styles.pressed]);
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import React, { useCallback, useMemo, memo } from 'react';
|
||||
import React, { useCallback, useMemo, memo, useRef } from 'react';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import Clipboard from '@react-native-clipboard/clipboard';
|
||||
import { Linking, Text, TextStyle, ViewStyle, StyleSheet, View } from 'react-native';
|
||||
import { Animated, Easing, Linking, Pressable, Text, TextStyle, ViewStyle, StyleSheet, View } from 'react-native';
|
||||
import Lnurl from '../class/lnurl';
|
||||
import { LightningTransaction, Transaction } from '../class/wallets/types';
|
||||
import TransactionExpiredIcon from '../components/icons/TransactionExpiredIcon';
|
||||
@ -28,11 +28,6 @@ import { uint8ArrayToHex } from '../blue_modules/uint8array-extras';
|
||||
import ListItem from './ListItem';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
pressable: {
|
||||
paddingVertical: 12,
|
||||
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||
width: '100%',
|
||||
},
|
||||
dateLine: {
|
||||
fontSize: 13,
|
||||
},
|
||||
@ -70,8 +65,45 @@ const styles = StyleSheet.create({
|
||||
rightTitle: {
|
||||
textAlign: 'right',
|
||||
},
|
||||
animatedScaleContainer: {
|
||||
width: '100%',
|
||||
},
|
||||
});
|
||||
|
||||
type AnimatedPressableRowProps = {
|
||||
onPress: () => void;
|
||||
children: React.ReactNode;
|
||||
accessibilityLabel: string;
|
||||
};
|
||||
|
||||
const AnimatedPressableRow: React.FC<AnimatedPressableRowProps> = ({ onPress, children, accessibilityLabel }) => {
|
||||
const scaleAnim = useRef(new Animated.Value(1)).current;
|
||||
|
||||
const animateTo = useCallback(
|
||||
(toValue: number) => {
|
||||
Animated.timing(scaleAnim, {
|
||||
toValue,
|
||||
duration: 120,
|
||||
easing: Easing.out(Easing.cubic),
|
||||
useNativeDriver: true,
|
||||
}).start();
|
||||
},
|
||||
[scaleAnim],
|
||||
);
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
onPress={onPress}
|
||||
onPressIn={() => animateTo(0.97)}
|
||||
onPressOut={() => animateTo(1)}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={accessibilityLabel}
|
||||
>
|
||||
<Animated.View style={[styles.animatedScaleContainer, { transform: [{ scale: scaleAnim }] }]}>{children}</Animated.View>
|
||||
</Pressable>
|
||||
);
|
||||
};
|
||||
|
||||
interface TransactionListItemProps {
|
||||
itemPriceUnit?: BitcoinUnit;
|
||||
walletID: string;
|
||||
@ -443,45 +475,45 @@ export const TransactionListItem: React.FC<TransactionListItemProps> = memo(
|
||||
|
||||
return (
|
||||
<ToolTipMenu
|
||||
isButton
|
||||
actions={toolTipActions}
|
||||
onPressMenuItem={onToolTipPress}
|
||||
onPress={onPress}
|
||||
shouldOpenOnLongPress
|
||||
buttonStyle={styles.fullWidthButton}
|
||||
style={styles.fullWidthButton}
|
||||
accessibilityLabel={`${transactionTypeLabel}, ${amountWithUnit}, ${subtitle ?? title}`}
|
||||
accessibilityRole="button"
|
||||
>
|
||||
{/* @ts-ignore - Context menu wrapper types can be overly strict about child element props */}
|
||||
<ListItem
|
||||
leftAvatar={avatar}
|
||||
title={listTitle}
|
||||
subtitle={<Text style={styles.dateLine}>{dateLine}</Text>}
|
||||
chevron={false}
|
||||
rightTitle={rowTitle}
|
||||
rightTitleStyle={rowTitleStyle}
|
||||
rightSubtitle={noteForCopy}
|
||||
rightSubtitleStyle={styles.rightColumn}
|
||||
containerStyle={combinedStyle}
|
||||
testID="TransactionListItem"
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={`${transactionTypeLabel}, ${amountWithUnit}, ${subtitle ?? title}`}
|
||||
>
|
||||
<View style={styles.row}>
|
||||
<View style={styles.avatarContainer}>{avatar}</View>
|
||||
<View style={styles.textContainer}>
|
||||
<Text style={[styles.title, titleStyle]} numberOfLines={1}>
|
||||
{title}
|
||||
</Text>
|
||||
{subtitleContent}
|
||||
<AnimatedPressableRow onPress={onPress} accessibilityLabel={`${transactionTypeLabel}, ${amountWithUnit}, ${subtitle ?? title}`}>
|
||||
{/* @ts-ignore - Context menu wrapper types can be overly strict about child element props */}
|
||||
<ListItem
|
||||
leftAvatar={avatar}
|
||||
title={listTitle}
|
||||
subtitle={<Text style={styles.dateLine}>{dateLine}</Text>}
|
||||
chevron={false}
|
||||
rightTitle={rowTitle}
|
||||
rightTitleStyle={rowTitleStyle}
|
||||
rightSubtitle={noteForCopy}
|
||||
rightSubtitleStyle={styles.rightColumn}
|
||||
containerStyle={combinedStyle}
|
||||
testID="TransactionListItem"
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={`${transactionTypeLabel}, ${amountWithUnit}, ${subtitle ?? title}`}
|
||||
>
|
||||
<View style={styles.row}>
|
||||
<View style={styles.avatarContainer}>{avatar}</View>
|
||||
<View style={styles.textContainer}>
|
||||
<Text style={[styles.title, titleStyle]} numberOfLines={1}>
|
||||
{title}
|
||||
</Text>
|
||||
{subtitleContent}
|
||||
</View>
|
||||
<View style={styles.rightColumn}>
|
||||
<Text style={[styles.rightTitle, rowTitleStyle]} numberOfLines={1}>
|
||||
{rowTitle}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.rightColumn}>
|
||||
<Text style={[styles.rightTitle, rowTitleStyle]} numberOfLines={1}>
|
||||
{rowTitle}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</ListItem>
|
||||
</ListItem>
|
||||
</AnimatedPressableRow>
|
||||
</ToolTipMenu>
|
||||
);
|
||||
},
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
import React, { useCallback, useEffect, useReducer, useRef } from 'react';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Animated,
|
||||
Dimensions,
|
||||
Easing,
|
||||
Image,
|
||||
Keyboard,
|
||||
KeyboardAvoidingView,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
TouchableWithoutFeedback,
|
||||
@ -80,6 +82,7 @@ function reducer(state: State, action: Action): State {
|
||||
const UnlockWith: React.FC = () => {
|
||||
const [state, dispatch] = useReducer(reducer, initialState);
|
||||
const isUnlockingWallets = useRef(false);
|
||||
const keyboardOffset = useRef(new Animated.Value(0)).current;
|
||||
const passwordInputRef = useRef<PasswordInputHandle>(null);
|
||||
const passwordResolveRef = useRef<((password: string | undefined) => void) | null>(null);
|
||||
const { setWalletsInitialized, isStorageEncrypted, startAndDecrypt } = useStorage();
|
||||
@ -89,6 +92,44 @@ const UnlockWith: React.FC = () => {
|
||||
setWalletsInitialized(false);
|
||||
}, [setWalletsInitialized]);
|
||||
|
||||
useEffect(() => {
|
||||
const windowHeight = Dimensions.get('window').height;
|
||||
|
||||
const animateToKeyboardPosition = (event: any, fallbackDuration = 220) => {
|
||||
const keyboardTop = event?.endCoordinates?.screenY ?? windowHeight;
|
||||
const keyboardHeight = Math.max(0, windowHeight - keyboardTop);
|
||||
const target = -Math.min(Math.max(keyboardHeight * 0.28, 0), 96);
|
||||
|
||||
Animated.timing(keyboardOffset, {
|
||||
toValue: target,
|
||||
duration: event?.duration ?? fallbackDuration,
|
||||
easing: Easing.out(Easing.cubic),
|
||||
useNativeDriver: true,
|
||||
}).start();
|
||||
};
|
||||
|
||||
const resetPosition = (event?: any) => {
|
||||
Animated.timing(keyboardOffset, {
|
||||
toValue: 0,
|
||||
duration: event?.duration ?? 220,
|
||||
easing: Easing.out(Easing.cubic),
|
||||
useNativeDriver: true,
|
||||
}).start();
|
||||
};
|
||||
|
||||
const subscriptions =
|
||||
Platform.OS === 'ios'
|
||||
? [
|
||||
Keyboard.addListener('keyboardWillChangeFrame', animateToKeyboardPosition),
|
||||
Keyboard.addListener('keyboardWillHide', resetPosition),
|
||||
]
|
||||
: [Keyboard.addListener('keyboardDidShow', animateToKeyboardPosition), Keyboard.addListener('keyboardDidHide', resetPosition)];
|
||||
|
||||
return () => {
|
||||
subscriptions.forEach(sub => sub.remove());
|
||||
};
|
||||
}, [keyboardOffset]);
|
||||
|
||||
const successfullyAuthenticated = useCallback(() => {
|
||||
setWalletsInitialized(true);
|
||||
isUnlockingWallets.current = false;
|
||||
@ -244,14 +285,14 @@ const UnlockWith: React.FC = () => {
|
||||
return (
|
||||
<SafeArea style={styles.root}>
|
||||
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
|
||||
<KeyboardAvoidingView style={styles.keyboardAvoidingView} behavior={Platform.OS === 'ios' ? 'padding' : 'height'}>
|
||||
<View style={styles.contentContainer}>
|
||||
<View style={styles.keyboardAvoidingView}>
|
||||
<Animated.View style={[styles.contentContainer, { transform: [{ translateY: keyboardOffset }] }]}>
|
||||
<View style={styles.logoContainer}>
|
||||
<Image source={require('../img/icon.png')} style={styles.logoImage} resizeMode="contain" />
|
||||
</View>
|
||||
<View style={styles.biometricRow}>{renderUnlockOptions()}</View>
|
||||
</View>
|
||||
</KeyboardAvoidingView>
|
||||
</Animated.View>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
</SafeArea>
|
||||
);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user