From 37b31a5d0a528d6f64bc90e610fdf0fb3ede82b7 Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Wed, 30 Mar 2016 02:46:02 +0200 Subject: [PATCH] Savings wallet (WIP) --- .../src/main/java/io/bitsquare/app/Log.java | 2 +- .../bitsquare/common/crypto/PubKeyRing.java | 2 +- .../common/crypto/SealedAndSigned.java | 3 +- .../main/java/io/bitsquare/alert/Alert.java | 3 +- .../io/bitsquare/arbitration/Dispute.java | 2 +- .../io/bitsquare/arbitration/DisputeList.java | 2 +- .../bitsquare/arbitration/DisputeResult.java | 2 +- .../messages/DisputeCommunicationMessage.java | 2 +- .../btc/AddressBasedCoinSelector.java | 9 +- .../java/io/bitsquare/btc/AddressEntry.java | 2 +- .../io/bitsquare/btc/AddressEntryList.java | 12 ++ .../io/bitsquare/btc/TradeWalletService.java | 65 +++--- .../java/io/bitsquare/btc/WalletService.java | 100 ++++++++-- .../io/bitsquare/payment/PaymentMethod.java | 2 +- .../bitsquare/trade/BuyerAsOffererTrade.java | 2 +- .../io/bitsquare/trade/BuyerAsTakerTrade.java | 2 +- .../bitsquare/trade/SellerAsOffererTrade.java | 2 +- .../bitsquare/trade/SellerAsTakerTrade.java | 2 +- .../java/io/bitsquare/trade/TradableList.java | 2 +- .../main/java/io/bitsquare/trade/Trade.java | 8 +- .../java/io/bitsquare/trade/TradeManager.java | 15 +- .../java/io/bitsquare/trade/offer/Offer.java | 2 +- .../io/bitsquare/trade/offer/OpenOffer.java | 2 +- .../trade/offer/OpenOfferManager.java | 7 +- .../protocol/placeoffer/PlaceOfferModel.java | 7 + .../tasks/BroadcastCreateOfferFeeTx.java | 111 +++++------ .../placeoffer/tasks/CreateOfferFeeTx.java | 6 + .../trade/BuyerAsOffererProtocol.java | 2 +- .../protocol/trade/BuyerAsTakerProtocol.java | 2 +- .../trade/protocol/trade/ProcessModel.java | 19 +- .../trade/SellerAsOffererProtocol.java | 2 +- .../protocol/trade/SellerAsTakerProtocol.java | 2 +- .../trade/protocol/trade/TradingPeer.java | 2 +- ...fererCreatesAndSignsDepositTxAsBuyer.java} | 7 +- ...> TakerCreatesDepositTxInputsAsBuyer.java} | 10 +- ...ererCreatesAndSignsDepositTxAsSeller.java} | 7 +- ... TakerCreatesDepositTxInputsAsSeller.java} | 12 +- .../tasks/taker/CreateTakeOfferFeeTx.java | 6 + .../java/io/bitsquare/app/BitsquareApp.java | 2 +- .../main/java/io/bitsquare/gui/bitsquare.css | 10 + .../AddressWithIconAndDirection.java | 13 +- .../gui/components/BalanceTextField.java | 13 +- .../io/bitsquare/gui/main/MainViewModel.java | 6 +- .../ArbitratorSelectionView.java | 21 +- .../bitsquare/gui/main/debug/DebugView.java | 8 +- .../disputes/trader/TraderDisputeView.java | 70 ++++--- .../main/funds/deposit/DepositListItem.java | 31 ++- .../gui/main/funds/deposit/DepositView.fxml | 16 +- .../gui/main/funds/deposit/DepositView.java | 185 ++++++++++++------ .../main/funds/reserved/ReservedListItem.java | 5 + .../gui/main/funds/reserved/ReservedView.fxml | 9 +- .../gui/main/funds/reserved/ReservedView.java | 34 ++-- .../transactions/TransactionsListItem.java | 65 ++++-- .../funds/transactions/TransactionsView.fxml | 16 +- .../funds/transactions/TransactionsView.java | 141 +++++++++++-- .../main/funds/withdrawal/WithdrawalView.fxml | 13 +- .../main/funds/withdrawal/WithdrawalView.java | 154 +++------------ .../statistics/MarketsStatisticsView.java | 45 +++-- .../createoffer/CreateOfferDataModel.java | 56 +++++- .../offer/createoffer/CreateOfferView.java | 91 +++++---- .../createoffer/CreateOfferViewModel.java | 58 +++--- .../main/offer/offerbook/OfferBookView.java | 12 +- .../offer/takeoffer/TakeOfferDataModel.java | 1 + .../main/offer/takeoffer/TakeOfferView.java | 3 +- .../bitsquare/gui/main/overlays/Overlay.java | 1 - .../main/overlays/windows/QRCodeWindow.java | 18 +- .../closedtrades/ClosedTradesView.fxml | 6 +- .../closedtrades/ClosedTradesView.java | 58 +++++- .../failedtrades/FailedTradesView.fxml | 6 +- .../failedtrades/FailedTradesView.java | 29 ++- .../portfolio/openoffer/OpenOffersView.fxml | 4 +- .../portfolio/openoffer/OpenOffersView.java | 27 ++- .../pendingtrades/PendingTradesView.fxml | 27 +-- .../pendingtrades/PendingTradesView.java | 128 +++++++----- .../steps/buyer/BuyerStep5View.java | 83 +++++--- .../settings/network/NetworkSettingsView.fxml | 2 +- .../settings/network/NetworkSettingsView.java | 22 ++- .../io/bitsquare/gui/util/FormBuilder.java | 28 +-- .../bitsquare/p2p/storage/P2PDataStorage.java | 5 +- .../payload/MailboxStoragePayload.java | 3 +- .../ProtectedMailboxStorageEntry.java | 3 +- .../storageentry/ProtectedStorageEntry.java | 3 +- 82 files changed, 1238 insertions(+), 740 deletions(-) rename core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/{CreateAndSignDepositTxAsBuyer.java => OffererCreatesAndSignsDepositTxAsBuyer.java} (90%) rename core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/{CreateDepositTxInputsAsBuyer.java => TakerCreatesDepositTxInputsAsBuyer.java} (79%) rename core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/{CreateAndSignDepositTxAsSeller.java => OffererCreatesAndSignsDepositTxAsSeller.java} (90%) rename core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/{CreateDepositTxInputsAsSeller.java => TakerCreatesDepositTxInputsAsSeller.java} (79%) diff --git a/common/src/main/java/io/bitsquare/app/Log.java b/common/src/main/java/io/bitsquare/app/Log.java index 4a4e04a9c5..4e3ea3c3f1 100644 --- a/common/src/main/java/io/bitsquare/app/Log.java +++ b/common/src/main/java/io/bitsquare/app/Log.java @@ -60,7 +60,7 @@ public class Log { appender.start(); logbackLogger = loggerContext.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME); - logbackLogger.setLevel(useDetailedLogging ? Level.TRACE : Level.WARN); + logbackLogger.setLevel(useDetailedLogging ? Level.TRACE : Level.TRACE); logbackLogger.addAppender(appender); // log errors in separate file diff --git a/common/src/main/java/io/bitsquare/common/crypto/PubKeyRing.java b/common/src/main/java/io/bitsquare/common/crypto/PubKeyRing.java index e5c67786ed..8c839dafa5 100644 --- a/common/src/main/java/io/bitsquare/common/crypto/PubKeyRing.java +++ b/common/src/main/java/io/bitsquare/common/crypto/PubKeyRing.java @@ -62,7 +62,7 @@ public final class PubKeyRing implements Payload { e.printStackTrace(); log.error(e.getMessage()); } catch (Throwable t) { - log.trace("Cannot be deserialized." + t.getMessage()); + log.warn("Cannot be deserialized." + t.getMessage()); } } diff --git a/common/src/main/java/io/bitsquare/common/crypto/SealedAndSigned.java b/common/src/main/java/io/bitsquare/common/crypto/SealedAndSigned.java index 1515a081f1..725d642557 100644 --- a/common/src/main/java/io/bitsquare/common/crypto/SealedAndSigned.java +++ b/common/src/main/java/io/bitsquare/common/crypto/SealedAndSigned.java @@ -52,8 +52,7 @@ public final class SealedAndSigned implements Payload { in.defaultReadObject(); sigPublicKey = KeyFactory.getInstance(Sig.KEY_ALGO, "BC").generatePublic(new X509EncodedKeySpec(sigPublicKeyBytes)); } catch (Throwable t) { - log.error("Exception at readObject: " + t.getMessage()); - t.printStackTrace(); + log.warn("Exception at readObject: " + t.getMessage()); } } diff --git a/core/src/main/java/io/bitsquare/alert/Alert.java b/core/src/main/java/io/bitsquare/alert/Alert.java index d3abbc19da..8a44c1974c 100644 --- a/core/src/main/java/io/bitsquare/alert/Alert.java +++ b/core/src/main/java/io/bitsquare/alert/Alert.java @@ -53,8 +53,7 @@ public final class Alert implements StoragePayload { in.defaultReadObject(); storagePublicKey = KeyFactory.getInstance(Sig.KEY_ALGO, "BC").generatePublic(new X509EncodedKeySpec(storagePublicKeyBytes)); } catch (Throwable t) { - log.error("Exception at readObject: " + t.getMessage()); - t.printStackTrace(); + log.warn("Exception at readObject: " + t.getMessage()); } } diff --git a/core/src/main/java/io/bitsquare/arbitration/Dispute.java b/core/src/main/java/io/bitsquare/arbitration/Dispute.java index 3dd6bd8189..40c5672b2a 100644 --- a/core/src/main/java/io/bitsquare/arbitration/Dispute.java +++ b/core/src/main/java/io/bitsquare/arbitration/Dispute.java @@ -134,7 +134,7 @@ public final class Dispute implements Payload { disputeResultProperty = new SimpleObjectProperty<>(disputeResult); isClosedProperty = new SimpleBooleanProperty(isClosed); } catch (Throwable t) { - log.trace("Cannot be deserialized." + t.getMessage()); + log.warn("Cannot be deserialized." + t.getMessage()); } } diff --git a/core/src/main/java/io/bitsquare/arbitration/DisputeList.java b/core/src/main/java/io/bitsquare/arbitration/DisputeList.java index d5806765de..6df6269699 100644 --- a/core/src/main/java/io/bitsquare/arbitration/DisputeList.java +++ b/core/src/main/java/io/bitsquare/arbitration/DisputeList.java @@ -53,7 +53,7 @@ public final class DisputeList extends ArrayList imple try { in.defaultReadObject(); } catch (Throwable t) { - log.trace("Cannot be deserialized." + t.getMessage()); + log.warn("Cannot be deserialized." + t.getMessage()); } } diff --git a/core/src/main/java/io/bitsquare/arbitration/DisputeResult.java b/core/src/main/java/io/bitsquare/arbitration/DisputeResult.java index 7c4570f3d1..9e6f7a39c9 100644 --- a/core/src/main/java/io/bitsquare/arbitration/DisputeResult.java +++ b/core/src/main/java/io/bitsquare/arbitration/DisputeResult.java @@ -84,7 +84,7 @@ public final class DisputeResult implements Payload { in.defaultReadObject(); init(); } catch (Throwable t) { - log.trace("Cannot be deserialized." + t.getMessage()); + log.warn("Cannot be deserialized." + t.getMessage()); } } diff --git a/core/src/main/java/io/bitsquare/arbitration/messages/DisputeCommunicationMessage.java b/core/src/main/java/io/bitsquare/arbitration/messages/DisputeCommunicationMessage.java index 87352e1636..d6226afd0a 100644 --- a/core/src/main/java/io/bitsquare/arbitration/messages/DisputeCommunicationMessage.java +++ b/core/src/main/java/io/bitsquare/arbitration/messages/DisputeCommunicationMessage.java @@ -66,7 +66,7 @@ public final class DisputeCommunicationMessage extends DisputeMessage { arrivedProperty = new SimpleBooleanProperty(arrived); storedInMailboxProperty = new SimpleBooleanProperty(storedInMailbox); } catch (Throwable t) { - log.trace("Cannot be deserialized." + t.getMessage()); + log.warn("Cannot be deserialized." + t.getMessage()); } } diff --git a/core/src/main/java/io/bitsquare/btc/AddressBasedCoinSelector.java b/core/src/main/java/io/bitsquare/btc/AddressBasedCoinSelector.java index 418e60b434..5e3f639cd1 100644 --- a/core/src/main/java/io/bitsquare/btc/AddressBasedCoinSelector.java +++ b/core/src/main/java/io/bitsquare/btc/AddressBasedCoinSelector.java @@ -46,7 +46,11 @@ class AddressBasedCoinSelector implements CoinSelector { // Constructor /////////////////////////////////////////////////////////////////////////////////////////// - public AddressBasedCoinSelector(NetworkParameters params, AddressEntry addressEntry) { + public AddressBasedCoinSelector(NetworkParameters params) { + this.params = params; + } + + public AddressBasedCoinSelector(NetworkParameters params, @Nullable AddressEntry addressEntry) { this.params = params; this.addressEntry = addressEntry; } @@ -119,6 +123,9 @@ class AddressBasedCoinSelector implements CoinSelector { log.trace("No match found at matchesRequiredAddress addressOutput / addressEntries " + addressOutput.toString () + " / " + addressEntries.toString()); + } else { + // use savings wallet + return true; } } return false; diff --git a/core/src/main/java/io/bitsquare/btc/AddressEntry.java b/core/src/main/java/io/bitsquare/btc/AddressEntry.java index 4dbe1bdc02..171be4ac78 100644 --- a/core/src/main/java/io/bitsquare/btc/AddressEntry.java +++ b/core/src/main/java/io/bitsquare/btc/AddressEntry.java @@ -101,7 +101,7 @@ public final class AddressEntry implements Persistable { params = RegTestParams.get(); } catch (Throwable t) { - log.trace("Cannot be deserialized." + t.getMessage()); + log.warn("Cannot be deserialized." + t.getMessage()); } } diff --git a/core/src/main/java/io/bitsquare/btc/AddressEntryList.java b/core/src/main/java/io/bitsquare/btc/AddressEntryList.java index 7fb7f70284..8621a53478 100644 --- a/core/src/main/java/io/bitsquare/btc/AddressEntryList.java +++ b/core/src/main/java/io/bitsquare/btc/AddressEntryList.java @@ -27,6 +27,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; +import java.util.Optional; /** * The List supporting our persistence solution. @@ -82,6 +83,17 @@ public final class AddressEntryList extends ArrayList implements P } + public void swapTradeToSavings(String offerId) { + Optional addressEntryOptional = this.stream().filter(addressEntry -> offerId.equals(addressEntry.getOfferId())).findAny(); + if (addressEntryOptional.isPresent()) { + AddressEntry addressEntry = addressEntryOptional.get(); + add(new AddressEntry(addressEntry.getKeyPair(), wallet.getParams(), AddressEntry.Context.SAVINGS)); + remove(addressEntry); + storage.queueUpForSave(); + } + } + + public AddressEntry getArbitratorAddressEntry() { if (size() > 0) return get(0); diff --git a/core/src/main/java/io/bitsquare/btc/TradeWalletService.java b/core/src/main/java/io/bitsquare/btc/TradeWalletService.java index 9161d620f3..5089dd1720 100644 --- a/core/src/main/java/io/bitsquare/btc/TradeWalletService.java +++ b/core/src/main/java/io/bitsquare/btc/TradeWalletService.java @@ -134,19 +134,22 @@ public class TradeWalletService { /** * @param addressEntry From where we want to spend the transaction fee. Used also as change address. + * @param useSavingsWallet * @param tradingFee The amount of the trading fee. - * @param feeReceiverAddresses The address of the receiver of the trading fee (arbitrator). - * @return The broadcasted transaction + * @param feeReceiverAddresses The address of the receiver of the trading fee (arbitrator). @return The broadcasted transaction * @throws InsufficientMoneyException * @throws AddressFormatException */ - public Transaction createTradingFeeTx(AddressEntry addressEntry, Coin tradingFee, String feeReceiverAddresses) + public Transaction createTradingFeeTx(AddressEntry addressEntry, Address changeAddress, Coin reservedFundsForOffer, + boolean useSavingsWallet, Coin tradingFee, String feeReceiverAddresses) throws InsufficientMoneyException, AddressFormatException { Transaction tradingFeeTx = new Transaction(params); Preconditions.checkArgument(Restrictions.isAboveFixedTxFeeAndDust(tradingFee), "You cannot send an amount which are smaller than the fee + dust output."); Coin outPutAmount = tradingFee.subtract(FeePolicy.getFixedTxFeeForTrades()); tradingFeeTx.addOutput(outPutAmount, new Address(params, feeReceiverAddresses)); + // the reserved amount we need for the trade we send to our trade address + tradingFeeTx.addOutput(reservedFundsForOffer, addressEntry.getAddress()); // we allow spending of unconfirmed tx (double spend risk is low and usability would suffer if we need to // wait for 1 confirmation) @@ -154,13 +157,16 @@ public class TradeWalletService { Wallet.SendRequest sendRequest = Wallet.SendRequest.forTx(tradingFeeTx); sendRequest.shuffleOutputs = false; sendRequest.aesKey = aesKey; - sendRequest.coinSelector = new AddressBasedCoinSelector(params, addressEntry); + if (useSavingsWallet) + sendRequest.coinSelector = new AddressBasedCoinSelector(params); + else + sendRequest.coinSelector = new AddressBasedCoinSelector(params, addressEntry); // We use a fixed fee sendRequest.feePerKb = Coin.ZERO; sendRequest.fee = FeePolicy.getFixedTxFeeForTrades(); - // We use always the same address for all transactions in a trade to keep things simple. - // To be discussed if that introduce any privacy issues. - sendRequest.changeAddress = addressEntry.getAddress(); + + // Change is optional in case of overpay or use of funds from savings wallet + sendRequest.changeAddress = changeAddress; checkNotNull(wallet, "Wallet must not be null"); wallet.completeTx(sendRequest); @@ -181,20 +187,20 @@ public class TradeWalletService { /** - * The taker creates a dummy transaction to get the input(s) and optional change output for the amount and the addressEntry for that trade. + * The taker creates a dummy transaction to get the input(s) and optional change output for the amount and the takersAddressEntry for that trade. * That will be used to send to the offerer for creating the deposit transaction. * - * @param inputAmount Amount of takers input - * @param addressEntry Address entry of taker + * @param inputAmount Amount of takers input + * @param takersAddressEntry Address entry of taker * @return A data container holding the inputs, the output value and address * @throws TransactionVerificationException * @throws WalletException */ - public InputsAndChangeOutput takerCreatesDepositsTxInputs(Coin inputAmount, AddressEntry addressEntry) throws - TransactionVerificationException, WalletException { + public InputsAndChangeOutput takerCreatesDepositsTxInputs(Coin inputAmount, AddressEntry takersAddressEntry, Address takersChangeAddress) throws + TransactionVerificationException, WalletException, AddressFormatException { log.trace("createTakerDepositTxInputs called"); log.trace("inputAmount " + inputAmount.toFriendlyString()); - log.trace("addressEntry " + addressEntry.toString()); + log.trace("takersAddressEntry " + takersAddressEntry.toString()); // We add the mining fee 2 times to the deposit tx: // 1. Will be spent when publishing the deposit tx (paid by buyer) @@ -224,7 +230,7 @@ public class TradeWalletService { // Find the needed inputs to pay the output, optionally add 1 change output. // Normally only 1 input and no change output is used, but we support multiple inputs and 1 change output. // Our spending transaction output is from the create offer fee payment. - addAvailableInputsAndChangeOutputs(dummyTX, addressEntry); + addAvailableInputsAndChangeOutputs(dummyTX, takersAddressEntry, takersChangeAddress); // The completeTx() call signs the input, but we don't want to pass over signed tx inputs so we remove the signature removeSignatures(dummyTX); @@ -261,17 +267,17 @@ public class TradeWalletService { /** * The offerer creates the deposit transaction using the takers input(s) and optional output and signs his input(s). * - * @param offererIsBuyer The flag indicating if we are in the offerer as buyer role or the opposite. - * @param contractHash The hash of the contract to be added to the OP_RETURN output. - * @param offererInputAmount The input amount of the offerer. - * @param msOutputAmount The output amount to our MS output. - * @param takerRawTransactionInputs Raw data for the connected outputs for all inputs of the taker (normally 1 input) - * @param takerChangeOutputValue Optional taker change output value - * @param takerChangeAddressString Optional taker change address - * @param offererAddressInfo The offerers address entry. - * @param buyerPubKey The public key of the buyer. - * @param sellerPubKey The public key of the seller. - * @param arbitratorPubKey The public key of the arbitrator. + * @param offererIsBuyer The flag indicating if we are in the offerer as buyer role or the opposite. + * @param contractHash The hash of the contract to be added to the OP_RETURN output. + * @param offererInputAmount The input amount of the offerer. + * @param msOutputAmount The output amount to our MS output. + * @param takerRawTransactionInputs Raw data for the connected outputs for all inputs of the taker (normally 1 input) + * @param takerChangeOutputValue Optional taker change output value + * @param takerChangeAddressString Optional taker change address + * @param offererAddressEntry The offerers address entry. + * @param buyerPubKey The public key of the buyer. + * @param sellerPubKey The public key of the seller. + * @param arbitratorPubKey The public key of the arbitrator. * @return A data container holding the serialized transaction and the offerer raw inputs * @throws SigningException * @throws TransactionVerificationException @@ -284,7 +290,8 @@ public class TradeWalletService { List takerRawTransactionInputs, long takerChangeOutputValue, @Nullable String takerChangeAddressString, - AddressEntry offererAddressInfo, + AddressEntry offererAddressEntry, + Address offererChangeAddress, byte[] buyerPubKey, byte[] sellerPubKey, byte[] arbitratorPubKey) @@ -308,7 +315,7 @@ public class TradeWalletService { Coin dummyOutputAmount = offererInputAmount.subtract(FeePolicy.getFixedTxFeeForTrades()); TransactionOutput dummyOutput = new TransactionOutput(params, dummyTx, dummyOutputAmount, new ECKey().toAddress(params)); dummyTx.addOutput(dummyOutput); - addAvailableInputsAndChangeOutputs(dummyTx, offererAddressInfo); + addAvailableInputsAndChangeOutputs(dummyTx, offererAddressEntry, offererChangeAddress); // Normally we have only 1 input but we support multiple inputs if the user has paid in with several transactions. List offererInputs = dummyTx.getInputs(); TransactionOutput offererOutput = null; @@ -1035,7 +1042,7 @@ public class TradeWalletService { } } - private void addAvailableInputsAndChangeOutputs(Transaction transaction, AddressEntry addressEntry) throws WalletException { + private void addAvailableInputsAndChangeOutputs(Transaction transaction, AddressEntry addressEntry, Address changeAddress) throws WalletException { try { // Lets let the framework do the work to find the right inputs Wallet.SendRequest sendRequest = Wallet.SendRequest.forTx(transaction); @@ -1047,7 +1054,7 @@ public class TradeWalletService { // we allow spending of unconfirmed tx (double spend risk is low and usability would suffer if we need to wait for 1 confirmation) sendRequest.coinSelector = new AddressBasedCoinSelector(params, addressEntry); // We use always the same address in a trade for all transactions - sendRequest.changeAddress = addressEntry.getAddress(); + sendRequest.changeAddress = changeAddress; // With the usage of completeTx() we get all the work done with fee calculation, validation and coin selection. // We don't commit that tx to the wallet as it will be changed later and it's not signed yet. // So it will not change the wallet balance. diff --git a/core/src/main/java/io/bitsquare/btc/WalletService.java b/core/src/main/java/io/bitsquare/btc/WalletService.java index 05bdd6408b..9f8ad2bb14 100644 --- a/core/src/main/java/io/bitsquare/btc/WalletService.java +++ b/core/src/main/java/io/bitsquare/btc/WalletService.java @@ -305,19 +305,13 @@ public class WalletService { /////////////////////////////////////////////////////////////////////////////////////////// - // AddressInfo + // Trade AddressEntry /////////////////////////////////////////////////////////////////////////////////////////// public List getAddressEntryList() { return ImmutableList.copyOf(addressEntryList); } - public List getSavingsAddressEntryList() { - return getAddressEntryList().stream() - .filter(e -> e.getContext().equals(AddressEntry.Context.SAVINGS)) - .collect(Collectors.toList()); - } - public AddressEntry getArbitratorAddressEntry() { return arbitratorAddressEntry; } @@ -332,10 +326,6 @@ public class WalletService { return addressEntryList.getNewTradeAddressEntry(offerId); } - public AddressEntry getNewSavingsAddressEntry() { - return addressEntryList.getNewSavingsAddressEntry(); - } - private Optional getAddressEntryByAddress(String address) { return getAddressEntryList().stream() .filter(e -> e.getAddressString() != null && e.getAddressString().equals(address)) @@ -343,6 +333,72 @@ public class WalletService { } + /////////////////////////////////////////////////////////////////////////////////////////// + // SavingsAddressEntry + /////////////////////////////////////////////////////////////////////////////////////////// + + public AddressEntry getNewSavingsAddressEntry() { + return addressEntryList.getNewSavingsAddressEntry(); + } + + public List getSavingsAddressEntryList() { + return getAddressEntryList().stream() + .filter(e -> e.getContext().equals(AddressEntry.Context.SAVINGS)) + .collect(Collectors.toList()); + } + + public AddressEntry getUnusedSavingsAddressEntry() { + List unusedSavingsAddressEntries = getUnusedSavingsAddressEntries(); + if (!unusedSavingsAddressEntries.isEmpty()) + return unusedSavingsAddressEntries.get(0); + else + return getNewSavingsAddressEntry(); + } + + public List getUnusedSavingsAddressEntries() { + return getSavingsAddressEntryList().stream() + .filter(addressEntry -> getNumTxOutputsForAddress(addressEntry.getAddress()) == 0) + .collect(Collectors.toList()); + } + + public List getUsedSavingsAddressEntries() { + return getSavingsAddressEntryList().stream() + .filter(addressEntry -> getNumTxOutputsForAddress(addressEntry.getAddress()) > 0) + .collect(Collectors.toList()); + } + + public List
getUsedSavingsAddresses() { + return getSavingsAddressEntryList().stream() + .filter(addressEntry -> getNumTxOutputsForAddress(addressEntry.getAddress()) > 0) + .map(addressEntry -> addressEntry.getAddress()) + .collect(Collectors.toList()); + } + + public List getUsedSavingWalletTransactions() { + List transactions = new ArrayList<>(); + List transactionOutputs = new ArrayList<>(); + List
usedSavingsAddresses = getUsedSavingsAddresses(); + log.debug("usedSavingsAddresses = " + usedSavingsAddresses); + wallet.getTransactions(true).stream().forEach(transaction -> transactionOutputs.addAll(transaction.getOutputs())); + for (TransactionOutput transactionOutput : transactionOutputs) { + if (transactionOutput.getScriptPubKey().isSentToAddress() || transactionOutput.getScriptPubKey().isPayToScriptHash()) { + Address addressOutput = transactionOutput.getScriptPubKey().getToAddress(params); + + if (usedSavingsAddresses.contains(addressOutput) && transactionOutput.getParentTransaction() != null) { + log.debug("transactionOutput.getParentTransaction() = " + transactionOutput.getParentTransaction().getHashAsString()); + transactions.add(transactionOutput.getParentTransaction()); + } + } + } + + return transactions; + } + + public void swapTradeToSavings(String offerId) { + addressEntryList.swapTradeToSavings(offerId); + } + + /////////////////////////////////////////////////////////////////////////////////////////// // TransactionConfidence /////////////////////////////////////////////////////////////////////////////////////////// @@ -448,6 +504,28 @@ public class WalletService { return balance; } + public Coin getSavingWalletBalance() { + Coin balance = Coin.ZERO; + for (AddressEntry addressEntry : getSavingsAddressEntryList()) { + balance = balance.add(getBalanceForAddress(addressEntry.getAddress())); + } + return balance; + } + + public int getNumTxOutputsForAddress(Address address) { + List transactionOutputs = new ArrayList<>(); + wallet.getTransactions(true).stream().forEach(t -> transactionOutputs.addAll(t.getOutputs())); + int outputs = 0; + for (TransactionOutput transactionOutput : transactionOutputs) { + if (transactionOutput.getScriptPubKey().isSentToAddress() || transactionOutput.getScriptPubKey().isPayToScriptHash()) { + Address addressOutput = transactionOutput.getScriptPubKey().getToAddress(params); + if (addressOutput.equals(address)) + outputs++; + } + } + return outputs; + } + /////////////////////////////////////////////////////////////////////////////////////////// // Withdrawal diff --git a/core/src/main/java/io/bitsquare/payment/PaymentMethod.java b/core/src/main/java/io/bitsquare/payment/PaymentMethod.java index 175f0df1ce..1ff2431fd7 100644 --- a/core/src/main/java/io/bitsquare/payment/PaymentMethod.java +++ b/core/src/main/java/io/bitsquare/payment/PaymentMethod.java @@ -115,7 +115,7 @@ public final class PaymentMethod implements Persistable, Comparable { this.maxTradePeriod = paymentMethod.getMaxTradePeriod(); this.maxTradeLimitInBitcoin = paymentMethod.getMaxTradeLimitInBitcoin(); } catch (Throwable t) { - log.error("Cannot be deserialized." + t.getMessage()); + log.warn("Cannot be deserialized." + t.getMessage()); } } diff --git a/core/src/main/java/io/bitsquare/trade/BuyerAsOffererTrade.java b/core/src/main/java/io/bitsquare/trade/BuyerAsOffererTrade.java index 720a128985..cf2cda9812 100644 --- a/core/src/main/java/io/bitsquare/trade/BuyerAsOffererTrade.java +++ b/core/src/main/java/io/bitsquare/trade/BuyerAsOffererTrade.java @@ -55,7 +55,7 @@ public final class BuyerAsOffererTrade extends BuyerTrade implements OffererTrad initStateProperties(); initAmountProperty(); } catch (Throwable t) { - log.trace("Cannot be deserialized." + t.getMessage()); + log.warn("Cannot be deserialized." + t.getMessage()); } } diff --git a/core/src/main/java/io/bitsquare/trade/BuyerAsTakerTrade.java b/core/src/main/java/io/bitsquare/trade/BuyerAsTakerTrade.java index b6c9be51e6..938d9812ea 100644 --- a/core/src/main/java/io/bitsquare/trade/BuyerAsTakerTrade.java +++ b/core/src/main/java/io/bitsquare/trade/BuyerAsTakerTrade.java @@ -55,7 +55,7 @@ public final class BuyerAsTakerTrade extends BuyerTrade implements TakerTrade { initStateProperties(); initAmountProperty(); } catch (Throwable t) { - log.trace("Cannot be deserialized." + t.getMessage()); + log.warn("Cannot be deserialized." + t.getMessage()); } } diff --git a/core/src/main/java/io/bitsquare/trade/SellerAsOffererTrade.java b/core/src/main/java/io/bitsquare/trade/SellerAsOffererTrade.java index b8aeebd951..d87a41243d 100644 --- a/core/src/main/java/io/bitsquare/trade/SellerAsOffererTrade.java +++ b/core/src/main/java/io/bitsquare/trade/SellerAsOffererTrade.java @@ -51,7 +51,7 @@ public final class SellerAsOffererTrade extends SellerTrade implements OffererTr initStateProperties(); initAmountProperty(); } catch (Throwable t) { - log.trace("Cannot be deserialized." + t.getMessage()); + log.warn("Cannot be deserialized." + t.getMessage()); } } diff --git a/core/src/main/java/io/bitsquare/trade/SellerAsTakerTrade.java b/core/src/main/java/io/bitsquare/trade/SellerAsTakerTrade.java index bb2c2e6293..b46c05b2d5 100644 --- a/core/src/main/java/io/bitsquare/trade/SellerAsTakerTrade.java +++ b/core/src/main/java/io/bitsquare/trade/SellerAsTakerTrade.java @@ -52,7 +52,7 @@ public final class SellerAsTakerTrade extends SellerTrade implements TakerTrade initStateProperties(); initAmountProperty(); } catch (Throwable t) { - log.trace("Cannot be deserialized." + t.getMessage()); + log.warn("Cannot be deserialized." + t.getMessage()); } } diff --git a/core/src/main/java/io/bitsquare/trade/TradableList.java b/core/src/main/java/io/bitsquare/trade/TradableList.java index eed38f17ae..4d05a20ac2 100644 --- a/core/src/main/java/io/bitsquare/trade/TradableList.java +++ b/core/src/main/java/io/bitsquare/trade/TradableList.java @@ -59,7 +59,7 @@ public final class TradableList extends ArrayList impleme try { in.defaultReadObject(); } catch (Throwable t) { - log.trace("Cannot be deserialized." + t.getMessage()); + log.warn("Cannot be deserialized." + t.getMessage()); } } diff --git a/core/src/main/java/io/bitsquare/trade/Trade.java b/core/src/main/java/io/bitsquare/trade/Trade.java index 53700a2d9f..0efe6a1d6c 100644 --- a/core/src/main/java/io/bitsquare/trade/Trade.java +++ b/core/src/main/java/io/bitsquare/trade/Trade.java @@ -209,7 +209,7 @@ public abstract class Trade implements Tradable, Model { initAmountProperty(); errorMessageProperty = new SimpleStringProperty(errorMessage); } catch (Throwable t) { - log.trace("Cannot be deserialized." + t.getMessage()); + log.warn("Cannot be deserialized." + t.getMessage()); } } @@ -220,7 +220,8 @@ public abstract class Trade implements Tradable, Model { TradeManager tradeManager, OpenOfferManager openOfferManager, User user, - KeyRing keyRing) { + KeyRing keyRing, + Coin fundsNeededForTrade) { Log.traceCall(); processModel.onAllServicesInitialized(offer, tradeManager, @@ -230,7 +231,8 @@ public abstract class Trade implements Tradable, Model { tradeWalletService, arbitratorManager, user, - keyRing); + keyRing, + fundsNeededForTrade); createProtocol(); diff --git a/core/src/main/java/io/bitsquare/trade/TradeManager.java b/core/src/main/java/io/bitsquare/trade/TradeManager.java index e23fef20f2..f285f940be 100644 --- a/core/src/main/java/io/bitsquare/trade/TradeManager.java +++ b/core/src/main/java/io/bitsquare/trade/TradeManager.java @@ -178,7 +178,7 @@ public class TradeManager { else {*/ trade.setStorage(tradableListStorage); trade.updateDepositTxFromWallet(tradeWalletService); - initTrade(trade); + initTrade(trade, trade.getProcessModel().getFundsNeededForTrade()); // } @@ -209,7 +209,7 @@ public class TradeManager { trade = new SellerAsOffererTrade(offer, tradableListStorage); trade.setStorage(tradableListStorage); - initTrade(trade); + initTrade(trade, trade.getProcessModel().getFundsNeededForTrade()); trades.add(trade); ((OffererTrade) trade).handleTakeOfferRequest(message, peerNodeAddress); } else { @@ -220,7 +220,7 @@ public class TradeManager { } } - private void initTrade(Trade trade) { + private void initTrade(Trade trade, Coin fundsNeededForTrade) { trade.init(p2PService, walletService, tradeWalletService, @@ -228,7 +228,8 @@ public class TradeManager { this, openOfferManager, user, - keyRing); + keyRing, + fundsNeededForTrade); } /////////////////////////////////////////////////////////////////////////////////////////// @@ -256,6 +257,7 @@ public class TradeManager { // First we check if offer is still available then we create the trade with the protocol public void onTakeOffer(Coin amount, + Coin fundsNeededForTrade, Offer offer, String paymentAccountId, TradeResultHandler tradeResultHandler) { @@ -263,11 +265,12 @@ public class TradeManager { offer.checkOfferAvailability(model, () -> { if (offer.getState() == Offer.State.AVAILABLE) - createTrade(amount, offer, paymentAccountId, model, tradeResultHandler); + createTrade(amount, fundsNeededForTrade, offer, paymentAccountId, model, tradeResultHandler); }); } private void createTrade(Coin amount, + Coin fundsNeededForTrade, Offer offer, String paymentAccountId, OfferAvailabilityModel model, @@ -282,7 +285,7 @@ public class TradeManager { trade.setTakeOfferDateAsBlockHeight(tradeWalletService.getBestChainHeight()); trade.setTakerPaymentAccountId(paymentAccountId); - initTrade(trade); + initTrade(trade, fundsNeededForTrade); trades.add(trade); ((TakerTrade) trade).takeAvailableOffer(); diff --git a/core/src/main/java/io/bitsquare/trade/offer/Offer.java b/core/src/main/java/io/bitsquare/trade/offer/Offer.java index 1b50251aa4..cc8061fe74 100644 --- a/core/src/main/java/io/bitsquare/trade/offer/Offer.java +++ b/core/src/main/java/io/bitsquare/trade/offer/Offer.java @@ -178,7 +178,7 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload // we don't need to fill it as the error message is only relevant locally, so we don't store it in the transmitted object errorMessageProperty = new SimpleStringProperty(); } catch (Throwable t) { - log.error("Cannot be deserialized." + t.getMessage()); + log.warn("Cannot be deserialized." + t.getMessage()); } } diff --git a/core/src/main/java/io/bitsquare/trade/offer/OpenOffer.java b/core/src/main/java/io/bitsquare/trade/offer/OpenOffer.java index a79e536070..ce4e715bed 100644 --- a/core/src/main/java/io/bitsquare/trade/offer/OpenOffer.java +++ b/core/src/main/java/io/bitsquare/trade/offer/OpenOffer.java @@ -65,7 +65,7 @@ public final class OpenOffer implements Tradable { setState(State.AVAILABLE); } catch (Throwable t) { - log.error("Cannot be deserialized." + t.getMessage()); + log.warn("Cannot be deserialized." + t.getMessage()); } } public Date getDate() { diff --git a/core/src/main/java/io/bitsquare/trade/offer/OpenOfferManager.java b/core/src/main/java/io/bitsquare/trade/offer/OpenOfferManager.java index 143857b57b..525c88f81c 100644 --- a/core/src/main/java/io/bitsquare/trade/offer/OpenOfferManager.java +++ b/core/src/main/java/io/bitsquare/trade/offer/OpenOfferManager.java @@ -44,6 +44,7 @@ import io.bitsquare.trade.protocol.placeoffer.PlaceOfferModel; import io.bitsquare.trade.protocol.placeoffer.PlaceOfferProtocol; import io.bitsquare.user.User; import javafx.collections.ObservableList; +import org.bitcoinj.core.Coin; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -227,9 +228,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe // API /////////////////////////////////////////////////////////////////////////////////////////// - public void placeOffer(Offer offer, - TransactionResultHandler resultHandler) { - PlaceOfferModel model = new PlaceOfferModel(offer, walletService, tradeWalletService, offerBookService, user); + public void placeOffer(Offer offer, Coin reservedFundsForOffer, boolean useSavingsWallet, TransactionResultHandler resultHandler) { + PlaceOfferModel model = new PlaceOfferModel(offer, reservedFundsForOffer, useSavingsWallet, walletService, tradeWalletService, offerBookService, user); PlaceOfferProtocol placeOfferProtocol = new PlaceOfferProtocol( model, transaction -> { @@ -272,6 +272,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe openOffer.setState(OpenOffer.State.CANCELED); openOffers.remove(openOffer); closedTradableManager.add(openOffer); + walletService.swapTradeToSavings(offer.getId()); resultHandler.handleResult(); }, errorMessageHandler); diff --git a/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/PlaceOfferModel.java b/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/PlaceOfferModel.java index 647b327d10..32a6ff49a9 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/PlaceOfferModel.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/PlaceOfferModel.java @@ -23,6 +23,7 @@ import io.bitsquare.common.taskrunner.Model; import io.bitsquare.trade.offer.Offer; import io.bitsquare.trade.offer.OfferBookService; import io.bitsquare.user.User; +import org.bitcoinj.core.Coin; import org.bitcoinj.core.Transaction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,6 +32,8 @@ public class PlaceOfferModel implements Model { private static final Logger log = LoggerFactory.getLogger(PlaceOfferModel.class); public final Offer offer; + public final Coin reservedFundsForOffer; + public final boolean useSavingsWallet; public final WalletService walletService; public final TradeWalletService tradeWalletService; public final OfferBookService offerBookService; @@ -39,11 +42,15 @@ public class PlaceOfferModel implements Model { private Transaction transaction; public PlaceOfferModel(Offer offer, + Coin reservedFundsForOffer, + boolean useSavingsWallet, WalletService walletService, TradeWalletService tradeWalletService, OfferBookService offerBookService, User user) { this.offer = offer; + this.reservedFundsForOffer = reservedFundsForOffer; + this.useSavingsWallet = useSavingsWallet; this.walletService = walletService; this.tradeWalletService = tradeWalletService; this.offerBookService = offerBookService; diff --git a/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/tasks/BroadcastCreateOfferFeeTx.java b/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/tasks/BroadcastCreateOfferFeeTx.java index f5204d8c84..0f0edaa3fa 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/tasks/BroadcastCreateOfferFeeTx.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/tasks/BroadcastCreateOfferFeeTx.java @@ -18,13 +18,10 @@ package io.bitsquare.trade.protocol.placeoffer.tasks; import com.google.common.util.concurrent.FutureCallback; -import io.bitsquare.btc.AddressEntry; -import io.bitsquare.btc.FeePolicy; import io.bitsquare.common.taskrunner.Task; import io.bitsquare.common.taskrunner.TaskRunner; import io.bitsquare.trade.offer.Offer; import io.bitsquare.trade.protocol.placeoffer.PlaceOfferModel; -import org.bitcoinj.core.Coin; import org.bitcoinj.core.Transaction; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; @@ -44,67 +41,59 @@ public class BroadcastCreateOfferFeeTx extends Task { protected void run() { try { runInterceptHook(); - Coin totalsNeeded = FeePolicy.getSecurityDeposit().add(FeePolicy.getCreateOfferFee()).add(FeePolicy.getFixedTxFeeForTrades()); - AddressEntry addressEntry = model.walletService.getTradeAddressEntry(model.offer.getId()); - Coin balance = model.walletService.getBalanceForAddress(addressEntry.getAddress()); - if (balance.compareTo(totalsNeeded) >= 0) { - model.tradeWalletService.broadcastTx(model.getTransaction(), new FutureCallback() { - @Override - public void onSuccess(Transaction transaction) { - log.info("Broadcast of offer fee payment succeeded: transaction = " + transaction.toString()); + model.tradeWalletService.broadcastTx(model.getTransaction(), new FutureCallback() { + @Override + public void onSuccess(Transaction transaction) { + log.info("Broadcast of offer fee payment succeeded: transaction = " + transaction.toString()); - if (model.getTransaction().getHashAsString().equals(transaction.getHashAsString())) { - model.offer.setState(Offer.State.OFFER_FEE_PAID); - // No tx malleability happened after broadcast (still not in blockchain) - complete(); - } else { - log.warn("Tx malleability happened after broadcast. We publish the changed offer to the P2P network again."); - // Tx malleability happened after broadcast. We first remove the malleable offer. - // Then we publish the changed offer to the P2P network again after setting the new TxId. - // Normally we use a delay for broadcasting to the peers, but at shut down we want to get it fast out - model.offerBookService.removeOffer(model.offer, - () -> { - log.info("We store now the changed txID to the offer and add that again."); - // We store now the changed txID to the offer and add that again. - model.offer.setOfferFeePaymentTxID(transaction.getHashAsString()); - model.setTransaction(transaction); - model.offerBookService.addOffer(model.offer, - () -> complete(), - errorMessage -> { - log.error("addOffer failed"); - addOfferFailed = true; - updateStateOnFault(); - model.offer.setErrorMessage("An error occurred when adding the offer to the P2P network.\n" + - "Error message:\n" - + errorMessage); - failed(errorMessage); - }); - }, - errorMessage -> { - log.error("removeOffer failed"); - removeOfferFailed = true; - updateStateOnFault(); - model.offer.setErrorMessage("An error occurred when removing the offer from the P2P network.\n" + - "Error message:\n" - + errorMessage); - failed(errorMessage); - }); - } + if (model.getTransaction().getHashAsString().equals(transaction.getHashAsString())) { + model.offer.setState(Offer.State.OFFER_FEE_PAID); + // No tx malleability happened after broadcast (still not in blockchain) + complete(); + } else { + log.warn("Tx malleability happened after broadcast. We publish the changed offer to the P2P network again."); + // Tx malleability happened after broadcast. We first remove the malleable offer. + // Then we publish the changed offer to the P2P network again after setting the new TxId. + // Normally we use a delay for broadcasting to the peers, but at shut down we want to get it fast out + model.offerBookService.removeOffer(model.offer, + () -> { + log.info("We store now the changed txID to the offer and add that again."); + // We store now the changed txID to the offer and add that again. + model.offer.setOfferFeePaymentTxID(transaction.getHashAsString()); + model.setTransaction(transaction); + model.offerBookService.addOffer(model.offer, + () -> complete(), + errorMessage -> { + log.error("addOffer failed"); + addOfferFailed = true; + updateStateOnFault(); + model.offer.setErrorMessage("An error occurred when adding the offer to the P2P network.\n" + + "Error message:\n" + + errorMessage); + failed(errorMessage); + }); + }, + errorMessage -> { + log.error("removeOffer failed"); + removeOfferFailed = true; + updateStateOnFault(); + model.offer.setErrorMessage("An error occurred when removing the offer from the P2P network.\n" + + "Error message:\n" + + errorMessage); + failed(errorMessage); + }); } + } - @Override - public void onFailure(@NotNull Throwable t) { - updateStateOnFault(); - model.offer.setErrorMessage("An error occurred.\n" + - "Error message:\n" - + t.getMessage()); - failed(t); - } - }); - } else { - updateStateOnFault(); - model.offer.setErrorMessage("You don't have enough balance in your wallet for placing the offer."); - } + @Override + public void onFailure(@NotNull Throwable t) { + updateStateOnFault(); + model.offer.setErrorMessage("An error occurred.\n" + + "Error message:\n" + + t.getMessage()); + failed(t); + } + }); } catch (Throwable t) { model.offer.setErrorMessage("An error occurred.\n" + "Error message:\n" diff --git a/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/tasks/CreateOfferFeeTx.java b/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/tasks/CreateOfferFeeTx.java index 213a9dd186..b39ee8624e 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/tasks/CreateOfferFeeTx.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/placeoffer/tasks/CreateOfferFeeTx.java @@ -28,6 +28,8 @@ import org.bitcoinj.core.Transaction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static com.google.common.base.Preconditions.checkNotNull; + public class CreateOfferFeeTx extends Task { private static final Logger log = LoggerFactory.getLogger(CreateOfferFeeTx.class); @@ -43,8 +45,12 @@ public class CreateOfferFeeTx extends Task { NodeAddress selectedArbitratorNodeAddress = ArbitrationSelectionRule.select(model.user.getAcceptedArbitratorAddresses(), model.offer); log.debug("selectedArbitratorAddress " + selectedArbitratorNodeAddress); Arbitrator selectedArbitrator = model.user.getAcceptedArbitratorByAddress(selectedArbitratorNodeAddress); + checkNotNull(selectedArbitrator, "selectedArbitrator must not be null at CreateOfferFeeTx"); Transaction transaction = model.tradeWalletService.createTradingFeeTx( model.walletService.getTradeAddressEntry(model.offer.getId()), + model.walletService.getUnusedSavingsAddressEntry().getAddress(), + model.reservedFundsForOffer, + model.useSavingsWallet, FeePolicy.getCreateOfferFee(), selectedArbitrator.getBtcAddress()); diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/BuyerAsOffererProtocol.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/BuyerAsOffererProtocol.java index 52aecc40fd..4362f26c93 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/BuyerAsOffererProtocol.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/BuyerAsOffererProtocol.java @@ -110,7 +110,7 @@ public class BuyerAsOffererProtocol extends TradeProtocol implements BuyerProtoc VerifyTakerAccount.class, LoadTakeOfferFeeTx.class, CreateAndSignContract.class, - CreateAndSignDepositTxAsBuyer.class, + OffererCreatesAndSignsDepositTxAsBuyer.class, InitWaitPeriodForOpenDispute.class, SetupDepositBalanceListener.class, SendPublishDepositTxRequest.class diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/BuyerAsTakerProtocol.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/BuyerAsTakerProtocol.java index f2ff132b7b..573a6d3d68 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/BuyerAsTakerProtocol.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/BuyerAsTakerProtocol.java @@ -95,7 +95,7 @@ public class BuyerAsTakerProtocol extends TradeProtocol implements BuyerProtocol LoadCreateOfferFeeTx.class, CreateTakeOfferFeeTx.class, BroadcastTakeOfferFeeTx.class, - CreateDepositTxInputsAsBuyer.class, + TakerCreatesDepositTxInputsAsBuyer.class, SendPayDepositRequest.class ); startTimeout(); diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/ProcessModel.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/ProcessModel.java index dc910b9612..9066443a35 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/ProcessModel.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/ProcessModel.java @@ -36,6 +36,8 @@ import io.bitsquare.trade.offer.Offer; import io.bitsquare.trade.offer.OpenOfferManager; import io.bitsquare.trade.protocol.trade.messages.TradeMessage; import io.bitsquare.user.User; +import org.bitcoinj.core.Address; +import org.bitcoinj.core.Coin; import org.bitcoinj.core.Transaction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -64,6 +66,7 @@ public class ProcessModel implements Model, Serializable { transient private KeyRing keyRing; transient private P2PService p2PService; + // Mutable public final TradingPeer tradingPeer; transient private TradeMessage tradeMessage; @@ -80,6 +83,8 @@ public class ProcessModel implements Model, Serializable { @Nullable private String changeOutputAddress; private Transaction takeOfferFeeTx; + public boolean useSavingsWallet; + private Coin fundsNeededForTrade; public ProcessModel() { tradingPeer = new TradingPeer(); @@ -89,7 +94,7 @@ public class ProcessModel implements Model, Serializable { try { in.defaultReadObject(); } catch (Throwable t) { - log.trace("Cannot be deserialized." + t.getMessage()); + log.warn("Cannot be deserialized." + t.getMessage()); } } @@ -101,7 +106,8 @@ public class ProcessModel implements Model, Serializable { TradeWalletService tradeWalletService, ArbitratorManager arbitratorManager, User user, - KeyRing keyRing) { + KeyRing keyRing, + Coin fundsNeededForTrade) { this.offer = offer; this.tradeManager = tradeManager; this.openOfferManager = openOfferManager; @@ -111,6 +117,7 @@ public class ProcessModel implements Model, Serializable { this.user = user; this.keyRing = keyRing; this.p2PService = p2PService; + this.fundsNeededForTrade = fundsNeededForTrade; } /////////////////////////////////////////////////////////////////////////////////////////// @@ -154,6 +161,10 @@ public class ProcessModel implements Model, Serializable { return p2PService.getAddress(); } + public Coin getFundsNeededForTrade() { + return fundsNeededForTrade; + } + /////////////////////////////////////////////////////////////////////////////////////////// // Getter/Setter for Mutable objects @@ -183,6 +194,10 @@ public class ProcessModel implements Model, Serializable { return walletService.getTradeAddressEntry(offer.getId()); } + public Address getUnusedSavingsAddress() { + return walletService.getUnusedSavingsAddressEntry().getAddress(); + } + public byte[] getTradeWalletPubKey() { return getAddressEntry().getPubKey(); } diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/SellerAsOffererProtocol.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/SellerAsOffererProtocol.java index 5a25d19c5d..178fa5d564 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/SellerAsOffererProtocol.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/SellerAsOffererProtocol.java @@ -111,7 +111,7 @@ public class SellerAsOffererProtocol extends TradeProtocol implements SellerProt LoadTakeOfferFeeTx.class, InitWaitPeriodForOpenDispute.class, CreateAndSignContract.class, - CreateAndSignDepositTxAsSeller.class, + OffererCreatesAndSignsDepositTxAsSeller.class, SetupDepositBalanceListener.class, SendPublishDepositTxRequest.class ); diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/SellerAsTakerProtocol.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/SellerAsTakerProtocol.java index 481975f771..c7cb5bee7f 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/SellerAsTakerProtocol.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/SellerAsTakerProtocol.java @@ -103,7 +103,7 @@ public class SellerAsTakerProtocol extends TradeProtocol implements SellerProtoc LoadCreateOfferFeeTx.class, CreateTakeOfferFeeTx.class, BroadcastTakeOfferFeeTx.class, - CreateDepositTxInputsAsSeller.class, + TakerCreatesDepositTxInputsAsSeller.class, SendPayDepositRequest.class ); startTimeout(); diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/TradingPeer.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/TradingPeer.java index b870268342..3d36016763 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/TradingPeer.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/TradingPeer.java @@ -64,7 +64,7 @@ public final class TradingPeer implements Persistable { try { in.defaultReadObject(); } catch (Throwable t) { - log.trace("Cannot be deserialized." + t.getMessage()); + log.warn("Cannot be deserialized." + t.getMessage()); } } diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/CreateAndSignDepositTxAsBuyer.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/OffererCreatesAndSignsDepositTxAsBuyer.java similarity index 90% rename from core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/CreateAndSignDepositTxAsBuyer.java rename to core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/OffererCreatesAndSignsDepositTxAsBuyer.java index d4168ca471..e59835296d 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/CreateAndSignDepositTxAsBuyer.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/OffererCreatesAndSignsDepositTxAsBuyer.java @@ -29,10 +29,10 @@ import org.slf4j.LoggerFactory; import static com.google.common.base.Preconditions.checkNotNull; -public class CreateAndSignDepositTxAsBuyer extends TradeTask { - private static final Logger log = LoggerFactory.getLogger(CreateAndSignDepositTxAsBuyer.class); +public class OffererCreatesAndSignsDepositTxAsBuyer extends TradeTask { + private static final Logger log = LoggerFactory.getLogger(OffererCreatesAndSignsDepositTxAsBuyer.class); - public CreateAndSignDepositTxAsBuyer(TaskRunner taskHandler, Trade trade) { + public OffererCreatesAndSignsDepositTxAsBuyer(TaskRunner taskHandler, Trade trade) { super(taskHandler, trade); } @@ -60,6 +60,7 @@ public class CreateAndSignDepositTxAsBuyer extends TradeTask { processModel.tradingPeer.getChangeOutputValue(), processModel.tradingPeer.getChangeOutputAddress(), processModel.getAddressEntry(), + processModel.getUnusedSavingsAddress(), processModel.getTradeWalletPubKey(), processModel.tradingPeer.getTradeWalletPubKey(), processModel.getArbitratorPubKey(trade.getArbitratorNodeAddress())); diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/CreateDepositTxInputsAsBuyer.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/TakerCreatesDepositTxInputsAsBuyer.java similarity index 79% rename from core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/CreateDepositTxInputsAsBuyer.java rename to core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/TakerCreatesDepositTxInputsAsBuyer.java index 5e4786d976..8a25486294 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/CreateDepositTxInputsAsBuyer.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/buyer/TakerCreatesDepositTxInputsAsBuyer.java @@ -26,10 +26,10 @@ import org.bitcoinj.core.Coin; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class CreateDepositTxInputsAsBuyer extends TradeTask { - private static final Logger log = LoggerFactory.getLogger(CreateDepositTxInputsAsBuyer.class); +public class TakerCreatesDepositTxInputsAsBuyer extends TradeTask { + private static final Logger log = LoggerFactory.getLogger(TakerCreatesDepositTxInputsAsBuyer.class); - public CreateDepositTxInputsAsBuyer(TaskRunner taskHandler, Trade trade) { + public TakerCreatesDepositTxInputsAsBuyer(TaskRunner taskHandler, Trade trade) { super(taskHandler, trade); } @@ -38,7 +38,9 @@ public class CreateDepositTxInputsAsBuyer extends TradeTask { try { runInterceptHook(); Coin takerInputAmount = FeePolicy.getSecurityDeposit().add(FeePolicy.getFixedTxFeeForTrades()); - InputsAndChangeOutput result = processModel.getTradeWalletService().takerCreatesDepositsTxInputs(takerInputAmount, processModel.getAddressEntry()); + InputsAndChangeOutput result = processModel.getTradeWalletService() + .takerCreatesDepositsTxInputs(takerInputAmount, processModel.getAddressEntry(), + processModel.getUnusedSavingsAddress()); processModel.setRawTransactionInputs(result.rawTransactionInputs); processModel.setChangeOutputValue(result.changeOutputValue); processModel.setChangeOutputAddress(result.changeOutputAddress); diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/CreateAndSignDepositTxAsSeller.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/OffererCreatesAndSignsDepositTxAsSeller.java similarity index 90% rename from core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/CreateAndSignDepositTxAsSeller.java rename to core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/OffererCreatesAndSignsDepositTxAsSeller.java index aa0c5467b6..c35a982786 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/CreateAndSignDepositTxAsSeller.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/OffererCreatesAndSignsDepositTxAsSeller.java @@ -29,10 +29,10 @@ import org.slf4j.LoggerFactory; import static com.google.common.base.Preconditions.checkNotNull; -public class CreateAndSignDepositTxAsSeller extends TradeTask { - private static final Logger log = LoggerFactory.getLogger(CreateAndSignDepositTxAsSeller.class); +public class OffererCreatesAndSignsDepositTxAsSeller extends TradeTask { + private static final Logger log = LoggerFactory.getLogger(OffererCreatesAndSignsDepositTxAsSeller.class); - public CreateAndSignDepositTxAsSeller(TaskRunner taskHandler, Trade trade) { + public OffererCreatesAndSignsDepositTxAsSeller(TaskRunner taskHandler, Trade trade) { super(taskHandler, trade); } @@ -60,6 +60,7 @@ public class CreateAndSignDepositTxAsSeller extends TradeTask { processModel.tradingPeer.getChangeOutputValue(), processModel.tradingPeer.getChangeOutputAddress(), processModel.getAddressEntry(), + processModel.getUnusedSavingsAddress(), processModel.tradingPeer.getTradeWalletPubKey(), processModel.getTradeWalletPubKey(), processModel.getArbitratorPubKey(trade.getArbitratorNodeAddress())); diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/CreateDepositTxInputsAsSeller.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/TakerCreatesDepositTxInputsAsSeller.java similarity index 79% rename from core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/CreateDepositTxInputsAsSeller.java rename to core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/TakerCreatesDepositTxInputsAsSeller.java index bb6ac807df..b381fd3ac5 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/CreateDepositTxInputsAsSeller.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/seller/TakerCreatesDepositTxInputsAsSeller.java @@ -26,10 +26,10 @@ import org.bitcoinj.core.Coin; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class CreateDepositTxInputsAsSeller extends TradeTask { - private static final Logger log = LoggerFactory.getLogger(CreateDepositTxInputsAsSeller.class); +public class TakerCreatesDepositTxInputsAsSeller extends TradeTask { + private static final Logger log = LoggerFactory.getLogger(TakerCreatesDepositTxInputsAsSeller.class); - public CreateDepositTxInputsAsSeller(TaskRunner taskHandler, Trade trade) { + public TakerCreatesDepositTxInputsAsSeller(TaskRunner taskHandler, Trade trade) { super(taskHandler, trade); } @@ -40,8 +40,10 @@ public class CreateDepositTxInputsAsSeller extends TradeTask { if (trade.getTradeAmount() != null) { Coin takerInputAmount = FeePolicy.getSecurityDeposit().add(FeePolicy.getFixedTxFeeForTrades()).add(trade.getTradeAmount()); - InputsAndChangeOutput result = processModel.getTradeWalletService().takerCreatesDepositsTxInputs(takerInputAmount, processModel - .getAddressEntry()); + InputsAndChangeOutput result = processModel.getTradeWalletService() + .takerCreatesDepositsTxInputs(takerInputAmount, + processModel.getAddressEntry(), + processModel.getUnusedSavingsAddress()); processModel.setRawTransactionInputs(result.rawTransactionInputs); processModel.setChangeOutputValue(result.changeOutputValue); processModel.setChangeOutputAddress(result.changeOutputAddress); diff --git a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/taker/CreateTakeOfferFeeTx.java b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/taker/CreateTakeOfferFeeTx.java index e731f7ed6e..4e4141f713 100644 --- a/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/taker/CreateTakeOfferFeeTx.java +++ b/core/src/main/java/io/bitsquare/trade/protocol/trade/tasks/taker/CreateTakeOfferFeeTx.java @@ -29,6 +29,8 @@ import org.bitcoinj.core.Transaction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static com.google.common.base.Preconditions.checkNotNull; + public class CreateTakeOfferFeeTx extends TradeTask { private static final Logger log = LoggerFactory.getLogger(CreateTakeOfferFeeTx.class); @@ -45,8 +47,12 @@ public class CreateTakeOfferFeeTx extends TradeTask { NodeAddress selectedArbitratorNodeAddress = ArbitrationSelectionRule.select(user.getAcceptedArbitratorAddresses(), processModel.getOffer()); log.debug("selectedArbitratorAddress " + selectedArbitratorNodeAddress); Arbitrator selectedArbitrator = user.getAcceptedArbitratorByAddress(selectedArbitratorNodeAddress); + checkNotNull(selectedArbitrator, "selectedArbitrator must not be null at CreateTakeOfferFeeTx"); Transaction createTakeOfferFeeTx = processModel.getTradeWalletService().createTradingFeeTx( processModel.getAddressEntry(), + processModel.getUnusedSavingsAddress(), + processModel.getFundsNeededForTrade(), + processModel.useSavingsWallet, FeePolicy.getTakeOfferFee(), selectedArbitrator.getBtcAddress()); diff --git a/gui/src/main/java/io/bitsquare/app/BitsquareApp.java b/gui/src/main/java/io/bitsquare/app/BitsquareApp.java index ac7d3afc6f..41f340da66 100644 --- a/gui/src/main/java/io/bitsquare/app/BitsquareApp.java +++ b/gui/src/main/java/io/bitsquare/app/BitsquareApp.java @@ -75,7 +75,7 @@ import static io.bitsquare.app.BitsquareEnvironment.APP_NAME_KEY; public class BitsquareApp extends Application { private static final Logger log = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(BitsquareApp.class); - public static final boolean DEV_MODE = false; + public static final boolean DEV_MODE = true; public static final boolean IS_RELEASE_VERSION = !DEV_MODE && true; private static Environment env; diff --git a/gui/src/main/java/io/bitsquare/gui/bitsquare.css b/gui/src/main/java/io/bitsquare/gui/bitsquare.css index 888545e22d..2aef7a0a6d 100644 --- a/gui/src/main/java/io/bitsquare/gui/bitsquare.css +++ b/gui/src/main/java/io/bitsquare/gui/bitsquare.css @@ -201,6 +201,12 @@ bg color of non edit textFields: fafafa -fx-cursor: hand; } +.internal-funds-icon { + -fx-text-fill: #999; + -fx-cursor: hand; +} + + /******************************************************************************* * * * Tooltip * @@ -1000,3 +1006,7 @@ textfield */ -fx-text-fill: white; -fx-cursor: hand; } + +#popup-qr-code-info { + -fx-font-size: 11; +} \ No newline at end of file diff --git a/gui/src/main/java/io/bitsquare/gui/components/AddressWithIconAndDirection.java b/gui/src/main/java/io/bitsquare/gui/components/AddressWithIconAndDirection.java index add6fec473..fe3f5cfb58 100644 --- a/gui/src/main/java/io/bitsquare/gui/components/AddressWithIconAndDirection.java +++ b/gui/src/main/java/io/bitsquare/gui/components/AddressWithIconAndDirection.java @@ -21,11 +21,16 @@ public class AddressWithIconAndDirection extends AnchorPane { private final Label directionIcon; private final Label label; - public AddressWithIconAndDirection(String text, String address, AwesomeIcon awesomeIcon, boolean received) { + public AddressWithIconAndDirection(String text, String address, AwesomeIcon awesomeIcon, boolean received, boolean isInternal) { directionIcon = new Label(); directionIcon.setLayoutY(3); - directionIcon.getStyleClass().add(received ? "received-funds-icon" : "sent-funds-icon"); - AwesomeDude.setIcon(directionIcon, received ? AwesomeIcon.SIGNIN : AwesomeIcon.SIGNOUT); + if (isInternal) { + directionIcon.getStyleClass().add("internal-funds-icon"); + AwesomeDude.setIcon(directionIcon, AwesomeIcon.REPEAT); + } else { + directionIcon.getStyleClass().add(received ? "received-funds-icon" : "sent-funds-icon"); + AwesomeDude.setIcon(directionIcon, received ? AwesomeIcon.SIGNIN : AwesomeIcon.SIGNOUT); + } directionIcon.setMouseTransparent(true); HBox hBox = new HBox(); @@ -52,7 +57,7 @@ public class AddressWithIconAndDirection extends AnchorPane { AnchorPane.setLeftAnchor(directionIcon, 3.0); AnchorPane.setTopAnchor(directionIcon, 2.0); - AnchorPane.setLeftAnchor(hBox, 20.0); + AnchorPane.setLeftAnchor(hBox, 22.0); AnchorPane.setRightAnchor(hBox, 15.0); AnchorPane.setRightAnchor(openLinkIcon, 4.0); AnchorPane.setTopAnchor(openLinkIcon, 3.0); diff --git a/gui/src/main/java/io/bitsquare/gui/components/BalanceTextField.java b/gui/src/main/java/io/bitsquare/gui/components/BalanceTextField.java index 601cd38340..1e23174c0e 100644 --- a/gui/src/main/java/io/bitsquare/gui/components/BalanceTextField.java +++ b/gui/src/main/java/io/bitsquare/gui/components/BalanceTextField.java @@ -60,9 +60,11 @@ public class BalanceTextField extends AnchorPane { getChildren().addAll(textField); } - public void setup(Address address, BSFormatter formatter) { + public void setFormatter(BSFormatter formatter) { this.formatter = formatter; + } + public void setupBalanceListener(Address address) { balanceListener = new BalanceListener(address) { @Override public void onBalanceChanged(Coin balance, Transaction tx) { @@ -74,16 +76,21 @@ public class BalanceTextField extends AnchorPane { } public void cleanup() { - walletService.removeBalanceListener(balanceListener); + if (balanceListener != null) + walletService.removeBalanceListener(balanceListener); } + public void setBalance(Coin balance) { + updateBalance(balance); + } /////////////////////////////////////////////////////////////////////////////////////////// // Private methods /////////////////////////////////////////////////////////////////////////////////////////// private void updateBalance(Coin balance) { - textField.setText(formatter.formatCoinWithCode(balance)); + if (formatter != null) + textField.setText(formatter.formatCoinWithCode(balance)); if (balance.isPositive()) textField.setEffect(fundedEffect); else diff --git a/gui/src/main/java/io/bitsquare/gui/main/MainViewModel.java b/gui/src/main/java/io/bitsquare/gui/main/MainViewModel.java index a28d74a6e4..b7787d697a 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/MainViewModel.java +++ b/gui/src/main/java/io/bitsquare/gui/main/MainViewModel.java @@ -473,7 +473,7 @@ public class MainViewModel implements ViewModel { if (tuple.payload instanceof Ping && ((Ping) tuple.payload).nonce == payload.nonce && ((Ping) tuple.payload).lastRoundTripTime == payload.lastRoundTripTime) - log.trace("Crypto test succeeded"); + log.debug("Crypto test succeeded"); else throw new CryptoException("Payload not correct after decryption"); } catch (CryptoException e) { @@ -494,9 +494,7 @@ public class MainViewModel implements ViewModel { } }; - // Delay a bit the test, there was one bug report (Key length not 128//192/256 bits) where the crypto test failed. - // TODO investigate - UserThread.runAfter(() -> checkCryptoThread.start(), 3); + checkCryptoThread.start(); } diff --git a/gui/src/main/java/io/bitsquare/gui/main/account/content/arbitratorselection/ArbitratorSelectionView.java b/gui/src/main/java/io/bitsquare/gui/main/account/content/arbitratorselection/ArbitratorSelectionView.java index 0fef3bbc80..d13283cd23 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/account/content/arbitratorselection/ArbitratorSelectionView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/account/content/arbitratorselection/ArbitratorSelectionView.java @@ -51,7 +51,7 @@ public class ArbitratorSelectionView extends ActivatableViewAndModel languagesListView; private ComboBox languageComboBox; - private TableView table; + private TableView tableView; private int gridRow = 0; private CheckBox autoSelectAllMatchingCheckBox; private ListChangeListener listChangeListener; @@ -83,7 +83,7 @@ public class ArbitratorSelectionView extends ActivatableViewAndModel(); - GridPane.setRowIndex(table, gridRow); - GridPane.setColumnSpan(table, 2); - GridPane.setMargin(table, new Insets(60, -10, 5, -10)); - root.getChildren().add(table); + tableView = new TableView<>(); + GridPane.setRowIndex(tableView, gridRow); + GridPane.setColumnSpan(tableView, 2); + GridPane.setMargin(tableView, new Insets(60, -10, 5, -10)); + root.getChildren().add(tableView); autoSelectAllMatchingCheckBox = addCheckBox(root, ++gridRow, "Auto select all arbitrators with matching language"); GridPane.setColumnSpan(autoSelectAllMatchingCheckBox, 2); @@ -202,15 +202,18 @@ public class ArbitratorSelectionView extends ActivatableViewAndModel model.setAutoSelectArbitrators(autoSelectAllMatchingCheckBox.isSelected())); TableColumn dateColumn = new TableColumn("Registration date"); + dateColumn.setSortable(false); dateColumn.setCellValueFactory(param -> new ReadOnlyObjectWrapper(param.getValue().getRegistrationDate())); dateColumn.setMinWidth(130); dateColumn.setMaxWidth(130); TableColumn nameColumn = new TableColumn("Onion address"); + nameColumn.setSortable(false); nameColumn.setCellValueFactory(param -> new ReadOnlyObjectWrapper(param.getValue().getAddressString())); nameColumn.setMinWidth(90); TableColumn languagesColumn = new TableColumn("Languages"); + languagesColumn.setSortable(false); languagesColumn.setCellValueFactory(param -> new ReadOnlyObjectWrapper(param.getValue().getLanguageCodes())); languagesColumn.setMinWidth(130); @@ -309,8 +312,8 @@ public class ArbitratorSelectionView extends ActivatableViewAndModel { private final List tempAttachments = new ArrayList<>(); - private TableView disputesTable; + private TableView tableView; + private SortedList sortedList; + private Dispute selectedDispute; private ListView messageListView; private TextArea inputTextArea; @@ -133,28 +135,32 @@ public class TraderDisputeView extends ActivatableView { @Override public void initialize() { - disputesTable = new TableView<>(); - VBox.setVgrow(disputesTable, Priority.SOMETIMES); - disputesTable.setMinHeight(150); - root.getChildren().add(disputesTable); - - TableColumn tradeIdColumn = getTradeIdColumn(); - disputesTable.getColumns().add(tradeIdColumn); - TableColumn roleColumn = getRoleColumn(); - disputesTable.getColumns().add(roleColumn); - TableColumn dateColumn = getDateColumn(); - disputesTable.getColumns().add(dateColumn); - TableColumn contractColumn = getContractColumn(); - disputesTable.getColumns().add(contractColumn); - TableColumn stateColumn = getStateColumn(); - disputesTable.getColumns().add(stateColumn); - - disputesTable.getSortOrder().add(dateColumn); - disputesTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); + tableView = new TableView<>(); + VBox.setVgrow(tableView, Priority.SOMETIMES); + tableView.setMinHeight(150); + root.getChildren().add(tableView); + tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); Label placeholder = new Label("There are no open tickets"); placeholder.setWrapText(true); - disputesTable.setPlaceholder(placeholder); - disputesTable.getSelectionModel().clearSelection(); + tableView.setPlaceholder(placeholder); + tableView.getSelectionModel().clearSelection(); + + TableColumn tradeIdColumn = getTradeIdColumn(); + tableView.getColumns().add(tradeIdColumn); + TableColumn roleColumn = getRoleColumn(); + tableView.getColumns().add(roleColumn); + TableColumn dateColumn = getDateColumn(); + tableView.getColumns().add(dateColumn); + TableColumn contractColumn = getContractColumn(); + tableView.getColumns().add(contractColumn); + TableColumn stateColumn = getStateColumn(); + tableView.getColumns().add(stateColumn); + + tradeIdColumn.setComparator((o1, o2) -> o1.getTradeId().compareTo(o2.getTradeId())); + dateColumn.setComparator((o1, o2) -> o1.getOpeningDate().compareTo(o2.getOpeningDate())); + + dateColumn.setSortType(TableColumn.SortType.DESCENDING); + tableView.getSortOrder().add(dateColumn); /*inputTextAreaListener = (observable, oldValue, newValue) -> sendButton.setDisable(newValue.length() == 0 @@ -183,24 +189,27 @@ public class TraderDisputeView extends ActivatableView { @Override protected void activate() { + FilteredList filteredList = new FilteredList<>(disputeManager.getDisputesAsObservableList()); setFilteredListPredicate(filteredList); - SortedList sortedList = new SortedList<>(filteredList); - // sortedList.setComparator((o1, o2) -> o2.getOpeningDate().compareTo(o1.getOpeningDate())); - sortedList.comparatorProperty().bind(disputesTable.comparatorProperty()); - disputesTable.setItems(sortedList); - disputesTable.sort(); - selectedDisputeSubscription = EasyBind.subscribe(disputesTable.getSelectionModel().selectedItemProperty(), this::onSelectDispute); - Dispute selectedItem = disputesTable.getSelectionModel().getSelectedItem(); + sortedList = new SortedList<>(filteredList); + sortedList.comparatorProperty().bind(tableView.comparatorProperty()); + tableView.setItems(sortedList); + + // sortedList.setComparator((o1, o2) -> o2.getOpeningDate().compareTo(o1.getOpeningDate())); + selectedDisputeSubscription = EasyBind.subscribe(tableView.getSelectionModel().selectedItemProperty(), this::onSelectDispute); + + Dispute selectedItem = tableView.getSelectionModel().getSelectedItem(); if (selectedItem != null) - disputesTable.getSelectionModel().select(selectedItem); + tableView.getSelectionModel().select(selectedItem); scrollToBottom(); } @Override protected void deactivate() { + sortedList.comparatorProperty().unbind(); selectedDisputeSubscription.unsubscribe(); removeListenersOnSelectDispute(); } @@ -752,6 +761,7 @@ public class TraderDisputeView extends ActivatableView { setMinWidth(130); } }; + column.setSortable(false); column.setCellValueFactory((dispute) -> new ReadOnlyObjectWrapper<>(dispute.getValue())); column.setCellFactory( new Callback, TableCell>() { @@ -809,6 +819,7 @@ public class TraderDisputeView extends ActivatableView { setSortable(false); } }; + column.setSortable(false); column.setCellValueFactory((dispute) -> new ReadOnlyObjectWrapper<>(dispute.getValue())); column.setCellFactory( new Callback, TableCell>() { @@ -846,6 +857,7 @@ public class TraderDisputeView extends ActivatableView { setMinWidth(50); } }; + column.setSortable(false); column.setCellValueFactory((dispute) -> new ReadOnlyObjectWrapper<>(dispute.getValue())); column.setCellFactory( new Callback, TableCell>() { diff --git a/gui/src/main/java/io/bitsquare/gui/main/funds/deposit/DepositListItem.java b/gui/src/main/java/io/bitsquare/gui/main/funds/deposit/DepositListItem.java index 6019f81a4b..60d1a2ec65 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/funds/deposit/DepositListItem.java +++ b/gui/src/main/java/io/bitsquare/gui/main/funds/deposit/DepositListItem.java @@ -37,6 +37,8 @@ public class DepositListItem { private final Logger log = LoggerFactory.getLogger(this.getClass()); private final StringProperty balance = new SimpleStringProperty(); private final WalletService walletService; + + private Coin balanceAsCoin; private BSFormatter formatter; private final ConfidenceProgressIndicator progressIndicator; private final Tooltip tooltip; @@ -44,8 +46,9 @@ public class DepositListItem { private String balanceString; private String addressString; - private String status = "Unused"; + private String usage = "-"; private TxConfidenceListener txConfidenceListener; + private int numTxOutputs = 0; // public DepositListItem(AddressEntry addressEntry, Transaction transaction, WalletService walletService, Optional tradableOptional, BSFormatter formatter) { public DepositListItem(AddressEntry addressEntry, WalletService walletService, BSFormatter formatter) { @@ -67,17 +70,17 @@ public class DepositListItem { walletService.addBalanceListener(new BalanceListener(address) { @Override public void onBalanceChanged(Coin balanceAsCoin, Transaction tx) { + DepositListItem.this.balanceAsCoin = balanceAsCoin; DepositListItem.this.balance.set(formatter.formatCoin(balanceAsCoin)); updateConfidence(walletService.getConfidenceForTxId(tx.getHashAsString())); - if (balanceAsCoin.isPositive()) - status = "Funded"; + updateUsage(address); } }); - Coin balanceAsCoin = walletService.getBalanceForAddress(address); + balanceAsCoin = walletService.getBalanceForAddress(address); balance.set(formatter.formatCoin(balanceAsCoin)); - if (balanceAsCoin.isPositive()) - status = "Funded"; + + updateUsage(address); TransactionConfidence transactionConfidence = walletService.getConfidenceForAddress(address); if (transactionConfidence != null) { @@ -93,8 +96,9 @@ public class DepositListItem { } } - public void setStatus(String status) { - this.status = status; + private void updateUsage(Address address) { + numTxOutputs = walletService.getNumTxOutputsForAddress(address); + usage = numTxOutputs == 0 ? "Unused" : "Used in " + numTxOutputs + " transactions"; } public void cleanup() { @@ -134,8 +138,8 @@ public class DepositListItem { return addressString; } - public String getStatus() { - return status; + public String getUsage() { + return usage; } public final StringProperty balanceProperty() { @@ -146,4 +150,11 @@ public class DepositListItem { return balance.get(); } + public Coin getBalanceAsCoin() { + return balanceAsCoin; + } + + public int getNumTxOutputs() { + return numTxOutputs; + } } diff --git a/gui/src/main/java/io/bitsquare/gui/main/funds/deposit/DepositView.fxml b/gui/src/main/java/io/bitsquare/gui/main/funds/deposit/DepositView.fxml index 8b2bc84b39..f40111f9c6 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/funds/deposit/DepositView.fxml +++ b/gui/src/main/java/io/bitsquare/gui/main/funds/deposit/DepositView.fxml @@ -18,7 +18,6 @@ --> - - + - - - - - - - - - + + + + diff --git a/gui/src/main/java/io/bitsquare/gui/main/funds/deposit/DepositView.java b/gui/src/main/java/io/bitsquare/gui/main/funds/deposit/DepositView.java index d314a94bd8..80a2527f60 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/funds/deposit/DepositView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/funds/deposit/DepositView.java @@ -18,7 +18,9 @@ package io.bitsquare.gui.main.funds.deposit; import de.jensd.fx.fontawesome.AwesomeIcon; +import io.bitsquare.app.BitsquareApp; import io.bitsquare.btc.AddressEntry; +import io.bitsquare.btc.Restrictions; import io.bitsquare.btc.WalletService; import io.bitsquare.btc.listeners.BalanceListener; import io.bitsquare.common.util.Tuple2; @@ -27,26 +29,20 @@ import io.bitsquare.gui.common.view.ActivatableView; import io.bitsquare.gui.common.view.FxmlView; import io.bitsquare.gui.components.AddressTextField; import io.bitsquare.gui.components.HyperlinkWithIcon; +import io.bitsquare.gui.components.InputTextField; import io.bitsquare.gui.components.TitledGroupBg; import io.bitsquare.gui.main.overlays.popups.Popup; -import io.bitsquare.gui.main.overlays.windows.OfferDetailsWindow; import io.bitsquare.gui.main.overlays.windows.QRCodeWindow; -import io.bitsquare.gui.main.overlays.windows.TradeDetailsWindow; -import io.bitsquare.gui.main.overlays.windows.WalletPasswordWindow; import io.bitsquare.gui.util.BSFormatter; import io.bitsquare.gui.util.Layout; -import io.bitsquare.gui.util.validation.BtcAddressValidator; -import io.bitsquare.trade.TradeManager; -import io.bitsquare.trade.closed.ClosedTradableManager; -import io.bitsquare.trade.failed.FailedTradesManager; -import io.bitsquare.trade.offer.OpenOfferManager; import io.bitsquare.user.Preferences; import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.collections.FXCollections; import javafx.collections.ObservableList; +import javafx.collections.transformation.SortedList; import javafx.fxml.FXML; +import javafx.geometry.HPos; import javafx.geometry.Insets; -import javafx.geometry.VPos; import javafx.scene.control.*; import javafx.scene.image.Image; import javafx.scene.image.ImageView; @@ -58,6 +54,8 @@ import net.glxn.qrgen.image.ImageType; import org.bitcoinj.core.Coin; import org.bitcoinj.core.Transaction; import org.bitcoinj.uri.BitcoinURI; +import org.fxmisc.easybind.EasyBind; +import org.fxmisc.easybind.Subscription; import org.jetbrains.annotations.NotNull; import javax.inject.Inject; @@ -72,30 +70,27 @@ public class DepositView extends ActivatableView { GridPane gridPane; @FXML - TableView table; + TableView tableView; @FXML - TableColumn selectColumn, addressColumn, balanceColumn, confidenceColumn, statusColumn; + TableColumn selectColumn, addressColumn, balanceColumn, confidenceColumn, usageColumn; private ImageView qrCodeImageView; private int gridRow = 0; private AddressTextField addressTextField; Button generateNewAddressButton; private final WalletService walletService; - private final TradeManager tradeManager; - private final ClosedTradableManager closedTradableManager; - private final FailedTradesManager failedTradesManager; - private final OpenOfferManager openOfferManager; private final BSFormatter formatter; private final Preferences preferences; - private final BtcAddressValidator btcAddressValidator; - private final WalletPasswordWindow walletPasswordWindow; - private final OfferDetailsWindow offerDetailsWindow; - private final TradeDetailsWindow tradeDetailsWindow; - private final ObservableList depositAddresses = FXCollections.observableArrayList(); + private final ObservableList observableList = FXCollections.observableArrayList(); + private final SortedList sortedList = new SortedList<>(observableList); + private BalanceListener balanceListener; private TitledGroupBg titledGroupBg; - private Label addressLabel; + private Label addressLabel, amountLabel; private Label qrCodeLabel; + private InputTextField amountTextField; + private Subscription amountTextFieldSubscription; + private String paymentLabel; /////////////////////////////////////////////////////////////////////////////////////////// @@ -103,36 +98,34 @@ public class DepositView extends ActivatableView { /////////////////////////////////////////////////////////////////////////////////////////// @Inject - private DepositView(WalletService walletService, TradeManager tradeManager, - ClosedTradableManager closedTradableManager, - FailedTradesManager failedTradesManager, OpenOfferManager openOfferManager, - BSFormatter formatter, Preferences preferences, - BtcAddressValidator btcAddressValidator, WalletPasswordWindow walletPasswordWindow, - OfferDetailsWindow offerDetailsWindow, TradeDetailsWindow tradeDetailsWindow) { + private DepositView(WalletService walletService, + BSFormatter formatter, + Preferences preferences) { this.walletService = walletService; - this.tradeManager = tradeManager; - this.closedTradableManager = closedTradableManager; - this.failedTradesManager = failedTradesManager; - this.openOfferManager = openOfferManager; this.formatter = formatter; this.preferences = preferences; - this.btcAddressValidator = btcAddressValidator; - this.walletPasswordWindow = walletPasswordWindow; - this.offerDetailsWindow = offerDetailsWindow; - this.tradeDetailsWindow = tradeDetailsWindow; } @Override public void initialize() { - table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); - table.setPlaceholder(new Label("No deposit addresses are generated yet")); + tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); + tableView.setPlaceholder(new Label("No deposit addresses are generated yet")); setSelectColumnCellFactory(); setAddressColumnCellFactory(); - setStatusColumnCellFactory(); + setBalanceColumnCellFactory(); + setUsageColumnCellFactory(); setConfidenceColumnCellFactory(); - titledGroupBg = addTitledGroupBg(gridPane, gridRow, 2, "Fund your wallet"); + addressColumn.setComparator((o1, o2) -> o1.getAddressString().compareTo(o2.getAddressString())); + balanceColumn.setComparator((o1, o2) -> o1.getBalanceAsCoin().compareTo(o2.getBalanceAsCoin())); + confidenceColumn.setComparator((o1, o2) -> Double.valueOf(o1.getProgressIndicator().getProgress()) + .compareTo(o2.getProgressIndicator().getProgress())); + usageColumn.setComparator((a, b) -> (a.getNumTxOutputs() < b.getNumTxOutputs()) ? -1 : ((a.getNumTxOutputs() == b.getNumTxOutputs()) ? 0 : 1)); + tableView.getSortOrder().add(usageColumn); + + + titledGroupBg = addTitledGroupBg(gridPane, gridRow, 3, "Fund your wallet"); qrCodeLabel = addLabel(gridPane, gridRow, "QR-Code:", 0); //GridPane.setMargin(qrCodeLabel, new Insets(Layout.FIRST_ROW_DISTANCE - 9, 0, 0, 5)); @@ -148,9 +141,18 @@ public class DepositView extends ActivatableView { Tuple2 addressTuple = addLabelAddressTextField(gridPane, ++gridRow, "Address:"); addressLabel = addressTuple.first; - GridPane.setValignment(addressLabel, VPos.TOP); - GridPane.setMargin(addressLabel, new Insets(3, 0, 0, 0)); + //GridPane.setValignment(addressLabel, VPos.TOP); + //GridPane.setMargin(addressLabel, new Insets(3, 0, 0, 0)); addressTextField = addressTuple.second; + paymentLabel = "Fund Bitsquare wallet"; + addressTextField.setPaymentLabel(paymentLabel); + + + Tuple2 amountTuple = addLabelInputTextField(gridPane, ++gridRow, "Amount in BTC (optional):"); + amountLabel = amountTuple.first; + amountTextField = amountTuple.second; + if (BitsquareApp.DEV_MODE) + amountTextField.setText("1"); titledGroupBg.setVisible(false); titledGroupBg.setManaged(false); @@ -162,16 +164,18 @@ public class DepositView extends ActivatableView { addressLabel.setManaged(false); addressTextField.setVisible(false); addressTextField.setManaged(false); + amountLabel.setVisible(false); + amountTextField.setManaged(false); generateNewAddressButton = addButton(gridPane, ++gridRow, "Generate new address", -20); + GridPane.setColumnIndex(generateNewAddressButton, 0); + GridPane.setHalignment(generateNewAddressButton, HPos.LEFT); generateNewAddressButton.setOnAction(event -> { - boolean hasUnUsedAddress = walletService.getSavingsAddressEntryList().stream() - .filter(addressEntry -> walletService.getBalanceForAddress(addressEntry.getAddress()).isZero()) - .findAny().isPresent(); + boolean hasUnUsedAddress = observableList.stream().filter(e -> e.getNumTxOutputs() == 0).findAny().isPresent(); if (hasUnUsedAddress) { - new Popup().warning("You have already addresses generated which are still not used.\n" + - "Please select in the address table an unused address.").show(); + new Popup().warning("You have addresses which are not used in any transaction.\n" + + "Please select in the address table any unused address.").show(); } else { AddressEntry newSavingsAddressEntry = walletService.getNewSavingsAddressEntry(); fillForm(newSavingsAddressEntry.getAddressString()); @@ -187,25 +191,44 @@ public class DepositView extends ActivatableView { }; } + private Coin getAmountAsCoin() { + Coin senderAmount = formatter.parseToCoin(amountTextField.getText()); + if (!Restrictions.isAboveFixedTxFeeAndDust(senderAmount)) { + senderAmount = Coin.ZERO; + /* new Popup() + .warning("The amount is lower than the transaction fee and the min. possible tx value (dust).") + .show();*/ + } + return senderAmount; + } + @NotNull private String getBitcoinURI() { return BitcoinURI.convertToBitcoinURI(addressTextField.getAddress(), - null, - null, + getAmountAsCoin(), + paymentLabel, null); } @Override protected void activate() { + sortedList.comparatorProperty().bind(tableView.comparatorProperty()); + tableView.setItems(sortedList); updateList(); walletService.addBalanceListener(balanceListener); + amountTextFieldSubscription = EasyBind.subscribe(amountTextField.textProperty(), t -> { + addressTextField.setAmountAsCoin(formatter.parseToCoin(t)); + updateQRCode(); + }); } @Override protected void deactivate() { - depositAddresses.forEach(DepositListItem::cleanup); + sortedList.comparatorProperty().unbind(); + observableList.forEach(DepositListItem::cleanup); walletService.removeBalanceListener(balanceListener); + amountTextFieldSubscription.unsubscribe(); } @@ -225,22 +248,30 @@ public class DepositView extends ActivatableView { addressLabel.setManaged(true); addressTextField.setVisible(true); addressTextField.setManaged(true); + amountLabel.setVisible(true); + amountTextField.setManaged(true); GridPane.setMargin(generateNewAddressButton, new Insets(15, 0, 0, 0)); addressTextField.setAddress(address); - final byte[] imageBytes = QRCode - .from(getBitcoinURI()) - .withSize(150, 150) // code has 41 elements 8 px is border with 150 we get 3x scale and min. border - .to(ImageType.PNG) - .stream() - .toByteArray(); - Image qrImage = new Image(new ByteArrayInputStream(imageBytes)); - qrCodeImageView.setImage(qrImage); + updateQRCode(); } + private void updateQRCode() { + if (addressTextField.getAddress() != null && !addressTextField.getAddress().isEmpty()) { + final byte[] imageBytes = QRCode + .from(getBitcoinURI()) + .withSize(150, 150) // code has 41 elements 8 px is border with 150 we get 3x scale and min. border + .to(ImageType.PNG) + .stream() + .toByteArray(); + Image qrImage = new Image(new ByteArrayInputStream(imageBytes)); + qrCodeImageView.setImage(qrImage); + } + } + private void openBlockExplorer(DepositListItem item) { if (item.getAddressString() != null) { try { @@ -258,10 +289,9 @@ public class DepositView extends ActivatableView { /////////////////////////////////////////////////////////////////////////////////////////// private void updateList() { - depositAddresses.clear(); + observableList.clear(); walletService.getSavingsAddressEntryList().stream() - .forEach(e -> depositAddresses.add(new DepositListItem(e, walletService, formatter))); - table.setItems(depositAddresses); + .forEach(e -> observableList.add(new DepositListItem(e, walletService, formatter))); } @@ -269,9 +299,9 @@ public class DepositView extends ActivatableView { // ColumnCellFactories /////////////////////////////////////////////////////////////////////////////////////////// - private void setStatusColumnCellFactory() { - statusColumn.setCellValueFactory((addressListItem) -> new ReadOnlyObjectWrapper<>(addressListItem.getValue())); - statusColumn.setCellFactory(new Callback, + private void setUsageColumnCellFactory() { + usageColumn.setCellValueFactory((addressListItem) -> new ReadOnlyObjectWrapper<>(addressListItem.getValue())); + usageColumn.setCellFactory(new Callback, TableCell>() { @Override @@ -283,7 +313,7 @@ public class DepositView extends ActivatableView { public void updateItem(final DepositListItem item, boolean empty) { super.updateItem(item, empty); if (item != null && !empty) { - setGraphic(new Label(item.getStatus())); + setGraphic(new Label(item.getUsage())); } else { setGraphic(null); } @@ -364,6 +394,33 @@ public class DepositView extends ActivatableView { }); } + private void setBalanceColumnCellFactory() { + balanceColumn.setCellValueFactory((addressListItem) -> new ReadOnlyObjectWrapper<>(addressListItem.getValue())); + balanceColumn.setCellFactory(new Callback, + TableCell>() { + + @Override + public TableCell call(TableColumn column) { + return new TableCell() { + + @Override + public void updateItem(final DepositListItem item, boolean empty) { + super.updateItem(item, empty); + if (item != null && !empty) { + if (!textProperty().isBound()) + textProperty().bind(item.balanceProperty()); + } else { + textProperty().unbind(); + setText(""); + } + } + }; + } + }); + } + + private void setConfidenceColumnCellFactory() { confidenceColumn.setCellValueFactory((addressListItem) -> new ReadOnlyObjectWrapper<>(addressListItem.getValue())); diff --git a/gui/src/main/java/io/bitsquare/gui/main/funds/reserved/ReservedListItem.java b/gui/src/main/java/io/bitsquare/gui/main/funds/reserved/ReservedListItem.java index 7d28a01554..ced9a55be2 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/funds/reserved/ReservedListItem.java +++ b/gui/src/main/java/io/bitsquare/gui/main/funds/reserved/ReservedListItem.java @@ -142,4 +142,9 @@ public class ReservedListItem { public String getFundsInfo() { return fundsInfo; } + + public Tradable getTradable() { + return tradable; + } + } diff --git a/gui/src/main/java/io/bitsquare/gui/main/funds/reserved/ReservedView.fxml b/gui/src/main/java/io/bitsquare/gui/main/funds/reserved/ReservedView.fxml index 9c03c2ecee..62d6d3f525 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/funds/reserved/ReservedView.fxml +++ b/gui/src/main/java/io/bitsquare/gui/main/funds/reserved/ReservedView.fxml @@ -18,7 +18,6 @@ --> - - + - - - - - + diff --git a/gui/src/main/java/io/bitsquare/gui/main/funds/reserved/ReservedView.java b/gui/src/main/java/io/bitsquare/gui/main/funds/reserved/ReservedView.java index e1c277e9b5..7a7deefdf5 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/funds/reserved/ReservedView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/funds/reserved/ReservedView.java @@ -38,6 +38,7 @@ import io.bitsquare.user.Preferences; import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.collections.FXCollections; import javafx.collections.ObservableList; +import javafx.collections.transformation.SortedList; import javafx.fxml.FXML; import javafx.scene.control.*; import javafx.scene.layout.VBox; @@ -53,7 +54,7 @@ import java.util.stream.Stream; @FxmlView public class ReservedView extends ActivatableView { @FXML - TableView table; + TableView tableView; @FXML TableColumn dateColumn, detailsColumn, addressColumn, balanceColumn, confidenceColumn; @@ -64,7 +65,8 @@ public class ReservedView extends ActivatableView { private final BSFormatter formatter; private final OfferDetailsWindow offerDetailsWindow; private final TradeDetailsWindow tradeDetailsWindow; - private final ObservableList reservedAddresses = FXCollections.observableArrayList(); + private final ObservableList observableList = FXCollections.observableArrayList(); + private final SortedList sortedList = new SortedList<>(observableList); private BalanceListener balanceListener; @@ -87,13 +89,21 @@ public class ReservedView extends ActivatableView { @Override public void initialize() { - table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); - table.setPlaceholder(new Label("No funds are reserved in open offers or trades")); + tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); + tableView.setPlaceholder(new Label("No funds are reserved in open offers or trades")); + setDateColumnCellFactory(); setDetailsColumnCellFactory(); setAddressColumnCellFactory(); setBalanceColumnCellFactory(); - table.getSortOrder().add(dateColumn); + + addressColumn.setComparator((o1, o2) -> o1.getAddressString().compareTo(o2.getAddressString())); + detailsColumn.setComparator((o1, o2) -> o1.getTradable().getId().compareTo(o2.getTradable().getId())); + balanceColumn.setComparator((o1, o2) -> o1.getBalance().compareTo(o2.getBalance())); + dateColumn.setComparator((o1, o2) -> getTradable(o2).get().getDate().compareTo(getTradable(o1).get().getDate())); + tableView.getSortOrder().add(dateColumn); + dateColumn.setSortType(TableColumn.SortType.DESCENDING); + balanceListener = new BalanceListener() { @Override public void onBalanceChanged(Coin balance, Transaction tx) { @@ -104,6 +114,8 @@ public class ReservedView extends ActivatableView { @Override protected void activate() { + sortedList.comparatorProperty().bind(tableView.comparatorProperty()); + tableView.setItems(sortedList); updateList(); walletService.addBalanceListener(balanceListener); @@ -111,7 +123,8 @@ public class ReservedView extends ActivatableView { @Override protected void deactivate() { - reservedAddresses.forEach(ReservedListItem::cleanup); + sortedList.comparatorProperty().unbind(); + observableList.forEach(ReservedListItem::cleanup); walletService.removeBalanceListener(balanceListener); } @@ -121,13 +134,12 @@ public class ReservedView extends ActivatableView { /////////////////////////////////////////////////////////////////////////////////////////// private void updateList() { - reservedAddresses.forEach(ReservedListItem::cleanup); - reservedAddresses.setAll(Stream.concat(openOfferManager.getOpenOffers().stream(), tradeManager.getTrades().stream()) + observableList.forEach(ReservedListItem::cleanup); + observableList.clear(); + observableList.setAll(Stream.concat(openOfferManager.getOpenOffers().stream(), tradeManager.getTrades().stream()) + .filter(tradable -> !(tradable instanceof Trade) || ((Trade) tradable).getState().getPhase() != Trade.Phase.PAYOUT_PAID) .map(tradable -> new ReservedListItem(tradable, walletService.getTradeAddressEntry(tradable.getOffer().getId()), walletService, formatter)) .collect(Collectors.toList())); - - reservedAddresses.sort((o1, o2) -> getTradable(o2).get().getDate().compareTo(getTradable(o1).get().getDate())); - table.setItems(reservedAddresses); } private void openBlockExplorer(ReservedListItem item) { diff --git a/gui/src/main/java/io/bitsquare/gui/main/funds/transactions/TransactionsListItem.java b/gui/src/main/java/io/bitsquare/gui/main/funds/transactions/TransactionsListItem.java index 75df4d2e8d..45057dec5a 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/funds/transactions/TransactionsListItem.java +++ b/gui/src/main/java/io/bitsquare/gui/main/funds/transactions/TransactionsListItem.java @@ -24,24 +24,24 @@ import io.bitsquare.gui.util.BSFormatter; import io.bitsquare.trade.Tradable; import io.bitsquare.trade.Trade; import io.bitsquare.trade.offer.OpenOffer; -import javafx.beans.property.SimpleStringProperty; -import javafx.beans.property.StringProperty; import javafx.scene.control.Tooltip; import org.bitcoinj.core.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.annotation.Nullable; import java.util.Optional; public class TransactionsListItem { - private final Logger log = LoggerFactory.getLogger(this.getClass()); - private final StringProperty date = new SimpleStringProperty(); - private final StringProperty amount = new SimpleStringProperty(); + + private String date; private final String txId; private final WalletService walletService; private final ConfidenceProgressIndicator progressIndicator; private final Tooltip tooltip; + private boolean isInternal; + @Nullable private Tradable tradable; private String details; private String addressString; @@ -49,18 +49,19 @@ public class TransactionsListItem { private TxConfidenceListener txConfidenceListener; private boolean received; private boolean detailsAvailable; + private Coin amountAsCoin = Coin.ZERO; + private BSFormatter formatter; public TransactionsListItem(Transaction transaction, WalletService walletService, Optional tradableOptional, BSFormatter formatter) { + this.formatter = formatter; txId = transaction.getHashAsString(); this.walletService = walletService; Coin valueSentToMe = transaction.getValueSentToMe(walletService.getWallet()); Coin valueSentFromMe = transaction.getValueSentFromMe(walletService.getWallet()); - Coin amountAsCoin; Address address = null; if (valueSentToMe.isZero()) { - amountAsCoin = valueSentFromMe; - amount.set("-" + formatter.formatCoin(amountAsCoin)); + amountAsCoin = valueSentFromMe.multiply(-1); for (TransactionOutput transactionOutput : transaction.getOutputs()) { if (!transactionOutput.isMine(walletService.getWallet())) { @@ -75,7 +76,7 @@ public class TransactionsListItem { } } else if (valueSentFromMe.isZero()) { amountAsCoin = valueSentToMe; - amount.set(formatter.formatCoin(amountAsCoin)); + direction = "Received with:"; received = true; @@ -88,9 +89,8 @@ public class TransactionsListItem { } } } - } else { + } else/* if (tradableOptional.isPresent())*/ { amountAsCoin = valueSentToMe.subtract(valueSentFromMe); - amount.set(formatter.formatCoin(amountAsCoin)); boolean outgoing = false; for (TransactionOutput transactionOutput : transaction.getOutputs()) { if (!transactionOutput.isMine(walletService.getWallet())) { @@ -107,7 +107,25 @@ public class TransactionsListItem { direction = "Sent to:"; received = false; } - } + } /*else { + // savings wallet tx + for (TransactionOutput transactionOutput : transaction.getOutputs()) { + if (transactionOutput.isMine(walletService.getWallet())) { + if (transactionOutput.getScriptPubKey().isSentToAddress() || + transactionOutput.getScriptPubKey().isPayToScriptHash()) { + address = transactionOutput.getScriptPubKey().getToAddress(walletService.getWallet().getParams()); + addressString = address.toString(); + + amountAsCoin = transactionOutput.getValue().multiply(-1); + } + } + } + + direction = "Transferred to:"; + received = false; + isInternal = true; + details = "Change output"; + }*/ if (tradableOptional.isPresent()) { @@ -141,11 +159,11 @@ public class TransactionsListItem { } else { if (amountAsCoin.isZero()) details = "No refund from dispute"; - else + else if (!isInternal) details = received ? "Received funds" : "Withdrawn from wallet"; } - date.set(formatter.formatDateTime(transaction.getUpdateTime())); + date = formatter.formatDateTime(transaction.getUpdateTime()); // confidence progressIndicator = new ConfidenceProgressIndicator(); @@ -202,14 +220,20 @@ public class TransactionsListItem { return progressIndicator; } - public final StringProperty dateProperty() { - return this.date; + public final String getDate() { + return date; } - public final StringProperty amountProperty() { - return this.amount; + + public String getAmount() { + return formatter.formatCoin(amountAsCoin); } + public Coin getAmountAsCoin() { + return amountAsCoin; + } + + public String getAddressString() { return addressString; } @@ -218,6 +242,10 @@ public class TransactionsListItem { return direction; } + public boolean isInternal() { + return isInternal; + } + public String getTxId() { return txId; } @@ -234,6 +262,7 @@ public class TransactionsListItem { return detailsAvailable; } + @Nullable public Tradable getTradable() { return tradable; } diff --git a/gui/src/main/java/io/bitsquare/gui/main/funds/transactions/TransactionsView.fxml b/gui/src/main/java/io/bitsquare/gui/main/funds/transactions/TransactionsView.fxml index 56435393d8..8830d4b780 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/funds/transactions/TransactionsView.fxml +++ b/gui/src/main/java/io/bitsquare/gui/main/funds/transactions/TransactionsView.fxml @@ -18,7 +18,6 @@ --> - - + - - - - - + - - - - - - + diff --git a/gui/src/main/java/io/bitsquare/gui/main/funds/transactions/TransactionsView.java b/gui/src/main/java/io/bitsquare/gui/main/funds/transactions/TransactionsView.java index f008b42fd9..8f0d217ef3 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/funds/transactions/TransactionsView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/funds/transactions/TransactionsView.java @@ -40,6 +40,7 @@ import io.bitsquare.user.Preferences; import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.collections.FXCollections; import javafx.collections.ObservableList; +import javafx.collections.transformation.SortedList; import javafx.fxml.FXML; import javafx.scene.control.*; import javafx.scene.layout.VBox; @@ -58,12 +59,12 @@ import java.util.stream.Stream; public class TransactionsView extends ActivatableView { @FXML - TableView table; + TableView tableView; @FXML - TableColumn dateColumn, detailsColumn, addressColumn, transactionColumn, amountColumn, typeColumn, - confidenceColumn; + TableColumn dateColumn, detailsColumn, addressColumn, transactionColumn, amountColumn, confidenceColumn; - private final ObservableList transactionsListItems = FXCollections.observableArrayList(); + private final ObservableList observableList = FXCollections.observableArrayList(); + private final SortedList sortedList = new SortedList<>(observableList); private final WalletService walletService; private final TradeManager tradeManager; @@ -102,13 +103,31 @@ public class TransactionsView extends ActivatableView { @Override public void initialize() { - table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); - table.setPlaceholder(new Label("No transactions available")); + tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); + tableView.setPlaceholder(new Label("No transactions available")); + + + setDateColumnCellFactory(); setDetailsColumnCellFactory(); setAddressColumnCellFactory(); setTransactionColumnCellFactory(); + setAmountColumnCellFactory(); setConfidenceColumnCellFactory(); - table.getSortOrder().add(dateColumn); + + dateColumn.setComparator((o1, o2) -> o1.getDate().compareTo(o2.getDate())); + detailsColumn.setComparator((o1, o2) -> { + String id1 = o1.getTradable() != null ? o1.getTradable().getId() : o1.getDetails(); + String id2 = o2.getTradable() != null ? o2.getTradable().getId() : o2.getDetails(); + return id1.compareTo(id2); + }); + addressColumn.setComparator((o1, o2) -> o1.getAddressString().compareTo(o2.getAddressString())); + transactionColumn.setComparator((o1, o2) -> o1.getTxId().compareTo(o2.getTxId())); + amountColumn.setComparator((o1, o2) -> o1.getAmountAsCoin().compareTo(o2.getAmountAsCoin())); + confidenceColumn.setComparator((o1, o2) -> Double.valueOf(o1.getProgressIndicator().getProgress()) + .compareTo(o2.getProgressIndicator().getProgress())); + + dateColumn.setSortType(TableColumn.SortType.DESCENDING); + tableView.getSortOrder().add(dateColumn); walletEventListener = new WalletEventListener() { @Override @@ -149,13 +168,17 @@ public class TransactionsView extends ActivatableView { @Override protected void activate() { + sortedList.comparatorProperty().bind(tableView.comparatorProperty()); + tableView.setItems(sortedList); updateList(); + walletService.getWallet().addEventListener(walletEventListener); } @Override protected void deactivate() { - transactionsListItems.forEach(TransactionsListItem::cleanup); + sortedList.comparatorProperty().unbind(); + observableList.forEach(TransactionsListItem::cleanup); walletService.getWallet().removeEventListener(walletEventListener); } @@ -170,7 +193,40 @@ public class TransactionsView extends ActivatableView { Stream concat3 = Stream.concat(concat2, failedTradesManager.getFailedTrades().stream()); Set all = concat3.collect(Collectors.toSet()); - List listItems = walletService.getWallet().getRecentTransactions(1000, true).stream() + Set transactions = walletService.getWallet().getTransactions(true); + /* List transactionsListItems = new ArrayList<>(); + for (Transaction transaction : transactions) { + Optional tradableOptional = all.stream() + .filter(tradable -> { + String txId = transaction.getHashAsString(); + if (tradable instanceof OpenOffer) + return tradable.getOffer().getOfferFeePaymentTxID().equals(txId); + else if (tradable instanceof Trade) { + Trade trade = (Trade) tradable; + boolean isTakeOfferFeeTx = txId.equals(trade.getTakeOfferFeeTxId()); + boolean isOfferFeeTx = trade.getOffer() != null && + txId.equals(trade.getOffer().getOfferFeePaymentTxID()); + boolean isDepositTx = trade.getDepositTx() != null && + trade.getDepositTx().getHashAsString().equals(txId); + boolean isPayoutTx = trade.getPayoutTx() != null && + trade.getPayoutTx().getHashAsString().equals(txId); + + boolean isDisputedPayoutTx = disputeManager.getDisputesAsObservableList().stream() + .filter(dispute -> txId.equals(dispute.getDisputePayoutTxId()) && + tradable.getId().equals(dispute.getTradeId())) + .findAny() + .isPresent(); + + return isTakeOfferFeeTx || isOfferFeeTx || isDepositTx || isPayoutTx || isDisputedPayoutTx; + } else + return false; + }) + .findAny(); + // if (tradableOptional.isPresent()) + transactionsListItems.add(new TransactionsListItem(transaction, walletService, tradableOptional, formatter)); + }*/ + + List transactionsListItems = transactions.stream() .map(transaction -> { Optional tradableOptional = all.stream() .filter(tradable -> { @@ -202,10 +258,15 @@ public class TransactionsView extends ActivatableView { }) .collect(Collectors.toList()); + /* List usedSavingWalletEntries = walletService.getUsedSavingWalletTransactions() + .stream() + .map(transaction -> new TransactionsListItem(transaction, walletService, Optional.empty(), formatter)) + .collect(Collectors.toList()); + transactionsListItems.addAll(usedSavingWalletEntries);*/ + // are sorted by getRecentTransactions - transactionsListItems.forEach(TransactionsListItem::cleanup); - transactionsListItems.setAll(listItems); - table.setItems(transactionsListItems); + observableList.forEach(TransactionsListItem::cleanup); + observableList.setAll(transactionsListItems); } private void openBlockExplorer(TransactionsListItem item) { @@ -232,6 +293,33 @@ public class TransactionsView extends ActivatableView { // ColumnCellFactories /////////////////////////////////////////////////////////////////////////////////////////// + private void setDateColumnCellFactory() { + dateColumn.setCellValueFactory((addressListItem) -> + new ReadOnlyObjectWrapper<>(addressListItem.getValue())); + dateColumn.setCellFactory( + new Callback, TableCell>() { + + @Override + public TableCell call(TableColumn column) { + return new TableCell() { + + @Override + public void updateItem(final TransactionsListItem item, boolean empty) { + super.updateItem(item, empty); + + if (item != null && !empty) { + setText(item.getDate()); + } else { + setText(""); + } + } + }; + } + }); + } + private void setDetailsColumnCellFactory() { detailsColumn.setCellValueFactory((addressListItem) -> new ReadOnlyObjectWrapper<>(addressListItem.getValue())); detailsColumn.setCellFactory( @@ -289,7 +377,7 @@ public class TransactionsView extends ActivatableView { if (item != null && !empty) { String addressString = item.getAddressString(); field = new AddressWithIconAndDirection(item.getDirection(), addressString, - AwesomeIcon.EXTERNAL_LINK, item.getReceived()); + AwesomeIcon.EXTERNAL_LINK, item.getReceived(), item.isInternal()); field.setOnAction(event -> openBlockExplorer(item)); field.setTooltip(new Tooltip("Open external blockchain explorer for " + "address: " + addressString)); @@ -339,6 +427,33 @@ public class TransactionsView extends ActivatableView { }); } + private void setAmountColumnCellFactory() { + amountColumn.setCellValueFactory((addressListItem) -> + new ReadOnlyObjectWrapper<>(addressListItem.getValue())); + amountColumn.setCellFactory( + new Callback, TableCell>() { + + @Override + public TableCell call(TableColumn column) { + return new TableCell() { + + @Override + public void updateItem(final TransactionsListItem item, boolean empty) { + super.updateItem(item, empty); + + if (item != null && !empty) { + setText(item.getAmount()); + } else { + setText(""); + } + } + }; + } + }); + } + private void setConfidenceColumnCellFactory() { confidenceColumn.setCellValueFactory((addressListItem) -> new ReadOnlyObjectWrapper<>(addressListItem.getValue())); diff --git a/gui/src/main/java/io/bitsquare/gui/main/funds/withdrawal/WithdrawalView.fxml b/gui/src/main/java/io/bitsquare/gui/main/funds/withdrawal/WithdrawalView.fxml index 2039695b3b..f0e8cd8731 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/funds/withdrawal/WithdrawalView.fxml +++ b/gui/src/main/java/io/bitsquare/gui/main/funds/withdrawal/WithdrawalView.fxml @@ -19,7 +19,6 @@ - @@ -27,17 +26,11 @@ - + - - - - - - - - + + diff --git a/gui/src/main/java/io/bitsquare/gui/main/funds/withdrawal/WithdrawalView.java b/gui/src/main/java/io/bitsquare/gui/main/funds/withdrawal/WithdrawalView.java index 7726f4f2be..224c6f88e6 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/funds/withdrawal/WithdrawalView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/funds/withdrawal/WithdrawalView.java @@ -20,7 +20,6 @@ package io.bitsquare.gui.main.funds.withdrawal; import com.google.common.util.concurrent.FutureCallback; import de.jensd.fx.fontawesome.AwesomeIcon; import io.bitsquare.app.BitsquareApp; -import io.bitsquare.btc.AddressEntry; import io.bitsquare.btc.Restrictions; import io.bitsquare.btc.WalletService; import io.bitsquare.btc.listeners.BalanceListener; @@ -46,6 +45,7 @@ import javafx.beans.binding.Bindings; import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.collections.FXCollections; import javafx.collections.ObservableList; +import javafx.collections.transformation.SortedList; import javafx.fxml.FXML; import javafx.scene.control.*; import javafx.scene.layout.VBox; @@ -59,10 +59,7 @@ import org.jetbrains.annotations.NotNull; import org.spongycastle.crypto.params.KeyParameter; import javax.inject.Inject; -import java.util.Date; -import java.util.HashSet; -import java.util.Optional; -import java.util.Set; +import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -72,11 +69,11 @@ public class WithdrawalView extends ActivatableView { @FXML Button withdrawButton; @FXML - TableView table; + TableView tableView; @FXML TextField withdrawFromTextField, withdrawToTextField, amountTextField; @FXML - TableColumn dateColumn, detailsColumn, addressColumn, balanceColumn, selectColumn; + TableColumn addressColumn, balanceColumn, selectColumn; private final WalletService walletService; private final TradeManager tradeManager; @@ -89,7 +86,8 @@ public class WithdrawalView extends ActivatableView { private final WalletPasswordWindow walletPasswordWindow; private final OfferDetailsWindow offerDetailsWindow; private final TradeDetailsWindow tradeDetailsWindow; - private final ObservableList fundedAddresses = FXCollections.observableArrayList(); + private final ObservableList observableList = FXCollections.observableArrayList(); + private final SortedList sortedList = new SortedList<>(observableList); private Set selectedItems = new HashSet<>(); private BalanceListener balanceListener; private Set fromAddresses; @@ -121,15 +119,18 @@ public class WithdrawalView extends ActivatableView { @Override public void initialize() { - table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); - table.setPlaceholder(new Label("No funds for withdrawal are available")); - setDateColumnCellFactory(); - setDetailsColumnCellFactory(); + tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); + tableView.setPlaceholder(new Label("No funds for withdrawal are available")); + tableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); + setAddressColumnCellFactory(); setBalanceColumnCellFactory(); setSelectColumnCellFactory(); - table.getSortOrder().add(dateColumn); - table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); + + addressColumn.setComparator((o1, o2) -> o1.getAddressString().compareTo(o2.getAddressString())); + balanceColumn.setComparator((o1, o2) -> o1.getBalance().compareTo(o2.getBalance())); + balanceColumn.setSortType(TableColumn.SortType.DESCENDING); + tableView.getSortOrder().add(balanceColumn); balanceListener = new BalanceListener() { @Override @@ -141,6 +142,8 @@ public class WithdrawalView extends ActivatableView { @Override protected void activate() { + sortedList.comparatorProperty().bind(tableView.comparatorProperty()); + tableView.setItems(sortedList); updateList(); reset(); @@ -152,7 +155,8 @@ public class WithdrawalView extends ActivatableView { @Override protected void deactivate() { - fundedAddresses.forEach(WithdrawalListItem::cleanup); + sortedList.comparatorProperty().unbind(); + observableList.forEach(WithdrawalListItem::cleanup); withdrawButton.disableProperty().unbind(); walletService.removeBalanceListener(balanceListener); } @@ -174,6 +178,14 @@ public class WithdrawalView extends ActivatableView { } else { log.error("onWithdraw transaction is null"); } + + List trades = new ArrayList<>(tradeManager.getTrades()); + trades.stream() + .filter(trade -> trade.getState().getPhase() == Trade.Phase.PAYOUT_PAID) + .forEach(trade -> { + if (walletService.getBalanceForAddress(walletService.getTradeAddressEntry(trade.getId()).getAddress()).isZero()) + tradeManager.addTradeToClosedTrades(trade); + }); } @Override @@ -283,32 +295,16 @@ public class WithdrawalView extends ActivatableView { private void updateList() { Set reservedTrades = Stream.concat(openOfferManager.getOpenOffers().stream(), tradeManager.getTrades().stream()) + .filter(tradable -> !(tradable instanceof Trade) || ((Trade) tradable).getState().getPhase() != Trade.Phase.PAYOUT_PAID) .map(tradable -> tradable.getOffer().getId()) .collect(Collectors.toSet()); - fundedAddresses.forEach(WithdrawalListItem::cleanup); - fundedAddresses.setAll(walletService.getAddressEntryList().stream() + observableList.forEach(WithdrawalListItem::cleanup); + observableList.setAll(walletService.getAddressEntryList().stream() .filter(e -> walletService.getBalanceForAddress(e.getAddress()).isPositive()) .filter(e -> !reservedTrades.contains(e.getOfferId())) .map(addressEntry -> new WithdrawalListItem(addressEntry, walletService, formatter)) .collect(Collectors.toList())); - - fundedAddresses.sort((o1, o2) -> { - Optional tradable1 = getTradable(o1); - Optional tradable2 = getTradable(o2); - // if we dont have a date we set it to now as it is likely a recent funding tx - // TODO get tx date from wallet instead - Date date1 = new Date(); - Date date2 = new Date(); - if (tradable1.isPresent()) - date1 = tradable1.get().getDate(); - - if (tradable2.isPresent()) - date2 = tradable2.get().getDate(); - - return date2.compareTo(date1); - }); - table.setItems(fundedAddresses); } private void doWithdraw(Coin amount, FutureCallback callback) { @@ -335,7 +331,7 @@ public class WithdrawalView extends ActivatableView { private void reset() { selectedItems = new HashSet<>(); - table.getSelectionModel().clearSelection(); + tableView.getSelectionModel().clearSelection(); withdrawFromTextField.setText(""); withdrawFromTextField.setPromptText("Select a source address from the table"); @@ -348,7 +344,7 @@ public class WithdrawalView extends ActivatableView { withdrawToTextField.setPromptText("Fill in your destination address"); if (BitsquareApp.DEV_MODE) - withdrawToTextField.setText("mi8k5f9L972VgDaT4LgjAhriC9hHEPL7EW"); + withdrawToTextField.setText("mo6y756TnpdZQCeHStraavjqrndeXzVkxi"); } private Optional getTradable(WithdrawalListItem item) { @@ -374,92 +370,6 @@ public class WithdrawalView extends ActivatableView { // ColumnCellFactories /////////////////////////////////////////////////////////////////////////////////////////// - private void setDateColumnCellFactory() { - dateColumn.setCellValueFactory((addressListItem) -> new ReadOnlyObjectWrapper<>(addressListItem.getValue())); - dateColumn.setCellFactory(new Callback, - TableCell>() { - - @Override - public TableCell call(TableColumn column) { - return new TableCell() { - - @Override - public void updateItem(final WithdrawalListItem item, boolean empty) { - super.updateItem(item, empty); - if (item != null && !empty) { - if (getTradable(item).isPresent()) - setText(formatter.formatDateTime(getTradable(item).get().getDate())); - else - setText("No date available"); - } else { - setText(""); - } - } - }; - } - }); - } - - private void setDetailsColumnCellFactory() { - detailsColumn.setCellValueFactory((addressListItem) -> new ReadOnlyObjectWrapper<>(addressListItem.getValue())); - detailsColumn.setCellFactory(new Callback, - TableCell>() { - - @Override - public TableCell call(TableColumn column) { - return new TableCell() { - - private HyperlinkWithIcon field; - - @Override - public void updateItem(final WithdrawalListItem item, boolean empty) { - super.updateItem(item, empty); - - if (item != null && !empty) { - Optional tradableOptional = getTradable(item); - if (tradableOptional.isPresent()) { - AddressEntry addressEntry = item.getAddressEntry(); - String details; - if (addressEntry.getContext() == AddressEntry.Context.TRADE) { - String prefix; - Tradable tradable = tradableOptional.get(); - if (tradable instanceof Trade) - prefix = "Trade ID: "; - else if (tradable instanceof OpenOffer) - prefix = "Offer ID: "; - else - prefix = ""; - - details = prefix + addressEntry.getShortOfferId(); - } else if (addressEntry.getContext() == AddressEntry.Context.ARBITRATOR) { - details = "Arbitration fee"; - } else { - details = "-"; - } - - field = new HyperlinkWithIcon(details, AwesomeIcon.INFO_SIGN); - field.setOnAction(event -> openDetailPopup(item)); - field.setTooltip(new Tooltip("Open popup for details")); - setGraphic(field); - } else if (item.getAddressEntry().getContext() == AddressEntry.Context.ARBITRATOR) { - setGraphic(new Label("Arbitrators fee")); - } else { - setGraphic(new Label("No details available")); - } - - } else { - setGraphic(null); - if (field != null) - field.setOnAction(null); - } - } - }; - } - }); - } - private void setAddressColumnCellFactory() { addressColumn.setCellValueFactory((addressListItem) -> new ReadOnlyObjectWrapper<>(addressListItem.getValue())); addressColumn.setCellFactory( diff --git a/gui/src/main/java/io/bitsquare/gui/main/markets/statistics/MarketsStatisticsView.java b/gui/src/main/java/io/bitsquare/gui/main/markets/statistics/MarketsStatisticsView.java index 5d3abe1527..dea9e7645c 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/markets/statistics/MarketsStatisticsView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/markets/statistics/MarketsStatisticsView.java @@ -23,6 +23,7 @@ import io.bitsquare.gui.components.TableGroupHeadline; import io.bitsquare.gui.util.BSFormatter; import io.bitsquare.locale.CurrencyUtil; import javafx.beans.property.ReadOnlyObjectWrapper; +import javafx.collections.transformation.SortedList; import javafx.geometry.Insets; import javafx.scene.control.Label; import javafx.scene.control.TableCell; @@ -38,7 +39,8 @@ import javax.inject.Inject; public class MarketsStatisticsView extends ActivatableViewAndModel { private final BSFormatter formatter; private final int gridRow = 0; - private TableView statisticsTableView; + private TableView tableView; + private SortedList sortedList; /////////////////////////////////////////////////////////////////////////////////////////// @@ -58,29 +60,44 @@ public class MarketsStatisticsView extends ActivatableViewAndModel(); - GridPane.setRowIndex(statisticsTableView, gridRow); - GridPane.setMargin(statisticsTableView, new Insets(20, -10, -10, -10)); - GridPane.setVgrow(statisticsTableView, Priority.ALWAYS); - GridPane.setHgrow(statisticsTableView, Priority.ALWAYS); - root.getChildren().add(statisticsTableView); - statisticsTableView.getColumns().add(getCurrencyColumn()); - statisticsTableView.getColumns().add(getNumberOfOffersColumn()); - statisticsTableView.getColumns().add(getTotalAmountColumn()); - statisticsTableView.getColumns().add(getSpreadColumn()); - statisticsTableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); + tableView = new TableView<>(); + GridPane.setRowIndex(tableView, gridRow); + GridPane.setMargin(tableView, new Insets(20, -10, -10, -10)); + GridPane.setVgrow(tableView, Priority.ALWAYS); + GridPane.setHgrow(tableView, Priority.ALWAYS); + root.getChildren().add(tableView); Label placeholder = new Label("Currently there is no data available"); placeholder.setWrapText(true); - statisticsTableView.setPlaceholder(placeholder); + tableView.setPlaceholder(placeholder); + + TableColumn currencyColumn = getCurrencyColumn(); + tableView.getColumns().add(currencyColumn); + TableColumn numberOfOffersColumn = getNumberOfOffersColumn(); + tableView.getColumns().add(numberOfOffersColumn); + TableColumn totalAmountColumn = getTotalAmountColumn(); + tableView.getColumns().add(totalAmountColumn); + TableColumn spreadColumn = getSpreadColumn(); + tableView.getColumns().add(spreadColumn); + tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); + + currencyColumn.setComparator((o1, o2) -> o1.currencyCode.compareTo(o2.currencyCode)); + numberOfOffersColumn.setComparator((o1, o2) -> Integer.valueOf(o1.numberOfOffers).compareTo(o2.numberOfOffers)); + totalAmountColumn.setComparator((o1, o2) -> o1.totalAmount.compareTo(o2.totalAmount)); + spreadColumn.setComparator((o1, o2) -> o1.spread != null && o2.spread != null ? o1.spread.compareTo(o2.spread) : 0); + + tableView.getSortOrder().add(numberOfOffersColumn); } @Override protected void activate() { - statisticsTableView.setItems(model.marketStatisticItems); + sortedList = new SortedList<>(model.marketStatisticItems); + sortedList.comparatorProperty().bind(tableView.comparatorProperty()); + tableView.setItems(sortedList); } @Override protected void deactivate() { + sortedList.comparatorProperty().unbind(); } diff --git a/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferDataModel.java b/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferDataModel.java index b966369f25..34dfc257f3 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferDataModel.java +++ b/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferDataModel.java @@ -28,6 +28,7 @@ import io.bitsquare.btc.listeners.BalanceListener; import io.bitsquare.btc.pricefeed.PriceFeed; import io.bitsquare.common.UserThread; import io.bitsquare.common.crypto.KeyRing; +import io.bitsquare.gui.Navigation; import io.bitsquare.gui.common.model.ActivatableDataModel; import io.bitsquare.gui.main.overlays.notifications.Notification; import io.bitsquare.gui.main.overlays.popups.Popup; @@ -64,13 +65,14 @@ import static com.google.common.base.Preconditions.checkNotNull; */ class CreateOfferDataModel extends ActivatableDataModel { private final OpenOfferManager openOfferManager; - private final WalletService walletService; + final WalletService walletService; private final TradeWalletService tradeWalletService; private final Preferences preferences; private final User user; private final KeyRing keyRing; private final P2PService p2PService; private final PriceFeed priceFeed; + private Navigation navigation; private final WalletPasswordWindow walletPasswordWindow; private final BlockchainService blockchainService; private final BSFormatter formatter; @@ -98,12 +100,16 @@ class CreateOfferDataModel extends ActivatableDataModel { final ObjectProperty priceAsFiat = new SimpleObjectProperty<>(); final ObjectProperty volumeAsFiat = new SimpleObjectProperty<>(); final ObjectProperty totalToPayAsCoin = new SimpleObjectProperty<>(); + final ObjectProperty missingCoin = new SimpleObjectProperty<>(Coin.ZERO); + final ObjectProperty balance = new SimpleObjectProperty<>(); final ObservableList paymentAccounts = FXCollections.observableArrayList(); PaymentAccount paymentAccount; private boolean isTabSelected; private Notification walletFundedNotification; + boolean useSavingsWallet; + Coin totalAvailableBalance; /////////////////////////////////////////////////////////////////////////////////////////// @@ -113,6 +119,7 @@ class CreateOfferDataModel extends ActivatableDataModel { @Inject CreateOfferDataModel(OpenOfferManager openOfferManager, WalletService walletService, TradeWalletService tradeWalletService, Preferences preferences, User user, KeyRing keyRing, P2PService p2PService, PriceFeed priceFeed, + Navigation navigation, WalletPasswordWindow walletPasswordWindow, BlockchainService blockchainService, BSFormatter formatter) { this.openOfferManager = openOfferManager; this.walletService = walletService; @@ -122,6 +129,7 @@ class CreateOfferDataModel extends ActivatableDataModel { this.keyRing = keyRing; this.p2PService = p2PService; this.priceFeed = priceFeed; + this.navigation = navigation; this.walletPasswordWindow = walletPasswordWindow; this.blockchainService = blockchainService; this.formatter = formatter; @@ -135,7 +143,7 @@ class CreateOfferDataModel extends ActivatableDataModel { balanceListener = new BalanceListener(getAddressEntry().getAddress()) { @Override public void onBalanceChanged(Coin balance, Transaction tx) { - updateBalance(balance); + updateBalance(); if (preferences.getBitcoinNetwork() == BitcoinNetwork.MAINNET) { SettableFuture future = blockchainService.requestFee(tx.getHashAsString()); @@ -172,7 +180,8 @@ class CreateOfferDataModel extends ActivatableDataModel { addListeners(); paymentAccounts.setAll(user.getPaymentAccounts()); - updateBalance(walletService.getBalanceForAddress(getAddressEntry().getAddress())); + calculateTotalToPay(); + updateBalance(); if (direction == Offer.Direction.BUY) calculateTotalToPay(); @@ -290,7 +299,7 @@ class CreateOfferDataModel extends ActivatableDataModel { } private void doPlaceOffer(Offer offer, TransactionResultHandler resultHandler) { - openOfferManager.placeOffer(offer, resultHandler); + openOfferManager.placeOffer(offer, totalToPayAsCoin.get().subtract(offerFeeAsCoin), useSavingsWallet, resultHandler); } public void onPaymentAccountSelected(PaymentAccount paymentAccount) { @@ -311,6 +320,11 @@ class CreateOfferDataModel extends ActivatableDataModel { } } + void useSavingsWalletForFunding() { + useSavingsWallet = true; + updateBalance(); + } + /////////////////////////////////////////////////////////////////////////////////////////// // Getters @@ -380,16 +394,30 @@ class CreateOfferDataModel extends ActivatableDataModel { void calculateTotalToPay() { if (securityDepositAsCoin != null) { - if (direction == Offer.Direction.BUY) - totalToPayAsCoin.set(offerFeeAsCoin.add(networkFeeAsCoin).add(securityDepositAsCoin)); - else - totalToPayAsCoin.set(offerFeeAsCoin.add(networkFeeAsCoin).add(securityDepositAsCoin).add(amountAsCoin.get() == null ? Coin.ZERO : amountAsCoin.get())); + Coin feeAndSecDeposit = offerFeeAsCoin.add(networkFeeAsCoin).add(securityDepositAsCoin); + Coin feeAndSecDepositAndAmount = feeAndSecDeposit.add(amountAsCoin.get() == null ? Coin.ZERO : amountAsCoin.get()); + Coin required = direction == Offer.Direction.BUY ? feeAndSecDeposit : feeAndSecDepositAndAmount; + totalToPayAsCoin.set(required); } } - private void updateBalance(Coin balance) { - isWalletFunded.set(totalToPayAsCoin.get() != null && balance.compareTo(totalToPayAsCoin.get()) >= 0); + void updateBalance() { + Coin tradeWalletBalance = walletService.getBalanceForAddress(getAddressEntry().getAddress()); + if (useSavingsWallet) { + Coin savingWalletBalance = walletService.getSavingWalletBalance(); + totalAvailableBalance = savingWalletBalance.add(tradeWalletBalance); + if (totalAvailableBalance.compareTo(totalToPayAsCoin.get()) > 0) + balance.set(totalToPayAsCoin.get()); + else + balance.set(totalAvailableBalance); + } else { + balance.set(tradeWalletBalance); + } + + missingCoin.set(totalToPayAsCoin.get().subtract(balance.get())); + + isWalletFunded.set(isBalanceSufficient(balance.get())); if (isWalletFunded.get()) { walletService.removeBalanceListener(balanceListener); if (walletFundedNotification == null) { @@ -404,6 +432,10 @@ class CreateOfferDataModel extends ActivatableDataModel { } } + private boolean isBalanceSufficient(Coin balance) { + return totalToPayAsCoin.get() != null && balance.compareTo(totalToPayAsCoin.get()) >= 0; + } + public Coin getOfferFeeAsCoin() { return offerFeeAsCoin; } @@ -423,4 +455,8 @@ class CreateOfferDataModel extends ActivatableDataModel { public Preferences getPreferences() { return preferences; } + + public void swapTradeToSavings() { + walletService.swapTradeToSavings(getOfferId()); + } } diff --git a/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferView.java b/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferView.java index df9950acaf..ba152701e4 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferView.java @@ -69,6 +69,7 @@ import org.jetbrains.annotations.NotNull; import javax.inject.Inject; import java.io.ByteArrayInputStream; +import java.net.URI; import java.util.concurrent.TimeUnit; import static io.bitsquare.gui.util.FormBuilder.*; @@ -85,13 +86,12 @@ public class CreateOfferView extends ActivatableViewAndModel paymentAccountsComboBox; @@ -108,7 +108,6 @@ public class CreateOfferView extends ActivatableViewAndModel showWarningInvalidFiatDecimalPlacesPlacesListener; private ChangeListener showWarningAdjustedVolumeListener; private ChangeListener errorMessageListener; - private ChangeListener isSpinnerVisibleListener; private ChangeListener placeOfferCompletedListener; private ChangeListener feeFromFundingTxListener; private EventHandler paymentAccountsComboBoxSelectionHandler; @@ -118,6 +117,7 @@ public class CreateOfferView extends ActivatableViewAndModel tradeCurrencyCodeListener; private ImageView qrCodeImageView; + private ChangeListener balanceListener; /////////////////////////////////////////////////////////////////////////////////////////// @@ -143,7 +143,9 @@ public class CreateOfferView extends ActivatableViewAndModel balanceTextField.setBalance(newValue); + paymentAccountsComboBox.setConverter(new StringConverter() { @Override public String toString(PaymentAccount paymentAccount) { @@ -173,8 +175,7 @@ public class CreateOfferView extends ActivatableViewAndModel navigation.navigateTo(MainView.class, FundsView.class, WithdrawalView.class)) + .show(); + } } public void setCloseHandler(OfferView.CloseHandler closeHandler) { @@ -264,8 +267,6 @@ public class CreateOfferView extends ActivatableViewAndModel spinner.setProgress(newValue ? -1 : 0); feeFromFundingTxListener = (observable, oldValue, newValue) -> { log.debug("feeFromFundingTxListener " + newValue); @@ -514,6 +511,7 @@ public class CreateOfferView extends ActivatableViewAndModel { close(); + model.dataModel.swapTradeToSavings(); navigation.navigateTo(MainView.class, FundsView.class, WithdrawalView.class); }) .show(); @@ -558,6 +556,7 @@ public class CreateOfferView extends ActivatableViewAndModel nextButton.requestFocus(), 100, TimeUnit.MILLISECONDS); cancelButton1 = tuple.second; cancelButton1.setDefaultButton(false); - cancelButton1.setOnAction(e -> close()); + cancelButton1.setOnAction(e -> { + close(); + model.dataModel.swapTradeToSavings(); + }); cancelButton1.setId("cancel-button"); GridPane.setMargin(nextButton, new Insets(-35, 0, 0, 0)); @@ -757,27 +758,47 @@ public class CreateOfferView extends ActivatableViewAndModel placeOfferTuple = addButtonWithStatusAfterGroup(gridPane, ++gridRow, ""); - placeOfferButton = placeOfferTuple.first; + Tuple2 tuple = add2ButtonsAfterGroup(gridPane, ++gridRow, "Transfer from Bitsquare wallet", "Fund from external wallet"); + fundFromSavingsWalletButton = tuple.first; + fundFromSavingsWalletButton.setVisible(false); + fundFromSavingsWalletButton.setDefaultButton(false); + fundFromSavingsWalletButton.setOnAction(e -> model.useSavingsWalletForFunding()); + + fundFromExternalWalletButton = tuple.second; + fundFromExternalWalletButton.setVisible(false); + fundFromExternalWalletButton.setDefaultButton(false); + fundFromExternalWalletButton.setOnAction(e -> { + try { + Utilities.openURI(URI.create(getBitcoinURI())); + } catch (Exception ex) { + log.warn(ex.getMessage()); + new Popup().warning("Opening a default bitcoin wallet application has failed. " + + "Perhaps you don't have one installed?").show(); + } + }); + + placeOfferButton = addButton(gridPane, ++gridRow, ""); placeOfferButton.setVisible(false); placeOfferButton.setOnAction(e -> onPlaceOffer()); placeOfferButton.setMinHeight(40); placeOfferButton.setPadding(new Insets(0, 20, 0, 20)); - spinner = placeOfferTuple.second; - spinnerInfoLabel = placeOfferTuple.third; - cancelButton2 = addButton(gridPane, ++gridRow, BSResources.get("shared.cancel")); cancelButton2.setOnAction(e -> { - if (model.dataModel.isWalletFunded.get()) + if (model.dataModel.isWalletFunded.get()) { new Popup().warning("You have already paid in the funds.\n" + "Are you sure you want to cancel.") .actionButtonText("No") .closeButtonText("Yes, close") - .onClose(() -> close()) + .onClose(() -> { + close(); + model.dataModel.swapTradeToSavings(); + }) .show(); - else + } else { close(); + model.dataModel.swapTradeToSavings(); + } }); cancelButton2.setDefaultButton(false); cancelButton2.setVisible(false); @@ -786,7 +807,7 @@ public class CreateOfferView extends ActivatableViewAndModel volumeValidationResult = new SimpleObjectProperty<>(); // Those are needed for the addressTextField - final ObjectProperty totalToPayAsCoin = new SimpleObjectProperty<>(); final ObjectProperty
address = new SimpleObjectProperty<>(); private ChangeListener amountListener; @@ -108,7 +106,6 @@ class CreateOfferViewModel extends ActivatableWithDataModel volumeAsFiatListener; private ChangeListener isWalletFundedListener; private ChangeListener feeFromFundingTxListener; - private ChangeListener requestPlaceOfferErrorMessageListener; private ChangeListener errorMessageListener; private Offer offer; private Timer timeoutTimer; @@ -170,8 +167,6 @@ class CreateOfferViewModel extends ActivatableWithDataModel formatter.formatCoinWithCode(dataModel.amountAsCoin.get()), dataModel.amountAsCoin)); - totalToPayAsCoin.bind(dataModel.totalToPayAsCoin); btcCode.bind(dataModel.btcCode); tradeCurrencyCode.bind(dataModel.tradeCurrencyCode); @@ -221,7 +205,6 @@ class CreateOfferViewModel extends ActivatableWithDataModel { updateButtonDisableState(); - spinnerInfoText.set("Checking funding tx miner fee..."); }; feeFromFundingTxListener = (ov, oldValue, newValue) -> { updateButtonDisableState(); - if (newValue.compareTo(FeePolicy.getMinRequiredFeeForFundingTx()) >= 0) { - isSpinnerVisible.set(false); - spinnerInfoText.set(""); - } - }; - requestPlaceOfferErrorMessageListener = (ov, oldValue, newValue) -> { - if (newValue != null) { - isSpinnerVisible.set(false); - spinnerInfoText.set(""); - } }; } @@ -298,7 +270,6 @@ class CreateOfferViewModel extends ActivatableWithDataModel navigation.navigateTo(MainView.class, FundsView.class, DepositView.class)) + .show(); + return false; + } + } @@ -648,7 +636,7 @@ class CreateOfferViewModel extends ActivatableWithDataModel currencyComboBox; private ComboBox paymentMethodComboBox; private Button createOfferButton; - private TableColumn amountColumn, volumeColumn, priceColumn, paymentMethodColumn; + private TableColumn amountColumn, volumeColumn, priceColumn, paymentMethodColumn, avatarColumn; private TableView tableView; private OfferView.OfferActionHandler offerActionHandler; @@ -155,7 +155,8 @@ public class OfferBookView extends ActivatableViewAndModel o1.getOffer().getAmount().compareTo(o2.getOffer().getAmount())); volumeColumn.setComparator((o1, o2) -> o1.getOffer().getOfferVolume().compareTo(o2.getOffer().getOfferVolume())); paymentMethodColumn.setComparator((o1, o2) -> o1.getOffer().getPaymentMethod().compareTo(o2.getOffer().getPaymentMethod())); + avatarColumn.setComparator((o1, o2) -> o1.getOffer().getOwnerNodeAddress().hostName.compareTo(o2.getOffer().getOwnerNodeAddress().hostName)); createOfferButton = addButton(root, ++gridRow, ""); createOfferButton.setMinHeight(40); @@ -218,7 +220,6 @@ public class OfferBookView extends ActivatableViewAndModel 1; paymentAccountsLabel.setVisible(showComboBox); diff --git a/gui/src/main/java/io/bitsquare/gui/main/overlays/Overlay.java b/gui/src/main/java/io/bitsquare/gui/main/overlays/Overlay.java index e2d62d6bdd..de503a5bc9 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/overlays/Overlay.java +++ b/gui/src/main/java/io/bitsquare/gui/main/overlays/Overlay.java @@ -625,7 +625,6 @@ public abstract class Overlay { messageLabel = new Label(truncatedMessage); messageLabel.setMouseTransparent(true); messageLabel.setWrapText(true); - messageLabel.setId("popup-message"); GridPane.setHalignment(messageLabel, HPos.LEFT); GridPane.setHgrow(messageLabel, Priority.ALWAYS); GridPane.setMargin(messageLabel, new Insets(3, 0, 0, 0)); diff --git a/gui/src/main/java/io/bitsquare/gui/main/overlays/windows/QRCodeWindow.java b/gui/src/main/java/io/bitsquare/gui/main/overlays/windows/QRCodeWindow.java index f6aeebf63b..b011667ab3 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/overlays/windows/QRCodeWindow.java +++ b/gui/src/main/java/io/bitsquare/gui/main/overlays/windows/QRCodeWindow.java @@ -2,9 +2,12 @@ package io.bitsquare.gui.main.overlays.windows; import io.bitsquare.gui.main.overlays.Overlay; import javafx.geometry.HPos; +import javafx.geometry.Insets; +import javafx.scene.control.Label; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.GridPane; +import javafx.scene.layout.Priority; import net.glxn.qrgen.QRCode; import net.glxn.qrgen.image.ImageType; import org.slf4j.Logger; @@ -15,9 +18,10 @@ import java.io.ByteArrayInputStream; public class QRCodeWindow extends Overlay { private static final Logger log = LoggerFactory.getLogger(QRCodeWindow.class); private final ImageView qrCodeImageView; + private String bitcoinURI; public QRCodeWindow(String bitcoinURI) { - + this.bitcoinURI = bitcoinURI; final byte[] imageBytes = QRCode .from(bitcoinURI) .withSize(250, 250) @@ -45,6 +49,18 @@ public class QRCodeWindow extends Overlay { GridPane.setHalignment(qrCodeImageView, HPos.CENTER); gridPane.getChildren().add(qrCodeImageView); + Label infoLabel = new Label("Payment request:\n" + bitcoinURI); + infoLabel.setMouseTransparent(true); + infoLabel.setWrapText(true); + infoLabel.setId("popup-qr-code-info"); + GridPane.setHalignment(infoLabel, HPos.CENTER); + GridPane.setHgrow(infoLabel, Priority.ALWAYS); + GridPane.setMargin(infoLabel, new Insets(3, 0, 0, 0)); + GridPane.setRowIndex(infoLabel, ++rowIndex); + GridPane.setColumnIndex(infoLabel, 0); + GridPane.setColumnSpan(infoLabel, 2); + gridPane.getChildren().add(infoLabel); + addCloseButton(); applyStyles(); display(); diff --git a/gui/src/main/java/io/bitsquare/gui/main/portfolio/closedtrades/ClosedTradesView.fxml b/gui/src/main/java/io/bitsquare/gui/main/portfolio/closedtrades/ClosedTradesView.fxml index 80f4352538..4c6d6c8cd3 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/portfolio/closedtrades/ClosedTradesView.fxml +++ b/gui/src/main/java/io/bitsquare/gui/main/portfolio/closedtrades/ClosedTradesView.fxml @@ -26,15 +26,15 @@ - + - + - + diff --git a/gui/src/main/java/io/bitsquare/gui/main/portfolio/closedtrades/ClosedTradesView.java b/gui/src/main/java/io/bitsquare/gui/main/portfolio/closedtrades/ClosedTradesView.java index 69b1f7f66b..93d504a998 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/portfolio/closedtrades/ClosedTradesView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/portfolio/closedtrades/ClosedTradesView.java @@ -24,15 +24,19 @@ import io.bitsquare.gui.main.overlays.windows.OfferDetailsWindow; import io.bitsquare.gui.main.overlays.windows.TradeDetailsWindow; import io.bitsquare.gui.util.BSFormatter; import io.bitsquare.gui.util.ImageUtil; +import io.bitsquare.p2p.NodeAddress; import io.bitsquare.trade.Tradable; import io.bitsquare.trade.Trade; import io.bitsquare.trade.offer.OpenOffer; import javafx.beans.property.ReadOnlyObjectWrapper; +import javafx.collections.transformation.SortedList; import javafx.fxml.FXML; import javafx.scene.Node; import javafx.scene.control.*; import javafx.scene.layout.VBox; import javafx.util.Callback; +import org.bitcoinj.core.Coin; +import org.bitcoinj.utils.Fiat; import javax.inject.Inject; @@ -40,13 +44,14 @@ import javax.inject.Inject; public class ClosedTradesView extends ActivatableViewAndModel { @FXML - TableView table; + TableView tableView; @FXML TableColumn priceColumn, amountColumn, volumeColumn, directionColumn, dateColumn, tradeIdColumn, stateColumn, avatarColumn; private final BSFormatter formatter; private final OfferDetailsWindow offerDetailsWindow; private final TradeDetailsWindow tradeDetailsWindow; + private SortedList sortedList; @Inject public ClosedTradesView(ClosedTradesViewModel model, BSFormatter formatter, OfferDetailsWindow offerDetailsWindow, TradeDetailsWindow tradeDetailsWindow) { @@ -58,6 +63,9 @@ public class ClosedTradesView extends ActivatableViewAndModel o1.getTradable().getId().compareTo(o2.getTradable().getId())); + dateColumn.setComparator((o1, o2) -> o1.getTradable().getDate().compareTo(o2.getTradable().getDate())); + directionColumn.setComparator((o1, o2) -> o1.getTradable().getOffer().getDirection().compareTo(o2.getTradable().getOffer().getDirection())); + priceColumn.setComparator((o1, o2) -> o1.getTradable().getOffer().getPrice().compareTo(o2.getTradable().getOffer().getPrice())); + volumeColumn.setComparator((o1, o2) -> { + if (o1.getTradable() instanceof Trade && o2.getTradable() instanceof Trade) { + Fiat tradeVolume1 = ((Trade) o1.getTradable()).getTradeVolume(); + Fiat tradeVolume2 = ((Trade) o2.getTradable()).getTradeVolume(); + return tradeVolume1 != null && tradeVolume2 != null ? tradeVolume1.compareTo(tradeVolume2) : 0; + } else + return 0; + }); + amountColumn.setComparator((o1, o2) -> { + if (o1.getTradable() instanceof Trade && o2.getTradable() instanceof Trade) { + Coin amount1 = ((Trade) o1.getTradable()).getTradeAmount(); + Coin amount2 = ((Trade) o2.getTradable()).getTradeAmount(); + return amount1 != null && amount2 != null ? amount1.compareTo(amount2) : 0; + } else + return 0; + }); + avatarColumn.setComparator((o1, o2) -> { + if (o1.getTradable() instanceof Trade && o2.getTradable() instanceof Trade) { + NodeAddress tradingPeerNodeAddress1 = ((Trade) o1.getTradable()).getTradingPeerNodeAddress(); + NodeAddress tradingPeerNodeAddress2 = ((Trade) o2.getTradable()).getTradingPeerNodeAddress(); + String address1 = tradingPeerNodeAddress1 != null ? tradingPeerNodeAddress1.hostName : ""; + String address2 = tradingPeerNodeAddress2 != null ? tradingPeerNodeAddress2.hostName : ""; + return address1 != null && address2 != null ? address1.compareTo(address2) : 0; + } else + return 0; + }); + stateColumn.setComparator((o1, o2) -> model.getState(o1).compareTo(model.getState(o2))); + + dateColumn.setSortType(TableColumn.SortType.DESCENDING); + tableView.getSortOrder().add(dateColumn); } @Override protected void activate() { - table.setItems(model.getList()); + sortedList = new SortedList<>(model.getList()); + sortedList.comparatorProperty().bind(tableView.comparatorProperty()); + tableView.setItems(sortedList); + } + + @Override + protected void deactivate() { + sortedList.comparatorProperty().unbind(); } diff --git a/gui/src/main/java/io/bitsquare/gui/main/portfolio/failedtrades/FailedTradesView.fxml b/gui/src/main/java/io/bitsquare/gui/main/portfolio/failedtrades/FailedTradesView.fxml index de276de16a..40d8ea77d0 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/portfolio/failedtrades/FailedTradesView.fxml +++ b/gui/src/main/java/io/bitsquare/gui/main/portfolio/failedtrades/FailedTradesView.fxml @@ -26,15 +26,15 @@ - + - + - + diff --git a/gui/src/main/java/io/bitsquare/gui/main/portfolio/failedtrades/FailedTradesView.java b/gui/src/main/java/io/bitsquare/gui/main/portfolio/failedtrades/FailedTradesView.java index 667702ab0c..3a0b6ad5f9 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/portfolio/failedtrades/FailedTradesView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/portfolio/failedtrades/FailedTradesView.java @@ -22,6 +22,7 @@ import io.bitsquare.gui.common.view.FxmlView; import io.bitsquare.gui.components.HyperlinkWithIcon; import io.bitsquare.gui.main.overlays.windows.TradeDetailsWindow; import javafx.beans.property.ReadOnlyObjectWrapper; +import javafx.collections.transformation.SortedList; import javafx.fxml.FXML; import javafx.scene.control.*; import javafx.scene.layout.VBox; @@ -33,11 +34,12 @@ import javax.inject.Inject; public class FailedTradesView extends ActivatableViewAndModel { @FXML - TableView table; + TableView tableView; @FXML TableColumn priceColumn, amountColumn, volumeColumn, directionColumn, dateColumn, tradeIdColumn, stateColumn; private final TradeDetailsWindow tradeDetailsWindow; + private SortedList sortedList; @Inject public FailedTradesView(FailedTradesViewModel model, TradeDetailsWindow tradeDetailsWindow) { @@ -47,6 +49,9 @@ public class FailedTradesView extends ActivatableViewAndModel o1.getTrade().getId().compareTo(o2.getTrade().getId())); + dateColumn.setComparator((o1, o2) -> o1.getTrade().getDate().compareTo(o2.getTrade().getDate())); + priceColumn.setComparator((o1, o2) -> o1.getTrade().getOffer().getPrice().compareTo(o2.getTrade().getOffer().getPrice())); + volumeColumn.setComparator((o1, o2) -> o1.getTrade().getTradeVolume().compareTo(o2.getTrade().getTradeVolume())); + amountColumn.setComparator((o1, o2) -> o1.getTrade().getTradeAmount().compareTo(o2.getTrade().getTradeAmount())); + stateColumn.setComparator((o1, o2) -> model.getState(o1).compareTo(model.getState(o2))); + + dateColumn.setSortType(TableColumn.SortType.DESCENDING); + tableView.getSortOrder().add(dateColumn); + } @Override protected void activate() { - table.setItems(model.getList()); + sortedList = new SortedList<>(model.getList()); + sortedList.comparatorProperty().bind(tableView.comparatorProperty()); + tableView.setItems(sortedList); } + @Override + protected void deactivate() { + sortedList.comparatorProperty().unbind(); + } + + private void setTradeIdColumnCellFactory() { tradeIdColumn.setCellValueFactory((offerListItem) -> new ReadOnlyObjectWrapper<>(offerListItem.getValue())); tradeIdColumn.setCellFactory( diff --git a/gui/src/main/java/io/bitsquare/gui/main/portfolio/openoffer/OpenOffersView.fxml b/gui/src/main/java/io/bitsquare/gui/main/portfolio/openoffer/OpenOffersView.fxml index b81dfcadd7..4a77e66689 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/portfolio/openoffer/OpenOffersView.fxml +++ b/gui/src/main/java/io/bitsquare/gui/main/portfolio/openoffer/OpenOffersView.fxml @@ -26,9 +26,9 @@ - + - + diff --git a/gui/src/main/java/io/bitsquare/gui/main/portfolio/openoffer/OpenOffersView.java b/gui/src/main/java/io/bitsquare/gui/main/portfolio/openoffer/OpenOffersView.java index 885bab1a22..543b6ac4b7 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/portfolio/openoffer/OpenOffersView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/portfolio/openoffer/OpenOffersView.java @@ -28,6 +28,7 @@ import io.bitsquare.gui.main.overlays.popups.Popup; import io.bitsquare.gui.main.overlays.windows.OfferDetailsWindow; import io.bitsquare.trade.offer.OpenOffer; import javafx.beans.property.ReadOnlyObjectWrapper; +import javafx.collections.transformation.SortedList; import javafx.fxml.FXML; import javafx.scene.control.*; import javafx.scene.image.ImageView; @@ -40,12 +41,13 @@ import javax.inject.Inject; public class OpenOffersView extends ActivatableViewAndModel { @FXML - TableView table; + TableView tableView; @FXML TableColumn priceColumn, amountColumn, volumeColumn, directionColumn, dateColumn, offerIdColumn, removeItemColumn; private final Navigation navigation; private final OfferDetailsWindow offerDetailsWindow; + private SortedList sortedList; @Inject public OpenOffersView(OpenOffersViewModel model, Navigation navigation, OfferDetailsWindow offerDetailsWindow) { @@ -64,13 +66,30 @@ public class OpenOffersView extends ActivatableViewAndModel o1.getOffer().getId().compareTo(o2.getOffer().getId())); + directionColumn.setComparator((o1, o2) -> o1.getOffer().getDirection().compareTo(o2.getOffer().getDirection())); + amountColumn.setComparator((o1, o2) -> o1.getOffer().getAmount().compareTo(o2.getOffer().getAmount())); + priceColumn.setComparator((o1, o2) -> o1.getOffer().getPrice().compareTo(o2.getOffer().getPrice())); + volumeColumn.setComparator((o1, o2) -> o1.getOffer().getOfferVolume().compareTo(o2.getOffer().getOfferVolume())); + dateColumn.setComparator((o1, o2) -> o1.getOffer().getDate().compareTo(o2.getOffer().getDate())); + + dateColumn.setSortType(TableColumn.SortType.DESCENDING); + tableView.getSortOrder().add(dateColumn); } @Override protected void activate() { - table.setItems(model.getList()); + sortedList = new SortedList<>(model.getList()); + sortedList.comparatorProperty().bind(tableView.comparatorProperty()); + tableView.setItems(sortedList); + } + + @Override + protected void deactivate() { + sortedList.comparatorProperty().unbind(); } private void onRemoveOpenOffer(OpenOffer openOffer) { diff --git a/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/PendingTradesView.fxml b/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/PendingTradesView.fxml index 21b3927083..2da9860fc3 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/PendingTradesView.fxml +++ b/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/PendingTradesView.fxml @@ -18,7 +18,6 @@ --> - - + - - - - - + - - - - - - - - - - - - - - - + + + diff --git a/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/PendingTradesView.java b/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/PendingTradesView.java index 73e6a41453..6c85ae632e 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/PendingTradesView.java +++ b/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/PendingTradesView.java @@ -17,7 +17,6 @@ package io.bitsquare.gui.main.portfolio.pendingtrades; -import io.bitsquare.app.Log; import io.bitsquare.common.UserThread; import io.bitsquare.gui.common.view.ActivatableViewAndModel; import io.bitsquare.gui.common.view.FxmlView; @@ -27,12 +26,12 @@ import io.bitsquare.gui.main.overlays.windows.TradeDetailsWindow; import io.bitsquare.gui.util.BSFormatter; import io.bitsquare.gui.util.ImageUtil; import javafx.beans.property.ReadOnlyObjectWrapper; +import javafx.collections.transformation.SortedList; import javafx.event.EventHandler; import javafx.fxml.FXML; import javafx.scene.Node; import javafx.scene.Scene; import javafx.scene.control.*; -import javafx.scene.control.cell.TextFieldTableCell; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCodeCombination; import javafx.scene.input.KeyCombination; @@ -40,9 +39,6 @@ import javafx.scene.input.KeyEvent; import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; import javafx.util.Callback; -import javafx.util.StringConverter; -import org.bitcoinj.core.Coin; -import org.bitcoinj.utils.Fiat; import org.fxmisc.easybind.EasyBind; import org.fxmisc.easybind.Subscription; @@ -54,14 +50,12 @@ public class PendingTradesView extends ActivatableViewAndModel table; + TableView tableView; @FXML - TableColumn priceColumn, tradeVolumeColumn; + TableColumn priceColumn, tradeVolumeColumn, tradeAmountColumn, avatarColumn, roleColumn, paymentMethodColumn, idColumn, dateColumn; @FXML - TableColumn avatarColumn, roleColumn, paymentMethodColumn, idColumn, dateColumn; - @FXML - TableColumn tradeAmountColumn; + private SortedList sortedList; private TradeSubView selectedSubView; private EventHandler keyEventEventHandler; private Scene scene; @@ -92,9 +86,22 @@ public class PendingTradesView extends ActivatableViewAndModel o1.getTrade().getId().compareTo(o2.getTrade().getId())); + dateColumn.setComparator((o1, o2) -> o1.getTrade().getDate().compareTo(o2.getTrade().getDate())); + tradeVolumeColumn.setComparator((o1, o2) -> o1.getTrade().getTradeVolume().compareTo(o2.getTrade().getTradeVolume())); + tradeAmountColumn.setComparator((o1, o2) -> o1.getTrade().getTradeAmount().compareTo(o2.getTrade().getTradeAmount())); + priceColumn.setComparator((o1, o2) -> o1.getPrice().compareTo(o2.getPrice())); + paymentMethodColumn.setComparator((o1, o2) -> o1.getTrade().getOffer().getPaymentMethod().getId().compareTo(o2.getTrade().getOffer().getPaymentMethod().getId())); + avatarColumn.setComparator((o1, o2) -> o1.getTrade().getTradingPeerNodeAddress().hostName.compareTo(o2.getTrade().getTradingPeerNodeAddress().hostName)); + roleColumn.setComparator((o1, o2) -> model.getMyRole(o1).compareTo(model.getMyRole(o2))); + + dateColumn.setSortType(TableColumn.SortType.DESCENDING); + tableView.getSortOrder().add(dateColumn); + // we use a hidden emergency shortcut to open support ticket keyEventEventHandler = event -> { @@ -116,7 +123,10 @@ public class PendingTradesView extends ActivatableViewAndModel(model.dataModel.list); + sortedList.comparatorProperty().bind(tableView.comparatorProperty()); + tableView.setItems(sortedList); + scene = root.getScene(); if (scene != null) { scene.addEventHandler(KeyEvent.KEY_RELEASED, keyEventEventHandler); @@ -133,7 +143,6 @@ public class PendingTradesView extends ActivatableViewAndModel { if (selectedItem != null) { @@ -164,7 +173,7 @@ public class PendingTradesView extends ActivatableViewAndModel { if (selectedItem != null && !selectedItem.equals(model.dataModel.selectedItemProperty.get())) model.dataModel.onSelectItem(selectedItem); @@ -175,6 +184,7 @@ public class PendingTradesView extends ActivatableViewAndModel { //TODO app wide focus - table.getSelectionModel().select(index); + tableView.getSelectionModel().select(index); //table.requestFocus(); //UserThread.execute(() -> table.getFocusModel().focus(index)); }); @@ -280,49 +290,69 @@ public class PendingTradesView extends ActivatableViewAndModelforTableColumn( - new StringConverter() { + tradeAmountColumn.setCellValueFactory((offer) -> new ReadOnlyObjectWrapper<>(offer.getValue())); + tradeAmountColumn.setCellFactory( + new Callback, TableCell>() { @Override - public String toString(Coin value) { - return formatter.formatCoinWithCode(value); + public TableCell call( + TableColumn column) { + return new TableCell() { + @Override + public void updateItem(final PendingTradesListItem item, boolean empty) { + super.updateItem(item, empty); + if (item != null && !empty) + setText(formatter.formatCoinWithCode(item.getTrade().getPayoutAmount())); + else + setText(null); + } + }; } - - @Override - public Coin fromString(String string) { - return null; - } - })); + }); } private void setPriceColumnCellFactory() { - priceColumn.setCellFactory(TextFieldTableCell.forTableColumn( - new StringConverter() { + priceColumn.setCellValueFactory((offer) -> new ReadOnlyObjectWrapper<>(offer.getValue())); + priceColumn.setCellFactory( + new Callback, TableCell>() { @Override - public String toString(Fiat value) { - return formatter.formatPriceWithCode(value); + public TableCell call( + TableColumn column) { + return new TableCell() { + @Override + public void updateItem(final PendingTradesListItem item, boolean empty) { + super.updateItem(item, empty); + if (item != null && !empty) + setText(formatter.formatPriceWithCode(item.getPrice())); + else + setText(null); + } + }; } - - @Override - public Fiat fromString(String string) { - return null; - } - })); - + }); } private void setVolumeColumnCellFactory() { - tradeVolumeColumn.setCellFactory(TextFieldTableCell.forTableColumn( - new StringConverter() { + tradeVolumeColumn.setCellValueFactory((offer) -> new ReadOnlyObjectWrapper<>(offer.getValue())); + tradeVolumeColumn.setCellFactory( + new Callback, TableCell>() { @Override - public String toString(Fiat value) { - return formatter.formatFiatWithCode(value); + public TableCell call( + TableColumn column) { + return new TableCell() { + @Override + public void updateItem(final PendingTradesListItem item, boolean empty) { + super.updateItem(item, empty); + if (item != null && !empty) + setText(formatter.formatPriceWithCode(item.getTrade().getTradeVolume())); + else + setText(null); + } + }; } - - @Override - public Fiat fromString(String string) { - return null; - } - })); + }); } private void setPaymentMethodColumnCellFactory() { diff --git a/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/steps/buyer/BuyerStep5View.java b/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/steps/buyer/BuyerStep5View.java index 4150cd6668..026cdd96cd 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/steps/buyer/BuyerStep5View.java +++ b/gui/src/main/java/io/bitsquare/gui/main/portfolio/pendingtrades/steps/buyer/BuyerStep5View.java @@ -48,7 +48,7 @@ public class BuyerStep5View extends TradeStepView { protected Label btcTradeAmountLabel; protected Label fiatTradeAmountLabel; private InputTextField withdrawAddressTextField; - private Button withdrawButton; + private Button withdrawToExternalWalletButton, useSavingsWalletButton; /////////////////////////////////////////////////////////////////////////////////////////// @@ -116,11 +116,19 @@ public class BuyerStep5View extends TradeStepView { addTitledGroupBg(gridPane, ++gridRow, 2, "Withdraw your bitcoins", Layout.GROUP_DISTANCE); addLabelTextField(gridPane, gridRow, "Amount to withdraw:", model.getPayoutAmount(), Layout.FIRST_ROW_AND_GROUP_DISTANCE); withdrawAddressTextField = addLabelInputTextField(gridPane, ++gridRow, "Withdraw to address:").second; - withdrawButton = addButtonAfterGroup(gridPane, ++gridRow, "Withdraw to external wallet"); - withdrawButton.setOnAction(e -> reviewWithdrawal()); + + Tuple2 tuple2 = add2ButtonsAfterGroup(gridPane, ++gridRow, "Move to Bitsquare wallet", "Withdraw to external wallet"); + useSavingsWalletButton = tuple2.first; + withdrawToExternalWalletButton = tuple2.second; + useSavingsWalletButton.setOnAction(e -> { + model.dataModel.walletService.swapTradeToSavings(trade.getId()); + handleTradeCompleted(); + model.dataModel.tradeManager.addTradeToClosedTrades(trade); + }); + withdrawToExternalWalletButton.setOnAction(e -> reviewWithdrawal()); if (BitsquareApp.DEV_MODE) { - withdrawAddressTextField.setText("mi8k5f9L972VgDaT4LgjAhriC9hHEPL7EW"); + withdrawAddressTextField.setText("mo6y756TnpdZQCeHStraavjqrndeXzVkxi"); } else { String key = "tradeCompleted" + trade.getId(); if (preferences.showAgain(key)) { @@ -133,29 +141,6 @@ public class BuyerStep5View extends TradeStepView { } } - private void doWithdrawal() { - withdrawButton.setDisable(true); - model.dataModel.onWithdrawRequest(withdrawAddressTextField.getText(), - () -> { - String key = "tradeCompleteWithdrawCompletedInfo"; - new Popup().headLine("Withdrawal completed") - .feedback("Your completed trades are stored under \"Portfolio/History\".\n" + - "You can review all your bitcoin transactions under \"Funds/Transactions\"") - .actionButtonText("Go to \"Transactions\"") - .onAction(() -> model.dataModel.navigation.navigateTo(MainView.class, FundsView.class, TransactionsView.class)) - .dontShowAgainId(key, preferences) - .show(); - withdrawButton.setDisable(true); - }, - (errorMessage, throwable) -> { - withdrawButton.setDisable(false); - if (throwable != null && throwable.getMessage() != null) - new Popup().error(errorMessage + "\n\n" + throwable.getMessage()).show(); - else - new Popup().error(errorMessage).show(); - }); - } - private void reviewWithdrawal() { Coin senderAmount = trade.getPayoutAmount(); WalletService walletService = model.dataModel.walletService; @@ -174,11 +159,11 @@ public class BuyerStep5View extends TradeStepView { validateWithdrawAddress(); } else if (Restrictions.isAboveFixedTxFeeAndDust(senderAmount)) { try { - Coin requiredFee = walletService.getRequiredFee(fromAddresses, toAddresses, senderAmount, null); - Coin receiverAmount = senderAmount.subtract(requiredFee); if (BitsquareApp.DEV_MODE) { doWithdrawal(); } else { + Coin requiredFee = walletService.getRequiredFee(fromAddresses, toAddresses, senderAmount, null); + Coin receiverAmount = senderAmount.subtract(requiredFee); BSFormatter formatter = model.formatter; String key = "reviewWithdrawalAtTradeComplete"; if (preferences.showAgain(key)) { @@ -190,9 +175,12 @@ public class BuyerStep5View extends TradeStepView { "The recipient will receive: " + formatter.formatCoinWithCode(receiverAmount) + "\n\n" + "Are you sure you want to proceed with the withdrawal?") .closeButtonText("Cancel") - .onClose(() -> withdrawButton.setDisable(false)) + .onClose(() -> { + useSavingsWalletButton.setDisable(false); + withdrawToExternalWalletButton.setDisable(false); + }) .actionButtonText("Yes") - .onAction(this::doWithdrawal) + .onAction(() -> doWithdrawal()) .dontShowAgainId(key, preferences) .show(); } else { @@ -210,10 +198,41 @@ public class BuyerStep5View extends TradeStepView { } } + private void doWithdrawal() { + useSavingsWalletButton.setDisable(true); + withdrawToExternalWalletButton.setDisable(true); + + model.dataModel.onWithdrawRequest(withdrawAddressTextField.getText(), + () -> { + handleTradeCompleted(); + }, + (errorMessage, throwable) -> { + useSavingsWalletButton.setDisable(false); + withdrawToExternalWalletButton.setDisable(false); + if (throwable != null && throwable.getMessage() != null) + new Popup().error(errorMessage + "\n\n" + throwable.getMessage()).show(); + else + new Popup().error(errorMessage).show(); + }); + } + + private void handleTradeCompleted() { + String key = "tradeCompleteWithdrawCompletedInfo"; + new Popup().headLine("Withdrawal completed") + .feedback("Your completed trades are stored under \"Portfolio/History\".\n" + + "You can review all your bitcoin transactions under \"Funds/Transactions\"") + .actionButtonText("Go to \"Transactions\"") + .onAction(() -> model.dataModel.navigation.navigateTo(MainView.class, FundsView.class, TransactionsView.class)) + .dontShowAgainId(key, preferences) + .show(); + useSavingsWalletButton.setDisable(true); + withdrawToExternalWalletButton.setDisable(true); + } + private void validateWithdrawAddress() { withdrawAddressTextField.setValidator(model.btcAddressValidator); withdrawAddressTextField.requestFocus(); - withdrawButton.requestFocus(); + useSavingsWalletButton.requestFocus(); } protected String getBtcTradeAmountLabel() { diff --git a/gui/src/main/java/io/bitsquare/gui/main/settings/network/NetworkSettingsView.fxml b/gui/src/main/java/io/bitsquare/gui/main/settings/network/NetworkSettingsView.fxml index 0b4f3638be..a8b14a085e 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/settings/network/NetworkSettingsView.fxml +++ b/gui/src/main/java/io/bitsquare/gui/main/settings/network/NetworkSettingsView.fxml @@ -66,7 +66,7 @@