BlueWallet/screen/wallets/list.js

556 lines
16 KiB
JavaScript
Raw Normal View History

import React, { useCallback, useContext, useEffect, useRef, useState } from 'react';
import {
StatusBar,
View,
TouchableOpacity,
Text,
StyleSheet,
SectionList,
Alert,
Platform,
2020-09-07 19:46:37 +02:00
Image,
Dimensions,
useWindowDimensions,
2021-01-05 02:44:28 +01:00
SafeAreaView,
} from 'react-native';
2020-10-15 18:25:24 +02:00
import { BlueHeaderDefaultMain, BlueTransactionListItem } from '../../BlueComponents';
import WalletsCarousel from '../../components/WalletsCarousel';
import { Icon } from 'react-native-elements';
2020-05-20 20:04:28 +02:00
import DeeplinkSchemaMatch from '../../class/deeplink-schema-match';
import ReactNativeHapticFeedback from 'react-native-haptic-feedback';
import { PlaceholderWallet } from '../../class';
import WalletImport from '../../class/wallet-import';
import ActionSheet from '../ActionSheet';
import Clipboard from '@react-native-community/clipboard';
2020-07-20 15:38:46 +02:00
import loc from '../../loc';
2020-09-07 19:46:37 +02:00
import { FContainer, FButton } from '../../components/FloatButtons';
import { isTablet } from 'react-native-device-info';
import { useFocusEffect, useNavigation, useRoute, useTheme } from '@react-navigation/native';
import { BlueStorageContext } from '../../blue_modules/storage-context';
import { isCatalyst, isMacCatalina } from '../../blue_modules/environment';
2020-12-27 01:10:54 +01:00
const A = require('../../blue_modules/analytics');
2020-12-15 05:11:05 +01:00
const fs = require('../../blue_modules/fs');
2020-05-17 03:41:38 +02:00
const WalletsListSections = { CAROUSEL: 'CAROUSEL', LOCALTRADER: 'LOCALTRADER', TRANSACTIONS: 'TRANSACTIONS' };
const WalletsList = () => {
const walletsCarousel = useRef();
const { wallets, pendingWallets, getTransactions, getBalance, refreshAllWalletTransactions, setSelectedWallet } = useContext(
BlueStorageContext,
);
const { width } = useWindowDimensions();
const { colors, scanImage } = useTheme();
2020-10-11 09:07:22 +02:00
const { navigate, setOptions } = useNavigation();
const routeName = useRoute().name;
const [isLoading, setIsLoading] = useState(false);
const [itemWidth, setItemWidth] = useState(width * 0.82 > 375 ? 375 : width * 0.82);
const [isLargeScreen, setIsLargeScreen] = useState(
Platform.OS === 'android' ? isTablet() : width >= Dimensions.get('screen').width / 3 && isTablet(),
);
const [carouselData, setCarouselData] = useState([]);
const dataSource = getTransactions(null, 10);
const walletsCount = useRef(wallets.length);
const stylesHook = StyleSheet.create({
walletsListWrapper: {
backgroundColor: colors.brandingColor,
},
listHeaderBack: {
backgroundColor: colors.background,
},
listHeaderText: {
color: colors.foregroundColor,
},
ltRoot: {
backgroundColor: colors.ballOutgoingExpired,
},
ltTextBig: {
color: colors.foregroundColor,
},
ltTextSmall: {
color: colors.alternativeTextColor,
},
});
useFocusEffect(
useCallback(() => {
verifyBalance();
setSelectedWallet('');
2020-11-17 02:48:49 +01:00
StatusBar.setBarStyle('default');
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []),
);
2018-01-30 23:42:38 +01:00
useEffect(() => {
const allWallets = wallets.concat(pendingWallets);
const newCarouselData = allWallets.concat(false);
setCarouselData(newCarouselData);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [wallets, pendingWallets]);
useEffect(() => {
if (pendingWallets.length > 0) {
walletsCarousel.current?.snapToItem(carouselData.length - pendingWallets.length);
} else {
if (walletsCount.current <= wallets.length) {
walletsCarousel.current?.snapToItem(walletsCount.current - 1);
walletsCount.current = wallets.length;
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [pendingWallets]);
const verifyBalance = () => {
if (getBalance() !== 0) {
A(A.ENUM.GOT_NONZERO_BALANCE);
} else {
A(A.ENUM.GOT_ZERO_BALANCE);
}
};
2020-10-11 09:07:22 +02:00
useEffect(() => {
setOptions({
title: '',
2020-12-27 01:10:54 +01:00
headerShown: !isCatalyst,
2020-10-11 09:07:22 +02:00
headerStyle: {
backgroundColor: colors.customHeader,
borderBottomWidth: 0,
elevation: 0,
shadowOpacity: 0,
shadowOffset: { height: 0, width: 0 },
},
headerRight: () => (
<TouchableOpacity testID="SettingsButton" style={styles.headerTouch} onPress={navigateToSettings}>
<Icon size={22} name="kebab-horizontal" type="octicon" color={colors.foregroundColor} />
</TouchableOpacity>
),
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [colors]);
const navigateToSettings = () => {
navigate('Settings');
};
2018-07-02 13:09:34 +02:00
/**
* Forcefully fetches TXs and balance for ALL wallets.
* Triggered manually by user on pull-to-refresh.
2018-07-02 13:09:34 +02:00
*/
2021-01-28 01:30:42 +01:00
const refreshTransactions = (showLoadingIndicator = true, showUpdateStatusIndicator = false) => {
setIsLoading(showLoadingIndicator);
2021-01-28 01:30:42 +01:00
refreshAllWalletTransactions(showLoadingIndicator, showUpdateStatusIndicator).finally(() => setIsLoading(false));
};
2018-06-25 00:22:46 +02:00
useEffect(() => {
2021-01-28 01:30:42 +01:00
refreshTransactions(false, true);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []); // call refreshTransactions() only once, when screen mounts
const handleClick = index => {
console.log('click', index);
const wallet = carouselData[index];
2018-06-25 00:22:46 +02:00
if (wallet) {
2019-12-27 03:21:07 +01:00
if (wallet.type === PlaceholderWallet.type) {
Alert.alert(
2020-07-20 15:38:46 +02:00
loc.wallets.add_details,
loc.wallets.list_import_problem,
2019-12-27 03:21:07 +01:00
[
{
2020-07-20 15:38:46 +02:00
text: loc.wallets.details_delete,
2019-12-27 03:21:07 +01:00
onPress: () => {
WalletImport.removePlaceholderWallet();
},
style: 'destructive',
},
{
2020-07-20 15:38:46 +02:00
text: loc.wallets.list_tryagain,
2019-12-27 03:21:07 +01:00
onPress: () => {
navigate('AddWalletRoot', { screen: 'ImportWallet', params: { label: wallet.getSecret() } });
2019-12-27 03:21:07 +01:00
WalletImport.removePlaceholderWallet();
},
style: 'default',
},
],
{ cancelable: false },
);
} else {
const walletID = wallet.getID();
navigate('WalletTransactions', {
walletID,
walletType: wallet.type,
key: `WalletTransactions-${walletID}`,
2019-12-27 03:21:07 +01:00
});
}
2018-06-25 00:22:46 +02:00
} else {
// if its out of index - this must be last card with incentive to create wallet
navigate('AddWalletRoot');
2018-06-25 00:22:46 +02:00
}
2020-05-17 14:17:08 +02:00
};
2018-06-25 00:22:46 +02:00
const onSnapToItem = index => {
2018-06-25 00:22:46 +02:00
console.log('onSnapToItem', index);
if (wallets[index] && (wallets[index].timeToRefreshBalance() || wallets[index].timeToRefreshTransaction())) {
console.log(wallets[index].getLabel(), 'thinks its time to refresh either balance or transactions. refetching both');
2021-01-28 01:30:42 +01:00
refreshAllWalletTransactions(index, false).finally(() => setIsLoading(false));
}
};
2018-09-18 09:24:42 +02:00
const renderListHeaderComponent = () => {
const style = { opacity: isLoading ? 1.0 : 0.5 };
return (
<View style={[styles.listHeaderBack, stylesHook.listHeaderBack]}>
<Text textBreakStrategy="simple" style={[styles.listHeaderText, stylesHook.listHeaderText]}>
{`${loc.transactions.list_title}${' '}`}
</Text>
{isCatalyst && (
2020-12-03 16:11:59 +01:00
<TouchableOpacity style={style} onPress={() => refreshTransactions(true)} disabled={isLoading}>
<Icon name="refresh" type="font-awesome" color={colors.feeText} />
2020-08-21 00:50:38 +02:00
</TouchableOpacity>
)}
</View>
);
};
const handleLongPress = () => {
if (carouselData.length > 1 && !carouselData.some(wallet => wallet.type === PlaceholderWallet.type)) {
navigate('ReorderWallets');
} else {
2019-05-03 14:36:11 +02:00
ReactNativeHapticFeedback.trigger('notificationError', { ignoreAndroidSystemSettings: false });
}
};
const renderTransactionListsRow = data => {
return (
<View style={styles.transaction}>
<BlueTransactionListItem item={data.item} itemPriceUnit={data.item.walletPreferredBalanceUnit} />
</View>
);
2019-02-17 02:22:14 +01:00
};
2019-12-28 01:53:34 +01:00
const renderLocalTrader = () => {
if (carouselData.every(wallet => wallet === false)) return null;
if (carouselData.length > 0 && !carouselData.some(wallet => wallet.type === PlaceholderWallet.type)) {
2021-01-05 02:44:28 +01:00
const button = (
2020-04-15 23:10:24 +02:00
<TouchableOpacity
onPress={() => {
navigate('HodlHodl', { screen: 'HodlHodl' });
2020-04-15 23:10:24 +02:00
}}
style={[styles.ltRoot, stylesHook.ltRoot]}
2020-04-15 23:10:24 +02:00
>
<View style={styles.ltTextWrap}>
2020-12-21 19:27:49 +01:00
<Text style={[styles.ltTextBig, stylesHook.ltTextBig]}>{loc.hodl.local_trader}</Text>
<Text style={[styles.ltTextSmall, stylesHook.ltTextSmall]}>{loc.hodl.p2p}</Text>
2020-04-15 23:10:24 +02:00
</View>
</TouchableOpacity>
);
2021-01-05 02:44:28 +01:00
return isLargeScreen ? <SafeAreaView>{button}</SafeAreaView> : button;
2020-05-17 03:41:38 +02:00
} else {
return null;
2020-04-15 23:10:24 +02:00
}
};
const renderWalletsCarousel = () => {
2020-05-17 03:41:38 +02:00
return (
<WalletsCarousel
removeClippedSubviews={false}
data={carouselData}
onPress={handleClick}
handleLongPress={handleLongPress}
onSnapToItem={onSnapToItem}
ref={walletsCarousel}
2020-05-21 17:36:46 +02:00
testID="WalletsList"
sliderWidth={width}
itemWidth={itemWidth}
2020-05-17 03:41:38 +02:00
/>
);
};
const renderSectionItem = item => {
2020-05-17 03:41:38 +02:00
switch (item.section.key) {
case WalletsListSections.CAROUSEL:
return isLargeScreen ? null : renderWalletsCarousel();
2020-05-17 03:41:38 +02:00
case WalletsListSections.LOCALTRADER:
return renderLocalTrader();
2020-05-17 03:41:38 +02:00
case WalletsListSections.TRANSACTIONS:
return renderTransactionListsRow(item);
2020-05-17 03:41:38 +02:00
default:
return null;
}
};
const renderSectionHeader = section => {
switch (section.section.key) {
2020-05-17 03:41:38 +02:00
case WalletsListSections.CAROUSEL:
return isLargeScreen ? null : (
2020-05-17 03:41:38 +02:00
<BlueHeaderDefaultMain
2020-07-20 15:38:46 +02:00
leftText={loc.wallets.list_title}
onNewWalletPress={!carouselData.some(wallet => wallet.type === PlaceholderWallet.type) ? () => navigate('AddWalletRoot') : null}
2020-05-17 03:41:38 +02:00
/>
);
case WalletsListSections.TRANSACTIONS:
return renderListHeaderComponent();
2020-05-17 03:41:38 +02:00
default:
return null;
2018-01-30 23:42:38 +01:00
}
2020-05-17 03:41:38 +02:00
};
const renderSectionFooter = section => {
switch (section.section.key) {
2020-05-17 14:17:08 +02:00
case WalletsListSections.TRANSACTIONS:
if (dataSource.length === 0 && !isLoading) {
2020-05-17 14:17:08 +02:00
return (
<View style={styles.footerRoot}>
2020-07-20 15:38:46 +02:00
<Text style={styles.footerEmpty}>{loc.wallets.list_empty_txs1}</Text>
<Text style={styles.footerStart}>{loc.wallets.list_empty_txs2}</Text>
2020-05-17 14:17:08 +02:00
</View>
);
} else {
return null;
}
default:
return null;
}
};
const renderScanButton = () => {
if (carouselData.length > 0 && !carouselData.some(wallet => wallet.type === PlaceholderWallet.type)) {
return (
2020-09-07 19:46:37 +02:00
<FContainer>
<FButton
onPress={onScanButtonPressed}
onLongPress={isMacCatalina ? undefined : sendButtonLongPress}
icon={<Image resizeMode="stretch" source={scanImage} />}
2020-09-07 19:46:37 +02:00
text={loc.send.details_scan}
/>
</FContainer>
);
} else {
return null;
}
};
const sectionListKeyExtractor = (item, index) => {
2020-05-17 03:41:38 +02:00
return `${item}${index}}`;
};
const onScanButtonPressed = () => {
if (isMacCatalina) {
2020-12-15 05:11:05 +01:00
fs.showActionSheet().then(onBarScanned);
} else {
navigate('ScanQRCodeRoot', {
screen: 'ScanQRCode',
params: {
launchedBy: routeName,
onBarScanned,
showFileImportButton: false,
},
});
}
2020-05-20 20:04:28 +02:00
};
const onBarScanned = value => {
2020-05-20 20:04:28 +02:00
DeeplinkSchemaMatch.navigationRouteFor({ url: value }, completionValue => {
ReactNativeHapticFeedback.trigger('impactLight', { ignoreAndroidSystemSettings: false });
navigate(...completionValue);
2020-05-20 20:04:28 +02:00
});
};
const copyFromClipboard = async () => {
onBarScanned(await Clipboard.getString());
};
const sendButtonLongPress = async () => {
const isClipboardEmpty = (await Clipboard.getString()).replace(' ', '').length === 0;
if (Platform.OS === 'ios') {
if (isMacCatalina) {
fs.showActionSheet().then(onBarScanned);
} else {
2020-12-20 08:03:22 +01:00
const options = [loc._.cancel, loc.wallets.list_long_choose, loc.wallets.list_long_scan];
if (!isClipboardEmpty) {
options.push(loc.wallets.list_long_clipboard);
}
ActionSheet.showActionSheetWithOptions({ options, cancelButtonIndex: 0 }, buttonIndex => {
if (buttonIndex === 1) {
fs.showImagePickerAndReadImage().then(onBarScanned);
} else if (buttonIndex === 2) {
navigate('ScanQRCodeRoot', {
screen: 'ScanQRCode',
params: {
launchedBy: routeName,
onBarScanned,
showFileImportButton: false,
},
});
} else if (buttonIndex === 3) {
copyFromClipboard();
}
});
}
} else if (Platform.OS === 'android') {
const buttons = [
{
2020-07-20 15:38:46 +02:00
text: loc._.cancel,
onPress: () => {},
style: 'cancel',
},
{
2020-07-20 15:38:46 +02:00
text: loc.wallets.list_long_choose,
onPress: () => fs.showActionSheet().then(onBarScanned),
},
{
2020-07-20 15:38:46 +02:00
text: loc.wallets.list_long_scan,
onPress: () =>
navigate('ScanQRCodeRoot', {
2020-05-30 07:30:43 +02:00
screen: 'ScanQRCode',
params: {
launchedBy: routeName,
onBarScanned,
2020-05-30 07:30:43 +02:00
showFileImportButton: false,
},
}),
},
];
if (!isClipboardEmpty) {
buttons.push({
2020-07-20 15:38:46 +02:00
text: loc.wallets.list_long_clipboard,
onPress: copyFromClipboard,
});
}
ActionSheet.showActionSheetWithOptions({
title: '',
message: '',
buttons,
});
}
};
const onLayout = _e => {
setIsLargeScreen(Platform.OS === 'android' ? isTablet() : width >= Dimensions.get('screen').width / 3 && isTablet());
setItemWidth(width * 0.82 > 375 ? 375 : width * 0.82);
};
2021-01-28 01:30:42 +01:00
const onRefresh = () => {
refreshTransactions(true, false);
};
return (
2021-01-05 02:44:28 +01:00
<View style={styles.root} onLayout={onLayout}>
<StatusBar barStyle="default" />
2021-01-05 02:44:28 +01:00
<View style={[styles.walletsListWrapper, stylesHook.walletsListWrapper]}>
<SectionList
2021-01-28 01:30:42 +01:00
onRefresh={onRefresh}
2021-01-05 02:44:28 +01:00
refreshing={isLoading}
renderItem={renderSectionItem}
keyExtractor={sectionListKeyExtractor}
renderSectionHeader={renderSectionHeader}
initialNumToRender={20}
contentInset={styles.scrollContent}
renderSectionFooter={renderSectionFooter}
sections={[
{ key: WalletsListSections.CAROUSEL, data: [WalletsListSections.CAROUSEL] },
{ key: WalletsListSections.LOCALTRADER, data: [WalletsListSections.LOCALTRADER] },
{ key: WalletsListSections.TRANSACTIONS, data: dataSource },
]}
/>
{renderScanButton()}
</View>
</View>
);
};
export default WalletsList;
2019-12-28 01:53:34 +01:00
const styles = StyleSheet.create({
2021-01-05 02:44:28 +01:00
root: {
flex: 1,
},
scrollContent: {
top: 0,
left: 0,
bottom: 60,
right: 0,
},
wrapper: {
flex: 1,
},
walletsListWrapper: {
flex: 1,
},
headerStyle: {
...Platform.select({
ios: {
marginTop: 44,
height: 32,
alignItems: 'flex-end',
justifyContent: 'center',
},
android: {
marginTop: 8,
height: 44,
alignItems: 'flex-end',
justifyContent: 'center',
},
}),
},
headerTouch: {
height: 48,
paddingRight: 16,
paddingLeft: 32,
paddingVertical: 10,
},
listHeaderBack: {
2020-08-21 00:50:38 +02:00
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginHorizontal: 16,
},
listHeaderText: {
fontWeight: 'bold',
fontSize: 24,
marginVertical: 8,
},
ltRoot: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginHorizontal: 16,
marginVertical: 16,
padding: 16,
borderRadius: 6,
},
ltTextWrap: {
flexDirection: 'column',
},
ltTextBig: {
fontSize: 16,
fontWeight: '600',
},
ltTextSmall: {
fontSize: 13,
fontWeight: '500',
},
footerRoot: {
top: 80,
height: 160,
marginBottom: 80,
},
footerEmpty: {
fontSize: 18,
color: '#9aa0aa',
textAlign: 'center',
},
footerStart: {
fontSize: 18,
color: '#9aa0aa',
textAlign: 'center',
fontWeight: '600',
},
listHeader: {
backgroundColor: '#FFFFFF',
},
transaction: {
marginHorizontal: 0,
},
});