BlueWallet/screen/wallets/hodlHodlMyContracts.js

468 lines
15 KiB
JavaScript
Raw Normal View History

2020-06-15 20:47:54 +02:00
/* global alert */
import React, { Component } from 'react';
2020-12-25 17:09:53 +01:00
import PropTypes from 'prop-types';
2020-06-15 20:47:54 +02:00
import {
Alert,
FlatList,
Keyboard,
KeyboardAvoidingView,
Linking,
Platform,
StyleSheet,
Text,
TouchableHighlight,
2020-07-31 19:01:04 +02:00
TouchableOpacity,
2020-06-15 20:47:54 +02:00
View,
} from 'react-native';
2020-11-17 09:43:38 +01:00
2020-12-25 17:09:53 +01:00
import { BlueButton, BlueCopyTextToClipboard, BlueLoading, BlueSpacing10, BlueSpacing20, BlueText } from '../../BlueComponents';
import navigationStyle from '../../components/navigationStyle';
2020-06-15 20:47:54 +02:00
import { HodlHodlApi } from '../../class/hodl-hodl-api';
import * as NavigationService from '../../NavigationService';
2020-07-15 19:32:59 +02:00
import { BlueCurrentTheme } from '../../components/themes';
2020-11-17 09:43:38 +01:00
import BottomModal from '../../components/BottomModal';
2020-07-20 15:38:46 +02:00
import loc from '../../loc';
import { BlueStorageContext } from '../../blue_modules/storage-context';
2020-06-15 20:47:54 +02:00
export default class HodlHodlMyContracts extends Component {
static contextType = BlueStorageContext;
2020-06-15 20:47:54 +02:00
constructor(props) {
super(props);
2020-12-25 17:09:53 +01:00
props.navigation.setParams({ handleLogout: this.handleLogout });
2020-06-15 20:47:54 +02:00
this.state = {
contracts: [],
isLoading: true,
};
}
componentWillUnmount() {
clearInterval(this.state.inverval);
}
2020-12-25 17:09:53 +01:00
handleLogout = () => {
this.context.setHodlHodlApiKey('', '<empty>');
this.props.navigation.navigate('WalletsList');
};
2020-06-15 20:47:54 +02:00
async componentDidMount() {
const hodlApiKey = await this.context.getHodlHodlApiKey();
2020-06-15 20:47:54 +02:00
const hodlApi = new HodlHodlApi(hodlApiKey);
this.setState({ hodlApi: hodlApi, contracts: [] });
const inverval = setInterval(async () => {
await this.refetchContracts();
}, 60 * 1000);
this.setState({ inverval });
await this.refetchContracts();
}
render() {
if (this.state.isLoading) return <BlueLoading />;
return (
2020-07-15 19:32:59 +02:00
<View style={styles.root}>
2020-06-15 20:47:54 +02:00
<FlatList
scrollEnabled={false}
keyExtractor={(item, index) => {
return item.id;
}}
2020-07-20 15:38:46 +02:00
ListEmptyComponent={() => <Text style={styles.emptyComponentText}>{loc.hodl.cont_no}</Text>}
2020-06-15 20:47:54 +02:00
style={styles.flatList}
ItemSeparatorComponent={() => <View style={styles.itemSeparatorComponent} />}
data={this.state.contracts}
renderItem={({ item: contract, index, separators }) => (
<TouchableHighlight
onShowUnderlay={separators.highlight}
onHideUnderlay={separators.unhighlight}
onPress={() => this._onContractPress(contract)}
>
<View style={styles.flexDirectionRow}>
<View style={['paid', 'completed'].includes(contract.status) ? styles.statusGreenWrapper : styles.statusGrayWrapper}>
<Text style={['paid', 'completed'].includes(contract.status) ? styles.statusGreenText : styles.statusGrayText}>
{contract.status}
</Text>
</View>
<View style={styles.flexDirectionColumn}>
<View style={styles.flexDirectionRow}>
<Text style={styles.volumeBreakdownText}>
{contract.volume_breakdown.goes_to_buyer} {contract.asset_code}
</Text>
2020-07-20 15:38:46 +02:00
<Text style={styles.roleText}>{contract.your_role === 'buyer' ? loc.hodl.cont_buying : loc.hodl.cont_selling}</Text>
2020-06-15 20:47:54 +02:00
</View>
<View>
<Text style={styles.contractStatusText}>{contract.statusText}</Text>
</View>
</View>
</View>
</TouchableHighlight>
)}
/>
{this.renderContract()}
2020-07-15 19:32:59 +02:00
</View>
2020-06-15 20:47:54 +02:00
);
}
async refetchContracts() {
this.setState({
isLoading: true,
});
const hodlApi = this.state.hodlApi;
let contracts = [];
2020-06-15 20:47:54 +02:00
let contractToDisplay = this.state.contractToDisplay;
const contractIds = await this.context.getHodlHodlContracts();
2020-06-15 20:47:54 +02:00
/*
* Initiator sends Getting contract request once every 1-3 minutes until contract.escrow.address is not null (thus, waiting for offers creator to confirm his payment password in case he uses the website)
* Each party verifies the escrow address locally
* Each party sends Confirming contracts escrow validity request to the server
*/
for (const id of contractIds) {
let contract;
try {
contract = await hodlApi.getContract(id);
} catch (_) {
continue;
}
if (contract.status === 'canceled') continue;
if (contract.escrow && contract.escrow.address && hodlApi.verifyEscrowAddress()) {
await hodlApi.markContractAsConfirmed(id);
contract.isDepositedEnought =
contract.escrow.confirmations >= contract.confirmations && +contract.escrow.amount_deposited >= +contract.volume;
// technically, we could fetch balance of escrow address ourselved and verify, but we are relying on api here
2020-07-20 15:38:46 +02:00
contract.statusText = loc.hodl.cont_st_waiting;
if (contract.isDepositedEnought && contract.status !== 'paid') contract.statusText = loc.hodl.cont_st_paid_enought;
if (contract.status === 'paid') contract.statusText = loc.hodl.cont_st_paid_waiting;
if (contract.status === 'in_progress' && contract.your_role === 'buyer') contract.statusText = loc.hodl.cont_st_in_progress_buyer;
if (contract.status === 'completed') contract.statusText = loc.hodl.cont_st_completed;
2020-06-15 20:47:54 +02:00
}
contracts.push(contract);
if (contractToDisplay && contract.id === this.state.contractToDisplay.id) {
// refreshing contract that is currently being displayed
contractToDisplay = contract;
}
}
contracts = contracts.sort((a, b) => (a.created_at >= b.created_at ? -1 : 1)); // new contracts on top
2020-06-15 20:47:54 +02:00
this.setState({ hodlApi: hodlApi, contracts, contractToDisplay, isLoading: false });
}
_onContractPress(contract) {
this.setState({
contractToDisplay: contract,
isRenderContractVisible: true,
});
}
2020-11-11 17:59:03 +01:00
hideContractModal = () => {
Keyboard.dismiss();
this.setState({ isRenderContractVisible: false });
};
2020-06-15 20:47:54 +02:00
renderContract = () => {
if (!this.state.contractToDisplay) return;
return (
2020-11-17 09:43:38 +01:00
<BottomModal isVisible={this.state.isRenderContractVisible} onClose={this.hideContractModal}>
2021-02-25 02:56:06 +01:00
<KeyboardAvoidingView enabled={!Platform.isPad} behavior={Platform.OS === 'ios' ? 'position' : null}>
2020-06-15 20:47:54 +02:00
<View style={styles.modalContent}>
<View style={styles.modalContentCentered}>
<Text style={styles.btcText}>
{this.state.contractToDisplay.volume_breakdown.goes_to_buyer} {this.state.contractToDisplay.asset_code}
</Text>
<View style={styles.statusGreenWrapper}>
<Text style={styles.statusGreenText}>
{this.state.contractToDisplay.price} {this.state.contractToDisplay.currency_code}
</Text>
</View>
</View>
2020-07-20 15:38:46 +02:00
<Text style={styles.subheaderText}>{loc.hodl.cont_address_to}</Text>
2020-06-15 20:47:54 +02:00
<View style={styles.modalContentCentered}>
<View style={styles.statusGrayWrapper2}>
<Text
style={styles.statusGrayText2}
onPress={() => Linking.openURL(`https://blockstream.info/address/${this.state.contractToDisplay.release_address}`)}
>
{this.state.contractToDisplay.release_address}
</Text>
</View>
</View>
<BlueSpacing10 />
2020-07-20 15:38:46 +02:00
<Text style={styles.subheaderText}>{loc.hodl.cont_address_escrow}</Text>
2020-06-15 20:47:54 +02:00
<View style={styles.modalContentCentered}>
<View style={styles.statusGrayWrapper2}>
<Text
style={styles.statusGrayText2}
onPress={() => Linking.openURL(`https://blockstream.info/address/${this.state.contractToDisplay.escrow.address}`)}
>
{this.state.contractToDisplay.escrow.address}
</Text>
</View>
</View>
<BlueSpacing20 />
{this.isAllowedToMarkContractAsPaid() ? (
<View>
2020-07-20 15:38:46 +02:00
<Text style={styles.subheaderText}>{loc.hodl.cont_how}</Text>
2020-06-15 20:47:54 +02:00
<View style={styles.modalContentCentered}>
<View style={styles.statusGrayWrapper2}>
2020-06-16 13:48:29 +02:00
<BlueCopyTextToClipboard text={this.state.contractToDisplay.payment_method_instruction.details} />
2020-06-15 20:47:54 +02:00
</View>
</View>
</View>
) : (
<View />
)}
<BlueSpacing20 />
{this.isAllowedToMarkContractAsPaid() ? (
<View>
2020-07-20 15:38:46 +02:00
<BlueButton title={loc.hodl.cont_paid} onPress={() => this._onMarkContractAsPaid()} />
2020-06-15 20:47:54 +02:00
<BlueSpacing20 />
</View>
) : (
<View />
)}
<BlueSpacing20 />
{this.state.contractToDisplay.can_be_canceled && (
<Text onPress={() => this._onCancelContract()} style={styles.cancelContractText}>
2020-07-20 15:38:46 +02:00
{loc.hodl.cont_cancel}
2020-06-15 20:47:54 +02:00
</Text>
)}
<Text onPress={() => this._onOpenContractOnWebsite()} style={styles.openChatText}>
2020-07-20 15:38:46 +02:00
{loc.hodl.cont_chat}
2020-06-15 20:47:54 +02:00
</Text>
</View>
</KeyboardAvoidingView>
2020-11-17 09:43:38 +01:00
</BottomModal>
2020-06-15 20:47:54 +02:00
);
};
/**
* If you are the buyer, DO NOT SEND PAYMENT UNTIL CONTRACT STATUS IS "in_progress".
*/
_onMarkContractAsPaid() {
if (!this.state.contractToDisplay) return;
Alert.alert(
2020-07-20 15:38:46 +02:00
loc.hodl.cont_paid_q,
loc.hodl.cont_paid_e,
2020-06-15 20:47:54 +02:00
[
{
2020-07-20 15:38:46 +02:00
text: loc._.yes,
2020-06-15 20:47:54 +02:00
onPress: async () => {
const hodlApi = this.state.hodlApi;
try {
await hodlApi.markContractAsPaid(this.state.contractToDisplay.id);
this.setState({ isRenderContractVisible: false });
await this.refetchContracts();
} catch (Error) {
alert(Error);
}
},
style: 'default',
},
{
2020-07-20 15:38:46 +02:00
text: loc._.cancel,
2020-06-15 20:47:54 +02:00
onPress: () => {},
style: 'cancel',
},
],
{ cancelable: true },
);
}
async _onOpenContractOnWebsite() {
if (!this.state.contractToDisplay) return;
const hodlApi = this.state.hodlApi;
const sigKey = await this.context.getHodlHodlSignatureKey();
2020-06-15 20:47:54 +02:00
if (!sigKey) {
alert('Error: signature key not set'); // should never happen
return;
}
const autologinKey = await hodlApi.requestAutologinToken(sigKey);
const uri = 'https://hodlhodl.com/contracts/' + this.state.contractToDisplay.id + '?sign_in_token=' + autologinKey;
this.setState({ isRenderContractVisible: false }, () => {
NavigationService.navigate('HodlHodlWebview', { uri });
});
}
_onCancelContract() {
if (!this.state.contractToDisplay) return;
Alert.alert(
2020-07-20 15:38:46 +02:00
loc.hodl.cont_cancel_q,
'',
2020-06-15 20:47:54 +02:00
[
{
2020-07-20 15:38:46 +02:00
text: loc.hodl.cont_cancel_y,
2020-06-15 20:47:54 +02:00
onPress: async () => {
const hodlApi = this.state.hodlApi;
try {
await hodlApi.cancelContract(this.state.contractToDisplay.id);
this.setState({ isRenderContractVisible: false });
await this.refetchContracts();
} catch (Error) {
alert(Error);
}
},
style: 'default',
},
{
2020-07-20 15:38:46 +02:00
text: loc._.cancel,
2020-06-15 20:47:54 +02:00
onPress: () => {},
style: 'cancel',
},
],
{ cancelable: true },
);
}
isAllowedToMarkContractAsPaid() {
return this.state.contractToDisplay.status === 'in_progress' && this.state.contractToDisplay.your_role === 'buyer';
}
}
const styles = StyleSheet.create({
2020-07-15 19:32:59 +02:00
root: {
flex: 1,
backgroundColor: BlueCurrentTheme.colors.elevated,
},
2020-06-15 20:47:54 +02:00
modalContent: {
2020-07-15 19:32:59 +02:00
backgroundColor: BlueCurrentTheme.colors.modal,
2020-06-15 20:47:54 +02:00
padding: 22,
borderTopLeftRadius: 16,
borderTopRightRadius: 16,
borderColor: 'rgba(0, 0, 0, 0.1)',
minHeight: 425,
},
modalContentCentered: {
justifyContent: 'center',
alignItems: 'center',
},
statusGreenWrapper: {
2020-07-15 19:32:59 +02:00
backgroundColor: BlueCurrentTheme.colors.feeLabel,
2020-06-15 20:47:54 +02:00
borderRadius: 20,
height: 28,
justifyContent: 'center',
alignItems: 'center',
margin: 15,
paddingLeft: 15,
paddingRight: 15,
},
statusGreenText: {
fontSize: 12,
2020-07-15 19:32:59 +02:00
color: BlueCurrentTheme.colors.feeValue,
2020-06-15 20:47:54 +02:00
},
statusGrayWrapper: {
2020-07-15 19:32:59 +02:00
backgroundColor: BlueCurrentTheme.colors.lightBorder,
2020-06-15 20:47:54 +02:00
borderRadius: 20,
height: 28,
justifyContent: 'center',
alignItems: 'center',
margin: 15,
paddingLeft: 15,
paddingRight: 15,
},
statusGrayText: {
fontSize: 12,
2020-07-15 19:32:59 +02:00
color: '#9AA0AA',
2020-06-15 20:47:54 +02:00
},
statusGrayWrapper2: {
2020-07-15 19:32:59 +02:00
backgroundColor: BlueCurrentTheme.colors.inputBackgroundColor,
2020-06-15 20:47:54 +02:00
borderRadius: 5,
2020-06-16 13:48:29 +02:00
minHeight: 28,
maxHeight: 56,
2020-06-15 20:47:54 +02:00
justifyContent: 'center',
alignItems: 'center',
paddingLeft: 15,
paddingRight: 15,
},
statusGrayText2: {
fontSize: 12,
2020-07-15 19:32:59 +02:00
color: '#9AA0AA',
2020-06-15 20:47:54 +02:00
},
btcText: {
fontWeight: 'bold',
fontSize: 18,
2020-07-15 19:32:59 +02:00
color: BlueCurrentTheme.colors.foregroundColor,
2020-06-15 20:47:54 +02:00
},
subheaderText: {
fontSize: 12,
fontWeight: 'bold',
2020-07-15 19:32:59 +02:00
color: BlueCurrentTheme.colors.feeText,
2020-06-15 20:47:54 +02:00
},
2020-07-15 19:32:59 +02:00
loading: { backgroundColor: BlueCurrentTheme.colors.elevated },
emptyComponentText: { textAlign: 'center', color: '#9AA0AA', paddingHorizontal: 16, backgroundColor: BlueCurrentTheme.colors.elevated },
2020-06-15 20:47:54 +02:00
itemSeparatorComponent: { height: 0.5, width: '100%', backgroundColor: '#C8C8C8' },
flexDirectionRow: { flexDirection: 'row' },
flexDirectionColumn: { flexDirection: 'column' },
2020-07-15 19:32:59 +02:00
volumeBreakdownText: { fontSize: 18, color: BlueCurrentTheme.colors.foregroundColor },
contractStatusText: { fontSize: 13, color: 'gray', fontWeight: 'normal' },
2020-06-15 20:47:54 +02:00
cancelContractText: { color: '#d0021b', fontSize: 15, paddingTop: 20, fontWeight: '500', textAlign: 'center' },
2020-07-15 19:32:59 +02:00
openChatText: { color: BlueCurrentTheme.colors.foregroundColor, fontSize: 15, paddingTop: 20, fontWeight: '500', textAlign: 'center' },
flatList: { paddingTop: 30, backgroundColor: BlueCurrentTheme.colors.elevated },
2020-06-15 20:47:54 +02:00
roleText: { fontSize: 14, color: 'gray', padding: 5 },
2020-07-31 19:01:04 +02:00
marginRight: {
marginRight: 20,
},
2020-06-15 20:47:54 +02:00
});
2020-07-15 19:32:59 +02:00
2020-12-25 17:09:53 +01:00
HodlHodlMyContracts.propTypes = {
navigation: PropTypes.shape({
navigate: PropTypes.func,
setParams: PropTypes.func,
}),
};
HodlHodlMyContracts.navigationOptions = navigationStyle(
{
closeButton: true,
2020-07-15 19:32:59 +02:00
},
2020-12-25 17:09:53 +01:00
(options, { theme, navigation, route }) => ({
...options,
2021-02-15 09:03:54 +01:00
title: loc.hodl.cont_title,
2020-12-25 17:09:53 +01:00
headerStyle: {
backgroundColor: theme.colors.elevated,
},
headerRight: () => (
<TouchableOpacity
style={styles.marginRight}
onPress={() => {
Alert.alert(
loc.hodl.are_you_sure_you_want_to_logout,
'',
[
{
text: loc._.ok,
onPress: route.params.handleLogout,
style: 'default',
2020-07-31 19:01:04 +02:00
},
2020-12-25 17:09:53 +01:00
{ text: loc._.cancel, onPress: () => {}, style: 'cancel' },
],
{ cancelable: false },
);
}}
>
<BlueText>{loc.hodl.logout}</BlueText>
</TouchableOpacity>
),
}),
);