Merge pull request #7631 from BlueWallet/headr

REF: Wallet tranaction header animation
This commit is contained in:
GLaDOS 2025-03-03 19:53:08 +00:00 committed by GitHub
commit 28e2e343b8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 160 additions and 196 deletions

View file

@ -170,7 +170,7 @@ const TransactionsNavigationHeader: React.FC<TransactionsNavigationHeaderProps>
> >
<Image source={imageSource} style={styles.chainIcon} /> <Image source={imageSource} style={styles.chainIcon} />
<Text testID="WalletLabel" numberOfLines={1} style={styles.walletLabel} selectable> <Text testID="WalletLabel" numberOfLines={2} style={styles.walletLabel} selectable>
{wallet.getLabel()} {wallet.getLabel()}
</Text> </Text>
<View style={styles.walletBalanceAndUnitContainer}> <View style={styles.walletBalanceAndUnitContainer}>

View file

@ -10,7 +10,7 @@ import { useCallback, useMemo } from 'react';
const requiresBiometrics = [ const requiresBiometrics = [
'WalletExportRoot', 'WalletExportRoot',
'WalletXpubRoot', 'WalletXpubRoot',
'ViewEditMultisigCosignersRoot', 'ViewEditMultisigCosigners',
'ExportMultisigCoordinationSetupRoot', 'ExportMultisigCoordinationSetupRoot',
]; ];

View file

@ -35,7 +35,6 @@ import ReceiveDetailsStackRoot from './ReceiveDetailsStack';
import ScanLndInvoiceRoot from './ScanLndInvoiceStack'; import ScanLndInvoiceRoot from './ScanLndInvoiceStack';
import SendDetailsStack from './SendDetailsStack'; import SendDetailsStack from './SendDetailsStack';
import SignVerifyStackRoot from './SignVerifyStack'; import SignVerifyStackRoot from './SignVerifyStack';
import ViewEditMultisigCosignersStackRoot from './ViewEditMultisigCosignersStack';
import WalletExportStack from './WalletExportStack'; import WalletExportStack from './WalletExportStack';
import WalletXpubStackRoot from './WalletXpubStack'; import WalletXpubStackRoot from './WalletXpubStack';
import SettingsButton from '../components/icons/SettingsButton'; import SettingsButton from '../components/icons/SettingsButton';
@ -66,6 +65,7 @@ import ToolsScreen from '../screen/settings/tools';
import SettingsPrivacy from '../screen/settings/SettingsPrivacy'; import SettingsPrivacy from '../screen/settings/SettingsPrivacy';
import { ScanQRCodeComponent } from './LazyLoadScanQRCodeStack'; import { ScanQRCodeComponent } from './LazyLoadScanQRCodeStack';
import { useIsLargeScreen } from '../hooks/useIsLargeScreen'; import { useIsLargeScreen } from '../hooks/useIsLargeScreen';
import { ViewEditMultisigCosignersComponent } from './LazyLoadViewEditMultisigCosignersStack';
const DetailViewStackScreensStack = () => { const DetailViewStackScreensStack = () => {
const theme = useTheme(); const theme = useTheme();
@ -342,8 +342,8 @@ const DetailViewStackScreensStack = () => {
/> />
<DetailViewStack.Screen <DetailViewStack.Screen
name="ViewEditMultisigCosignersRoot" name="ViewEditMultisigCosigners"
component={ViewEditMultisigCosignersStackRoot} component={ViewEditMultisigCosignersComponent}
options={{ ...NavigationDefaultOptions, ...StatusBarLightOptions, gestureEnabled: false, fullScreenGestureEnabled: false }} options={{ ...NavigationDefaultOptions, ...StatusBarLightOptions, gestureEnabled: false, fullScreenGestureEnabled: false }}
initialParams={{ walletID: undefined, cosigners: undefined }} initialParams={{ walletID: undefined, cosigners: undefined }}
/> />

View file

@ -79,7 +79,7 @@ export type DetailViewStackParamList = {
ReleaseNotes: undefined; ReleaseNotes: undefined;
ToolsScreen: undefined; ToolsScreen: undefined;
SettingsPrivacy: undefined; SettingsPrivacy: undefined;
ViewEditMultisigCosignersRoot: { walletID: string; cosigners: string[] }; ViewEditMultisigCosigners: { walletID: string; cosigners: string[]; onBarScanned?: string };
WalletXpubRoot: undefined; WalletXpubRoot: undefined;
SignVerifyRoot: { SignVerifyRoot: {
screen: 'SignVerify'; screen: 'SignVerify';

View file

@ -1,48 +0,0 @@
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import React from 'react';
import navigationStyle from '../components/navigationStyle';
import { useTheme } from '../components/themes';
import loc from '../loc';
import { ViewEditMultisigCosignersComponent } from './LazyLoadViewEditMultisigCosignersStack';
import { ScanQRCodeComponent } from './LazyLoadScanQRCodeStack';
import { ScanQRCodeParamList } from './DetailViewStackParamList';
export type ViewEditMultisigCosignersStackParamList = {
ViewEditMultisigCosigners: {
walletID: string;
onBarScanned?: string;
};
ScanQRCode: ScanQRCodeParamList;
};
const Stack = createNativeStackNavigator<ViewEditMultisigCosignersStackParamList>();
const ViewEditMultisigCosignersStackRoot = () => {
const theme = useTheme();
return (
<Stack.Navigator screenOptions={{ headerShadowVisible: false }}>
<Stack.Screen
name="ViewEditMultisigCosigners"
component={ViewEditMultisigCosignersComponent}
options={navigationStyle({
headerBackVisible: false,
title: loc.multisig.manage_keys,
})(theme)}
/>
<Stack.Screen
name="ScanQRCode"
component={ScanQRCodeComponent}
options={navigationStyle({
headerShown: false,
statusBarHidden: true,
presentation: 'fullScreenModal',
headerShadowVisible: false,
})(theme)}
/>
</Stack.Navigator>
);
};
export default ViewEditMultisigCosignersStackRoot;

View file

@ -1,5 +1,5 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { RouteProp, useFocusEffect, useRoute, usePreventRemove, CommonActions } from '@react-navigation/native'; import { RouteProp, useFocusEffect, useRoute, usePreventRemove, StackActions } from '@react-navigation/native';
import { import {
ActivityIndicator, ActivityIndicator,
Alert, Alert,
@ -18,7 +18,15 @@ import {
import { Badge, Icon } from '@rneui/themed'; import { Badge, Icon } from '@rneui/themed';
import { isDesktop } from '../../blue_modules/environment'; import { isDesktop } from '../../blue_modules/environment';
import { encodeUR } from '../../blue_modules/ur'; import { encodeUR } from '../../blue_modules/ur';
import { BlueCard, BlueFormMultiInput, BlueLoading, BlueSpacing10, BlueSpacing20, BlueTextCentered } from '../../BlueComponents'; import {
BlueCard,
BlueFormMultiInput,
BlueLoading,
BlueSpacing10,
BlueSpacing20,
BlueSpacing40,
BlueTextCentered,
} from '../../BlueComponents';
import { HDSegwitBech32Wallet, MultisigCosigner, MultisigHDWallet } from '../../class'; import { HDSegwitBech32Wallet, MultisigCosigner, MultisigHDWallet } from '../../class';
import presentAlert from '../../components/Alert'; import presentAlert from '../../components/Alert';
import BottomModal, { BottomModalHandle } from '../../components/BottomModal'; import BottomModal, { BottomModalHandle } from '../../components/BottomModal';
@ -40,14 +48,14 @@ import { useStorage } from '../../hooks/context/useStorage';
import ToolTipMenu from '../../components/TooltipMenu'; import ToolTipMenu from '../../components/TooltipMenu';
import { CommonToolTipActions } from '../../typings/CommonToolTipActions'; import { CommonToolTipActions } from '../../typings/CommonToolTipActions';
import { useSettings } from '../../hooks/context/useSettings'; import { useSettings } from '../../hooks/context/useSettings';
import { ViewEditMultisigCosignersStackParamList } from '../../navigation/ViewEditMultisigCosignersStack';
import { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import SafeArea from '../../components/SafeArea'; import SafeArea from '../../components/SafeArea';
import { TWallet } from '../../class/wallets/types'; import { TWallet } from '../../class/wallets/types';
import { AddressInputScanButton } from '../../components/AddressInputScanButton'; import { AddressInputScanButton } from '../../components/AddressInputScanButton';
import { DetailViewStackParamList } from '../../navigation/DetailViewStackParamList';
type RouteParams = RouteProp<ViewEditMultisigCosignersStackParamList, 'ViewEditMultisigCosigners'>; type RouteParams = RouteProp<DetailViewStackParamList, 'ViewEditMultisigCosigners'>;
type NavigationProp = NativeStackNavigationProp<ViewEditMultisigCosignersStackParamList, 'ViewEditMultisigCosigners'>; type NavigationProp = NativeStackNavigationProp<DetailViewStackParamList, 'ViewEditMultisigCosigners'>;
const ViewEditMultisigCosigners: React.FC = () => { const ViewEditMultisigCosigners: React.FC = () => {
const hasLoaded = useRef(false); const hasLoaded = useRef(false);
@ -169,9 +177,11 @@ const ViewEditMultisigCosigners: React.FC = () => {
setIsSaveButtonDisabled(true); setIsSaveButtonDisabled(true);
setWalletsWithNewOrder(newWallets); setWalletsWithNewOrder(newWallets);
setTimeout(() => { setTimeout(() => {
dispatch( const popTo = StackActions.popTo('WalletTransactions', {
CommonActions.navigate({ name: 'WalletTransactions', params: { walletID: wallet.getID(), walletType: MultisigHDWallet.type } }), walletID,
); walletType: wallet.type,
});
dispatch(popTo);
}, 500); }, 500);
}, 100); }, 100);
}; };
@ -560,6 +570,7 @@ const ViewEditMultisigCosigners: React.FC = () => {
{!isLoading && ( {!isLoading && (
<> <>
<BlueSpacing40 />
<AddressInputScanButton <AddressInputScanButton
beforePress={async () => { beforePress={async () => {
await provideMnemonicsModalRef.current?.dismiss(); await provideMnemonicsModalRef.current?.dismiss();
@ -568,7 +579,7 @@ const ViewEditMultisigCosigners: React.FC = () => {
type="link" type="link"
onChangeText={setImportText} onChangeText={setImportText}
/> />
<BlueSpacing20 /> <BlueSpacing40 />
</> </>
)} )}
</> </>

View file

@ -37,7 +37,7 @@ import { useExtendedNavigation } from '../../hooks/useExtendedNavigation';
import loc, { formatBalanceWithoutSuffix } from '../../loc'; import loc, { formatBalanceWithoutSuffix } from '../../loc';
import { BitcoinUnit, Chain } from '../../models/bitcoinUnits'; import { BitcoinUnit, Chain } from '../../models/bitcoinUnits';
import { useStorage } from '../../hooks/context/useStorage'; import { useStorage } from '../../hooks/context/useStorage';
import { useFocusEffect, useRoute, RouteProp, usePreventRemove } from '@react-navigation/native'; import { useFocusEffect, useRoute, RouteProp, usePreventRemove, CommonActions } from '@react-navigation/native';
import { LightningTransaction, Transaction, TWallet } from '../../class/wallets/types'; import { LightningTransaction, Transaction, TWallet } from '../../class/wallets/types';
import { DetailViewStackParamList } from '../../navigation/DetailViewStackParamList'; import { DetailViewStackParamList } from '../../navigation/DetailViewStackParamList';
import HeaderMenuButton from '../../components/HeaderMenuButton'; import HeaderMenuButton from '../../components/HeaderMenuButton';
@ -66,7 +66,7 @@ const WalletDetails: React.FC = () => {
const [hideTransactionsInWalletsList, setHideTransactionsInWalletsList] = useState<boolean>( const [hideTransactionsInWalletsList, setHideTransactionsInWalletsList] = useState<boolean>(
wallet.getHideTransactionsInWalletsList ? !wallet.getHideTransactionsInWalletsList() : true, wallet.getHideTransactionsInWalletsList ? !wallet.getHideTransactionsInWalletsList() : true,
); );
const { setOptions, navigate } = useExtendedNavigation(); const { setOptions, navigate, dispatch } = useExtendedNavigation();
const { colors } = useTheme(); const { colors } = useTheme();
const [walletName, setWalletName] = useState<string>(wallet.getLabel()); const [walletName, setWalletName] = useState<string>(wallet.getLabel());
@ -305,13 +305,11 @@ const WalletDetails: React.FC = () => {
}); });
}; };
const navigateToViewEditCosigners = () => { const navigateToViewEditCosigners = () => {
navigate('ViewEditMultisigCosignersRoot', { navigate('ViewEditMultisigCosigners', {
screen: 'ViewEditMultisigCosigners', walletID,
params: {
walletID,
},
}); });
}; };
const navigateToXPub = () => const navigateToXPub = () =>
navigate('WalletXpubRoot', { navigate('WalletXpubRoot', {
screen: 'WalletXpub', screen: 'WalletXpub',
@ -395,6 +393,28 @@ const WalletDetails: React.FC = () => {
wallet._hdWalletInstance._lastTxFetch = 0; wallet._hdWalletInstance._lastTxFetch = 0;
// @ts-expect-error: Need to fix later // @ts-expect-error: Need to fix later
wallet._hdWalletInstance._lastBalanceFetch = 0; wallet._hdWalletInstance._lastBalanceFetch = 0;
// Find the WalletTransactions screen in the navigation state and reset just that screen.
// It can be multiple WalletTransactions screen.
dispatch(state => {
// Find the route that contains 'WalletTransactions' in the navigation stack
const routes = state.routes.map(route => {
if (route.name === 'WalletTransactions' && (route.params as { walletID: string })?.walletID === walletID) {
// Reset this specific route with the same params to force a refresh
return {
...route,
key: `WalletTransactions-${walletID}-${Date.now()}`, // Force new key to ensure fresh mount
};
}
return route;
});
return CommonActions.reset({
...state,
routes,
index: state.index,
});
});
presentAlert({ message: msg }); presentAlert({ message: msg });
} }
}; };

View file

@ -5,16 +5,15 @@ import {
Alert, Alert,
Dimensions, Dimensions,
findNodeHandle, findNodeHandle,
FlatList,
I18nManager, I18nManager,
InteractionManager, InteractionManager,
LayoutAnimation, LayoutAnimation,
PixelRatio, PixelRatio,
ScrollView,
StyleSheet, StyleSheet,
Text, Text,
View, View,
RefreshControl, Animated,
LayoutChangeEvent,
} from 'react-native'; } from 'react-native';
import { Icon } from '@rneui/themed'; import { Icon } from '@rneui/themed';
import * as BlueElectrum from '../../blue_modules/BlueElectrum'; import * as BlueElectrum from '../../blue_modules/BlueElectrum';
@ -38,7 +37,6 @@ import { NativeStackScreenProps } from '@react-navigation/native-stack';
import { DetailViewStackParamList } from '../../navigation/DetailViewStackParamList'; import { DetailViewStackParamList } from '../../navigation/DetailViewStackParamList';
import { Transaction, TWallet } from '../../class/wallets/types'; import { Transaction, TWallet } from '../../class/wallets/types';
import getWalletTransactionsOptions from '../../navigation/helpers/getWalletTransactionsOptions'; import getWalletTransactionsOptions from '../../navigation/helpers/getWalletTransactionsOptions';
import { presentWalletExportReminder } from '../../helpers/presentWalletExportReminder';
import selectWallet from '../../helpers/select-wallet'; import selectWallet from '../../helpers/select-wallet';
import assert from 'assert'; import assert from 'assert';
import useMenuElements from '../../hooks/useMenuElements'; import useMenuElements from '../../hooks/useMenuElements';
@ -46,7 +44,6 @@ import { useSettings } from '../../hooks/context/useSettings';
import { getClipboardContent } from '../../blue_modules/clipboard'; import { getClipboardContent } from '../../blue_modules/clipboard';
import HandOffComponent from '../../components/HandOffComponent'; import HandOffComponent from '../../components/HandOffComponent';
import { HandOffActivityType } from '../../components/types'; import { HandOffActivityType } from '../../components/types';
import WalletGradient from '../../class/wallet-gradient';
const buttonFontSize = const buttonFontSize =
PixelRatio.roundToNearestPixel(Dimensions.get('window').width / 26) > 22 PixelRatio.roundToNearestPixel(Dimensions.get('window').width / 26) > 22
@ -56,6 +53,7 @@ const buttonFontSize =
type WalletTransactionsProps = NativeStackScreenProps<DetailViewStackParamList, 'WalletTransactions'>; type WalletTransactionsProps = NativeStackScreenProps<DetailViewStackParamList, 'WalletTransactions'>;
type RouteProps = RouteProp<DetailViewStackParamList, 'WalletTransactions'>; type RouteProps = RouteProp<DetailViewStackParamList, 'WalletTransactions'>;
type TransactionListItem = Transaction & { type: 'transaction' | 'header' }; type TransactionListItem = Transaction & { type: 'transaction' | 'header' };
const WalletTransactions: React.FC<WalletTransactionsProps> = ({ route }) => { const WalletTransactions: React.FC<WalletTransactionsProps> = ({ route }) => {
const { wallets, saveToDisk, setSelectedWalletID } = useStorage(); const { wallets, saveToDisk, setSelectedWalletID } = useStorage();
const { setReloadTransactionsMenuActionFunction } = useMenuElements(); const { setReloadTransactionsMenuActionFunction } = useMenuElements();
@ -74,6 +72,14 @@ const WalletTransactions: React.FC<WalletTransactionsProps> = ({ route }) => {
const [lastFetchTimestamp, setLastFetchTimestamp] = useState(() => wallet?._lastTxFetch || 0); const [lastFetchTimestamp, setLastFetchTimestamp] = useState(() => wallet?._lastTxFetch || 0);
const [fetchFailures, setFetchFailures] = useState(0); const [fetchFailures, setFetchFailures] = useState(0);
const MAX_FAILURES = 3; const MAX_FAILURES = 3;
const scrollY = useRef(new Animated.Value(0)).current;
const [headerHeight, setHeaderHeight] = useState(0);
const headerTranslate = scrollY.interpolate({
inputRange: [0, headerHeight],
outputRange: [0, -headerHeight],
extrapolate: 'clamp',
});
const stylesHook = StyleSheet.create({ const stylesHook = StyleSheet.create({
listHeaderText: { listHeaderText: {
@ -256,11 +262,8 @@ const WalletTransactions: React.FC<WalletTransactionsProps> = ({ route }) => {
); );
const navigateToViewEditCosigners = useCallback(() => { const navigateToViewEditCosigners = useCallback(() => {
navigate('ViewEditMultisigCosignersRoot', { navigate('ViewEditMultisigCosigners', {
screen: 'ViewEditMultisigCosigners', walletID,
params: {
walletID,
},
}); });
}, [navigate, walletID]); }, [navigate, walletID]);
@ -280,9 +283,11 @@ const WalletTransactions: React.FC<WalletTransactionsProps> = ({ route }) => {
walletID, walletID,
}, },
}); });
} else if (wallet?.type === MultisigHDWallet.type) {
navigateToViewEditCosigners();
} }
}, },
[name, navigate, onWalletSelect, walletID, wallets], [name, navigate, navigateToViewEditCosigners, onWalletSelect, wallet?.type, walletID, wallets],
); );
const getItemLayout = (_: any, index: number) => ({ const getItemLayout = (_: any, index: number) => ({
@ -397,7 +402,8 @@ const WalletTransactions: React.FC<WalletTransactionsProps> = ({ route }) => {
console.debug('Next screen is focused, clearing reloadTransactionsMenuActionFunction'); console.debug('Next screen is focused, clearing reloadTransactionsMenuActionFunction');
setReloadTransactionsMenuActionFunction(() => {}); setReloadTransactionsMenuActionFunction(() => {});
}; };
}, [setReloadTransactionsMenuActionFunction, refreshTransactions]), // eslint-disable-next-line react-hooks/exhaustive-deps
}, []),
); );
const [balance, setBalance] = useState(wallet ? wallet.getBalance() : 0); const [balance, setBalance] = useState(wallet ? wallet.getBalance() : 0);
@ -418,8 +424,9 @@ const WalletTransactions: React.FC<WalletTransactionsProps> = ({ route }) => {
const handleScroll = useCallback( const handleScroll = useCallback(
(event: any) => { (event: any) => {
const offsetY = event.nativeEvent.contentOffset.y; const offsetY = event.nativeEvent.contentOffset.y;
const combinedHeight = 180; // Use the measured header height to determine when to show/hide the header title
if (offsetY < combinedHeight) { const threshold = headerHeight * 0.75;
if (offsetY < threshold) {
setOptions({ ...getWalletTransactionsOptions({ route }), headerTitle: undefined }); setOptions({ ...getWalletTransactionsOptions({ route }), headerTitle: undefined });
} else { } else {
navigation.setOptions({ navigation.setOptions({
@ -427,104 +434,82 @@ const WalletTransactions: React.FC<WalletTransactionsProps> = ({ route }) => {
}); });
} }
}, },
[navigation, wallet, walletBalance, setOptions, route], [navigation, wallet, walletBalance, setOptions, route, headerHeight],
); );
const ListHeaderComponent = useCallback( // Extracted named callbacks
() => const handleWalletUnitChange = useCallback(
wallet ? ( async (selectedUnit: any) => {
<> if (wallet) {
<TransactionsNavigationHeader wallet.preferredBalanceUnit = selectedUnit;
wallet={wallet} await saveToDisk();
onWalletUnitChange={async selectedUnit => { }
wallet.preferredBalanceUnit = selectedUnit; },
await saveToDisk(); [wallet, saveToDisk],
}}
unit={wallet.preferredBalanceUnit}
onWalletBalanceVisibilityChange={async isShouldBeVisible => {
const isBiometricsEnabled = await isBiometricUseCapableAndEnabled();
if (wallet.hideBalance && isBiometricsEnabled) {
const unlocked = await unlockWithBiometrics();
if (!unlocked) throw new Error('Biometrics failed');
}
wallet.hideBalance = isShouldBeVisible;
await saveToDisk();
}}
onManageFundsPressed={id => {
if (wallet.type === MultisigHDWallet.type) {
navigateToViewEditCosigners();
} else if (wallet.type === LightningCustodianWallet.type) {
if (wallet.getUserHasSavedExport()) {
if (!id) return;
onManageFundsPressed(id);
} else {
presentWalletExportReminder()
.then(async () => {
if (!id) return;
wallet.setUserHasSavedExport(true);
await saveToDisk();
onManageFundsPressed(id);
})
.catch(() => {
navigate('WalletExportRoot', {
screen: 'WalletExport',
params: {
walletID,
},
});
});
}
}
}}
/>
<>
<View style={[styles.flex, { backgroundColor: colors.background }]}>
<View style={styles.listHeaderTextRow}>
<Text style={[styles.listHeaderText, stylesHook.listHeaderText]}>{loc.transactions.list_title}</Text>
</View>
</View>
<View style={{ backgroundColor: colors.background }}>
{wallet.type === WatchOnlyWallet.type && wallet.isWatchOnlyWarningVisible && (
<WatchOnlyWarning
handleDismiss={() => {
wallet.isWatchOnlyWarningVisible = false;
LayoutAnimation.configureNext(LayoutAnimation.Presets.linear);
saveToDisk();
}}
/>
)}
</View>
</>
</>
) : undefined,
[
wallet,
colors.background,
stylesHook.listHeaderText,
saveToDisk,
isBiometricUseCapableAndEnabled,
navigateToViewEditCosigners,
onManageFundsPressed,
navigate,
walletID,
],
); );
const handleWalletBalanceVisibilityChange = useCallback(
async (isShouldBeVisible: boolean) => {
if (wallet) {
const isBiometricsEnabled = await isBiometricUseCapableAndEnabled();
if (wallet.hideBalance && isBiometricsEnabled) {
const unlocked = await unlockWithBiometrics();
if (!unlocked) throw new Error('Biometrics failed');
}
wallet.hideBalance = isShouldBeVisible;
await saveToDisk();
}
},
[wallet, saveToDisk, isBiometricUseCapableAndEnabled],
);
const handleHeaderLayout = useCallback((event: LayoutChangeEvent) => {
const { height } = event.nativeEvent.layout;
setHeaderHeight(height);
}, []);
const refreshProps =
!isDesktop && !isElectrumDisabled ? { onRefresh: refreshTransactions, progressViewOffset: headerHeight, refreshing: isLoading } : {};
const renderHeader = useCallback(() => {
return (
<View style={{ backgroundColor: colors.background }}>
<View style={styles.listHeaderTextRow}>
<Text style={[styles.listHeaderText, stylesHook.listHeaderText]}>{loc.transactions.list_title}</Text>
</View>
<View style={{ backgroundColor: colors.background }}>
{wallet?.type === WatchOnlyWallet.type && wallet.isWatchOnlyWarningVisible && (
<WatchOnlyWarning
handleDismiss={() => {
wallet.isWatchOnlyWarningVisible = false;
LayoutAnimation.configureNext(LayoutAnimation.Presets.linear);
saveToDisk();
}}
/>
)}
</View>
</View>
);
}, [colors.background, stylesHook.listHeaderText, wallet, saveToDisk]);
return ( return (
<View style={[styles.flex, { backgroundColor: colors.background }]}> <Animated.View style={styles.container}>
{/* The color of the refresh indicator. Temporary hack */} <Animated.View style={[styles.stickyHeader, { transform: [{ translateY: headerTranslate }] }]} onLayout={handleHeaderLayout}>
<View {wallet ? (
style={[ <TransactionsNavigationHeader
styles.refreshIndicatorBackground, wallet={wallet}
{ backgroundColor: wallet ? WalletGradient.headerColorFor(wallet.type) : colors.background }, onWalletUnitChange={handleWalletUnitChange}
]} unit={wallet.preferredBalanceUnit}
testID="TransactionsListView" onWalletBalanceVisibilityChange={handleWalletBalanceVisibilityChange}
/> onManageFundsPressed={onManageFundsPressed}
/>
<FlatList<Transaction> ) : null}
</Animated.View>
<Animated.FlatList<Transaction>
getItemLayout={getItemLayout} getItemLayout={getItemLayout}
updateCellsBatchingPeriod={50} updateCellsBatchingPeriod={50}
onEndReachedThreshold={0.3} onEndReachedThreshold={0.3}
ListHeaderComponent={renderHeader}
onEndReached={loadMoreTransactions} onEndReached={loadMoreTransactions}
ListFooterComponent={renderListFooterComponent} ListFooterComponent={renderListFooterComponent}
data={getTransactions(limit)} data={getTransactions(limit)}
@ -532,26 +517,22 @@ const WalletTransactions: React.FC<WalletTransactionsProps> = ({ route }) => {
keyExtractor={_keyExtractor} keyExtractor={_keyExtractor}
renderItem={renderItem} renderItem={renderItem}
initialNumToRender={10} initialNumToRender={10}
style={{ marginTop: headerHeight }}
removeClippedSubviews removeClippedSubviews
contentContainerStyle={{ backgroundColor: colors.background }} contentContainerStyle={{ backgroundColor: colors.background }}
contentInset={{ top: 0, left: 0, bottom: 90, right: 0 }} contentInset={{ top: 0, left: 0, bottom: 90, right: 0 }}
maxToRenderPerBatch={10} maxToRenderPerBatch={10}
testID="TransactionsListView"
onScroll={handleScroll} onScroll={handleScroll}
scrollEventThrottle={16} scrollEventThrottle={16}
stickyHeaderHiddenOnScroll {...refreshProps}
ListHeaderComponent={ListHeaderComponent}
ListEmptyComponent={ ListEmptyComponent={
<ScrollView style={[styles.flex, { backgroundColor: colors.background }]} contentContainerStyle={styles.scrollViewContent}> <View style={[styles.flex, { backgroundColor: colors.background }]} testID="TransactionsListEmpty">
<Text numberOfLines={0} style={styles.emptyTxs} testID="TransactionsListEmpty"> <Text numberOfLines={0} style={styles.emptyTxs}>
{(isLightning() && loc.wallets.list_empty_txs1_lightning) || loc.wallets.list_empty_txs1} {(isLightning() && loc.wallets.list_empty_txs1_lightning) || loc.wallets.list_empty_txs1}
</Text> </Text>
{isLightning() && <Text style={styles.emptyTxsLightning}>{loc.wallets.list_empty_txs2_lightning}</Text>} {isLightning() && <Text style={styles.emptyTxsLightning}>{loc.wallets.list_empty_txs2_lightning}</Text>}
</ScrollView> </View>
}
refreshControl={
!isDesktop && !isElectrumDisabled ? (
<RefreshControl refreshing={isLoading} onRefresh={() => refreshTransactions(true)} tintColor={colors.msSuccessCheck} />
) : undefined
} }
windowSize={15} windowSize={15}
maintainVisibleContentPosition={{ maintainVisibleContentPosition={{
@ -598,27 +579,27 @@ const WalletTransactions: React.FC<WalletTransactionsProps> = ({ route }) => {
url={`https://www.blockonomics.co/#/search?q=${wallet.getXpub()}`} url={`https://www.blockonomics.co/#/search?q=${wallet.getXpub()}`}
/> />
) : null} ) : null}
</View> </Animated.View>
); );
}; };
export default WalletTransactions; export default WalletTransactions;
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { flex: 1 },
flex: { flex: 1 }, flex: { flex: 1 },
scrollViewContent: { flex: 1, justifyContent: 'center', paddingHorizontal: 16, paddingBottom: 500 },
activityIndicator: { marginVertical: 20 }, activityIndicator: { marginVertical: 20 },
listHeaderTextRow: { flex: 1, margin: 16, flexDirection: 'row', justifyContent: 'space-between' }, listHeaderTextRow: { padding: 16, flexDirection: 'row' },
listHeaderText: { marginTop: 8, marginBottom: 8, fontWeight: 'bold', fontSize: 24 }, listHeaderText: { fontWeight: 'bold', fontSize: 24 },
refreshIndicatorBackground: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: 140,
},
emptyTxs: { fontSize: 18, color: '#9aa0aa', textAlign: 'center', marginVertical: 16 }, emptyTxs: { fontSize: 18, color: '#9aa0aa', textAlign: 'center', marginVertical: 16 },
emptyTxsLightning: { fontSize: 18, color: '#9aa0aa', textAlign: 'center', fontWeight: '600' }, emptyTxsLightning: { fontSize: 18, color: '#9aa0aa', textAlign: 'center', fontWeight: '600' },
sendIcon: { transform: [{ rotate: I18nManager.isRTL ? '-225deg' : '225deg' }] }, sendIcon: { transform: [{ rotate: I18nManager.isRTL ? '-225deg' : '225deg' }] },
receiveIcon: { transform: [{ rotate: I18nManager.isRTL ? '45deg' : '-45deg' }] }, receiveIcon: { transform: [{ rotate: I18nManager.isRTL ? '45deg' : '-45deg' }] },
stickyHeader: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
zIndex: 1,
},
}); });

View file

@ -758,7 +758,7 @@ describe('BlueWallet UI Tests - import BIP84 wallet', () => {
await waitForId('TransactionsListEmpty'); await waitForId('TransactionsListEmpty');
assert.strictEqual(await countElements('TransactionListItem'), 0); assert.strictEqual(await countElements('TransactionListItem'), 0);
await element(by.id('TransactionsListView')).swipe('down', 'slow'); // pul-to-refresh await element(by.id('TransactionsListView')).swipe('down', 'slow', 0.5, 0.3); // pul-to-refresh
// asserting balance and txs loaded: // asserting balance and txs loaded:
await waitForText('0.00105526'); // the wait inside allows network request to propagate await waitForText('0.00105526'); // the wait inside allows network request to propagate