mirror of
https://github.com/bisq-network/bisq.git
synced 2025-02-25 07:27:18 +01:00
Add create proposal view and fee payment tx. Add tabs for other dao subviews
This commit is contained in:
parent
44f8baecef
commit
790563e997
27 changed files with 1565 additions and 123 deletions
|
@ -116,4 +116,9 @@ public class FeeService {
|
|||
return Coin.valueOf(feeData.takeOfferFee);
|
||||
}
|
||||
|
||||
public Coin getCreateProposalFee() {
|
||||
//TODO
|
||||
return Coin.valueOf(1000);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
* This file is part of Bitsquare.
|
||||
*
|
||||
* Bitsquare is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.bitsquare.btc.wallet;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.bitcoinj.core.*;
|
||||
import org.bitcoinj.params.RegTestParams;
|
||||
import org.bitcoinj.wallet.CoinSelection;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* We use a specialized version of the CoinSelector based on the DefaultCoinSelector implementation.
|
||||
* We lookup for spendable outputs which matches our address of our address.
|
||||
*/
|
||||
class BtcProposalFeeCoinSelector {
|
||||
private static final Logger log = LoggerFactory.getLogger(BtcProposalFeeCoinSelector.class);
|
||||
|
||||
private Wallet wallet;
|
||||
|
||||
public BtcProposalFeeCoinSelector(Wallet wallet) {
|
||||
this.wallet = wallet;
|
||||
}
|
||||
|
||||
public CoinSelection getCoinSelection(Coin target) throws InsufficientMoneyException, ChangeBelowDustException {
|
||||
return select(target, getCandidates());
|
||||
}
|
||||
|
||||
private List<TransactionOutput> getCandidates() {
|
||||
return wallet.calculateAllSpendCandidates(true, true);
|
||||
}
|
||||
|
||||
private CoinSelection select(Coin target, List<TransactionOutput> candidates) throws InsufficientMoneyException, ChangeBelowDustException {
|
||||
ArrayList<TransactionOutput> selected = new ArrayList<>();
|
||||
// Sort the inputs by value so we get the lowest values first.
|
||||
ArrayList<TransactionOutput> sortedOutputs = new ArrayList<>(candidates);
|
||||
// When calculating the wallet balance, we may be asked to select all possible coins, if so, avoid sorting
|
||||
// them in order to improve performance.
|
||||
if (!target.equals(NetworkParameters.MAX_MONEY))
|
||||
sortOutputsByValue(sortedOutputs);
|
||||
|
||||
// Now iterate over the sorted outputs until we have got as close to the target as possible or a little
|
||||
// bit over (excessive value will be change).
|
||||
long total = 0;
|
||||
long dust = Transaction.MIN_NONDUST_OUTPUT.value;
|
||||
long targetValue = target.value;
|
||||
for (TransactionOutput output : sortedOutputs) {
|
||||
if (total >= targetValue) {
|
||||
long change = total - targetValue;
|
||||
// If we get a change it must not be smaller than dust
|
||||
if (change >= dust || change == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
// Only pick chain-included transactions, or transactions that are ours and pending.
|
||||
if (!isSelectable(output.getParentTransaction()))
|
||||
continue;
|
||||
|
||||
selected.add(output);
|
||||
total += output.getValue().value;
|
||||
}
|
||||
|
||||
long missing = targetValue - total;
|
||||
if (missing > 0)
|
||||
throw new InsufficientMoneyException(Coin.valueOf(missing));
|
||||
|
||||
long change = total - targetValue;
|
||||
if (change > 0 && change < dust)
|
||||
throw new ChangeBelowDustException(Coin.valueOf(change));
|
||||
|
||||
return new CoinSelection(Coin.valueOf(total), selected);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void sortOutputsByValue(ArrayList<TransactionOutput> outputs) {
|
||||
Collections.sort(outputs, (a, b) -> {
|
||||
int c2 = a.getValue().compareTo(b.getValue());
|
||||
if (c2 != 0) return c2;
|
||||
// They are entirely equivalent (possibly pending) so sort by hash to ensure a total ordering.
|
||||
Sha256Hash parentTransactionHash = a.getParentTransactionHash();
|
||||
Sha256Hash parentTransactionHash1 = b.getParentTransactionHash();
|
||||
if (parentTransactionHash != null && parentTransactionHash1 != null) {
|
||||
BigInteger aHash = parentTransactionHash.toBigInteger();
|
||||
BigInteger bHash = parentTransactionHash1.toBigInteger();
|
||||
return aHash.compareTo(bHash);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public boolean isSelectable(Transaction tx) {
|
||||
if (tx != null) {
|
||||
// Only pick chain-included transactions, or transactions that are ours and pending.
|
||||
TransactionConfidence confidence = tx.getConfidence();
|
||||
TransactionConfidence.ConfidenceType type = confidence.getConfidenceType();
|
||||
return type.equals(TransactionConfidence.ConfidenceType.BUILDING) ||
|
||||
type.equals(TransactionConfidence.ConfidenceType.PENDING) &&
|
||||
confidence.getSource().equals(TransactionConfidence.Source.SELF) &&
|
||||
// In regtest mode we expect to have only one peer, so we won't see transactions propagate.
|
||||
// TODO: The value 1 below dates from a time when transactions we broadcast *to* were counted, set to 0
|
||||
(confidence.numBroadcastPeers() > 1 || tx.getParams() == RegTestParams.get());
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -17,7 +17,6 @@
|
|||
|
||||
package io.bitsquare.btc.wallet;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
|
@ -30,6 +29,7 @@ import io.bitsquare.user.Preferences;
|
|||
import org.bitcoinj.core.*;
|
||||
import org.bitcoinj.crypto.DeterministicKey;
|
||||
import org.bitcoinj.crypto.KeyCrypterScrypt;
|
||||
import org.bitcoinj.script.ScriptBuilder;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -43,6 +43,7 @@ import java.util.Optional;
|
|||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
/**
|
||||
|
@ -160,8 +161,10 @@ public class BtcWalletService extends WalletService {
|
|||
wallet.completeTx(sendRequest);
|
||||
|
||||
resultTx = sendRequest.tx;
|
||||
//TODO add estimated signature size
|
||||
txSize = resultTx.bitcoinSerialize().length;
|
||||
}
|
||||
while (counter < 10 && Math.abs(resultTx.getFee().value - txFeePerByte.multiply(resultTx.bitcoinSerialize().length).value) > 1000);
|
||||
while (counter < 10 && Math.abs(resultTx.getFee().value - txFeePerByte.multiply(txSize).value) > 1000);
|
||||
if (counter == 10) {
|
||||
log.error("Could not calculate the fee. Tx=" + resultTx);
|
||||
}
|
||||
|
@ -185,6 +188,119 @@ public class BtcWalletService extends WalletService {
|
|||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Add mining fee input and mandatory change output for prepared proposal fee tx
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public Transaction addInputAndOutputToPreparedSquProposalFeeTx(Transaction preparedSquTx, byte[] hash) throws
|
||||
TransactionVerificationException, WalletException, InsufficientFundsException {
|
||||
|
||||
// preparedSquTx has following structure:
|
||||
// inputs [1-n] SQU inputs
|
||||
// outputs [0-1] SQU change output
|
||||
// mining fee: SQU proposal fee
|
||||
|
||||
try {
|
||||
int counter = 0;
|
||||
final int sigSizePerInput = 106;
|
||||
int txSize = 203; // typical size for a tx with 2 unsigned inputs and 3 outputs
|
||||
final Coin txFeePerByte = feeService.getTxFeePerByte();
|
||||
Coin valueToForceChange = Coin.ZERO;
|
||||
Address changeAddress = getOrCreateAddressEntry(AddressEntry.Context.AVAILABLE).getAddress();
|
||||
checkNotNull(changeAddress, "changeAddress must not be null");
|
||||
final BtcCoinSelector coinSelector = new BtcCoinSelector(params, changeAddress);
|
||||
|
||||
final List<TransactionInput> preparedSquTxInputs = preparedSquTx.getInputs();
|
||||
final List<TransactionOutput> preparedSquTxOutputs = preparedSquTx.getOutputs();
|
||||
Transaction resultTx = null;
|
||||
int numInputs = preparedSquTxInputs.size() + 1;
|
||||
boolean validTx;
|
||||
do {
|
||||
counter++;
|
||||
if (counter >= 10) {
|
||||
checkNotNull(resultTx, "resultTx must not be null");
|
||||
log.error("Could not calculate the fee. Tx=" + resultTx);
|
||||
break;
|
||||
}
|
||||
|
||||
Transaction tx = new Transaction(params);
|
||||
preparedSquTxInputs.stream().forEach(tx::addInput);
|
||||
|
||||
if (valueToForceChange.isZero()) {
|
||||
preparedSquTxOutputs.stream().forEach(tx::addOutput);
|
||||
} else {
|
||||
//TODO test that case
|
||||
// We have only 1 output at preparedSquTxOutputs otherwise the valueToForceChange must be 0
|
||||
checkArgument(preparedSquTxOutputs.size() == 1);
|
||||
tx.addOutput(valueToForceChange, changeAddress);
|
||||
tx.addOutput(preparedSquTxOutputs.get(0));
|
||||
}
|
||||
|
||||
Wallet.SendRequest sendRequest = Wallet.SendRequest.forTx(tx);
|
||||
sendRequest.shuffleOutputs = false;
|
||||
sendRequest.aesKey = aesKey;
|
||||
// Need to be false as it would try to sign all inputs (SQU inputs are not in this wallet)
|
||||
sendRequest.signInputs = false;
|
||||
sendRequest.ensureMinRequiredFee = false;
|
||||
sendRequest.feePerKb = Coin.ZERO;
|
||||
Coin fee = txFeePerByte.multiply(txSize + sigSizePerInput * numInputs);
|
||||
sendRequest.fee = fee;
|
||||
sendRequest.coinSelector = coinSelector;
|
||||
sendRequest.changeAddress = changeAddress;
|
||||
wallet.completeTx(sendRequest);
|
||||
|
||||
Transaction txWithBtcInputs = sendRequest.tx;
|
||||
// add OP_RETURN output
|
||||
txWithBtcInputs.addOutput(new TransactionOutput(params, txWithBtcInputs, Coin.ZERO, ScriptBuilder.createOpReturnScript(hash).getProgram()));
|
||||
|
||||
|
||||
// As we cannot remove an outut we recreate the tx
|
||||
resultTx = new Transaction(params);
|
||||
txWithBtcInputs.getInputs().stream().forEach(resultTx::addInput);
|
||||
txWithBtcInputs.getOutputs().stream().forEach(resultTx::addOutput);
|
||||
|
||||
numInputs = resultTx.getInputs().size();
|
||||
|
||||
printTx("resultTx", resultTx);
|
||||
txSize = resultTx.bitcoinSerialize().length + sigSizePerInput * numInputs;
|
||||
log.error("txSize " + txSize);
|
||||
// We need min. 1 output beside op_return
|
||||
// calculated fee must be inside of a tolerance range with tx fee
|
||||
final int tolerance = 1000;
|
||||
final long txFeeAsLong = resultTx.getFee().value;
|
||||
final long calculatedFeeAsLong = txFeePerByte.multiply(txSize + sigSizePerInput * numInputs).value;
|
||||
final boolean correctOutputs = resultTx.getOutputs().size() > 1;
|
||||
validTx = correctOutputs && Math.abs(txFeeAsLong - calculatedFeeAsLong) < tolerance;
|
||||
|
||||
if (resultTx.getOutputs().size() == 1) {
|
||||
// We have the rare case that both inputs equalled the required fees, so both did not render
|
||||
// a change output.
|
||||
// We need to add artificially a change output as the OP_RETURN is not allowed as only output
|
||||
valueToForceChange = Transaction.MIN_NONDUST_OUTPUT;
|
||||
}
|
||||
}
|
||||
while (!validTx);
|
||||
|
||||
// Sign all BTC inputs
|
||||
int startIndex = preparedSquTxInputs.size();
|
||||
int endIndex = resultTx.getInputs().size();
|
||||
for (int i = startIndex; i < endIndex; i++) {
|
||||
TransactionInput txIn = resultTx.getInputs().get(i);
|
||||
signTransactionInput(resultTx, txIn, i);
|
||||
checkScriptSig(resultTx, txIn, i);
|
||||
}
|
||||
|
||||
checkWalletConsistency();
|
||||
verifyTransaction(resultTx);
|
||||
|
||||
printTx("Signed resultTx", resultTx);
|
||||
return resultTx;
|
||||
} catch (InsufficientMoneyException e) {
|
||||
throw new InsufficientFundsException("The fees for that preparedSquTx exceed the available funds " +
|
||||
"or the resulting output value is below the min. dust value:\n" +
|
||||
"Missing " + (e.missing != null ? e.missing.toFriendlyString() : "null"));
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// AddressEntry
|
||||
|
@ -571,7 +687,7 @@ public class BtcWalletService extends WalletService {
|
|||
AddressEntry.Context context) throws AddressFormatException,
|
||||
AddressEntryException {
|
||||
Transaction tx = new Transaction(params);
|
||||
Preconditions.checkArgument(Restrictions.isAboveDust(amount, fee),
|
||||
checkArgument(Restrictions.isAboveDust(amount, fee),
|
||||
"The amount is too low (dust limit).");
|
||||
tx.addOutput(amount.subtract(fee), new Address(params, toAddress));
|
||||
|
||||
|
@ -599,7 +715,7 @@ public class BtcWalletService extends WalletService {
|
|||
@Nullable KeyParameter aesKey) throws
|
||||
AddressFormatException, AddressEntryException {
|
||||
Transaction tx = new Transaction(params);
|
||||
Preconditions.checkArgument(Restrictions.isAboveDust(amount),
|
||||
checkArgument(Restrictions.isAboveDust(amount),
|
||||
"The amount is too low (dust limit).");
|
||||
tx.addOutput(amount.subtract(fee), new Address(params, toAddress));
|
||||
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* This file is part of Bitsquare.
|
||||
*
|
||||
* Bitsquare is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.bitsquare.btc.wallet;
|
||||
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class ChangeBelowDustException extends Exception {
|
||||
private static final Logger log = LoggerFactory.getLogger(ChangeBelowDustException.class);
|
||||
private final Coin change;
|
||||
|
||||
public ChangeBelowDustException(Coin change) {
|
||||
this(change, "Change value would be below min. dust. Change: " + change + " satoshis");
|
||||
}
|
||||
|
||||
public ChangeBelowDustException(Coin change, String message) {
|
||||
super(message);
|
||||
this.change = change;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,132 @@
|
|||
/*
|
||||
* This file is part of Bitsquare.
|
||||
*
|
||||
* Bitsquare is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.bitsquare.btc.wallet;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.bitcoinj.core.*;
|
||||
import org.bitcoinj.params.RegTestParams;
|
||||
import org.bitcoinj.wallet.CoinSelection;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* We use a specialized version of the CoinSelector based on the DefaultCoinSelector implementation.
|
||||
* We lookup for spendable outputs which matches our address of our address.
|
||||
*/
|
||||
class SquFeeCoinSelector {
|
||||
private static final Logger log = LoggerFactory.getLogger(SquFeeCoinSelector.class);
|
||||
|
||||
private Wallet wallet;
|
||||
private final boolean allowUnconfirmedSpend;
|
||||
|
||||
public SquFeeCoinSelector(Wallet wallet, boolean allowUnconfirmedSpend) {
|
||||
this.wallet = wallet;
|
||||
this.allowUnconfirmedSpend = allowUnconfirmedSpend;
|
||||
}
|
||||
|
||||
public CoinSelection getCoinSelection(Coin target) throws InsufficientMoneyException, ChangeBelowDustException {
|
||||
return select(target, getCandidates());
|
||||
}
|
||||
|
||||
private List<TransactionOutput> getCandidates() {
|
||||
//TODO
|
||||
wallet.setUTXOProvider(null);
|
||||
return wallet.calculateAllSpendCandidates(true, true);
|
||||
}
|
||||
|
||||
private CoinSelection select(Coin target, List<TransactionOutput> candidates) throws InsufficientMoneyException, ChangeBelowDustException {
|
||||
ArrayList<TransactionOutput> selected = new ArrayList<>();
|
||||
// Sort the inputs by value so we get the lowest values first.
|
||||
ArrayList<TransactionOutput> sortedOutputs = new ArrayList<>(candidates);
|
||||
// When calculating the wallet balance, we may be asked to select all possible coins, if so, avoid sorting
|
||||
// them in order to improve performance.
|
||||
if (!target.equals(NetworkParameters.MAX_MONEY))
|
||||
sortOutputsByValue(sortedOutputs);
|
||||
|
||||
// Now iterate over the sorted outputs until we have got as close to the target as possible or a little
|
||||
// bit over (excessive value will be change).
|
||||
long total = 0;
|
||||
long dust = Transaction.MIN_NONDUST_OUTPUT.value;
|
||||
long targetValue = target.value;
|
||||
for (TransactionOutput output : sortedOutputs) {
|
||||
if (total >= targetValue) {
|
||||
long change = total - targetValue;
|
||||
// If we get a change it must not be smaller than dust
|
||||
if (change >= dust || change == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
// Only pick chain-included transactions, or transactions that are ours and pending.
|
||||
if (!isSelectable(output.getParentTransaction()))
|
||||
continue;
|
||||
|
||||
selected.add(output);
|
||||
total += output.getValue().value;
|
||||
}
|
||||
|
||||
long missing = targetValue - total;
|
||||
if (missing > 0)
|
||||
throw new InsufficientMoneyException(Coin.valueOf(missing));
|
||||
|
||||
long change = total - targetValue;
|
||||
if (change > 0 && change < dust)
|
||||
throw new ChangeBelowDustException(Coin.valueOf(change));
|
||||
|
||||
return new CoinSelection(Coin.valueOf(total), selected);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void sortOutputsByValue(ArrayList<TransactionOutput> outputs) {
|
||||
Collections.sort(outputs, (a, b) -> {
|
||||
int c2 = a.getValue().compareTo(b.getValue());
|
||||
if (c2 != 0) return c2;
|
||||
// They are entirely equivalent (possibly pending) so sort by hash to ensure a total ordering.
|
||||
Sha256Hash parentTransactionHash = a.getParentTransactionHash();
|
||||
Sha256Hash parentTransactionHash1 = b.getParentTransactionHash();
|
||||
if (parentTransactionHash != null && parentTransactionHash1 != null) {
|
||||
BigInteger aHash = parentTransactionHash.toBigInteger();
|
||||
BigInteger bHash = parentTransactionHash1.toBigInteger();
|
||||
return aHash.compareTo(bHash);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public boolean isSelectable(Transaction tx) {
|
||||
if (tx != null) {
|
||||
// Only pick chain-included transactions, or transactions that are ours and pending.
|
||||
TransactionConfidence confidence = tx.getConfidence();
|
||||
TransactionConfidence.ConfidenceType type = confidence.getConfidenceType();
|
||||
boolean isMine = confidence.getSource().equals(TransactionConfidence.Source.SELF) &&
|
||||
// In regtest mode we expect to have only one peer, so we won't see transactions propagate.
|
||||
// TODO: The value 1 below dates from a time when transactions we broadcast *to* were counted, set to 0
|
||||
(confidence.numBroadcastPeers() > 1 || tx.getParams() == RegTestParams.get());
|
||||
return type.equals(TransactionConfidence.ConfidenceType.BUILDING) ||
|
||||
(type.equals(TransactionConfidence.ConfidenceType.PENDING) && (allowUnconfirmedSpend || isMine));
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -23,15 +23,16 @@ import org.slf4j.Logger;
|
|||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.*;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class SquUTXOProvider implements UTXOProvider {
|
||||
private static final Logger log = LoggerFactory.getLogger(SquUTXOProvider.class);
|
||||
|
||||
private Map<String, UTXO> utxoByAddressAsStringMap = new HashMap<>();
|
||||
private Set<String> addressesAsStringSet = new HashSet<>();
|
||||
private Set<UTXO> transactionOutputSet = new HashSet<>();
|
||||
private NetworkParameters params;
|
||||
|
||||
@Inject
|
||||
|
@ -45,22 +46,7 @@ public class SquUTXOProvider implements UTXOProvider {
|
|||
|
||||
@Override
|
||||
public List<UTXO> getOpenTransactionOutputs(List<Address> addresses) throws UTXOProviderException {
|
||||
// addressesAsStringSet.clear();
|
||||
// addressesAsStringSet.addAll(addresses.stream().map(Address::toString).collect(Collectors.toSet()));
|
||||
List<UTXO> foundOutputs = addresses.stream().map(e -> utxoByAddressAsStringMap.get(e.toString())).filter(e -> e != null).collect(Collectors.toList());
|
||||
|
||||
// List<UTXO> foundOutputs = transactionOutputSet.stream().filter(e -> addressesAsStringSet.contains(e.getAddress())).collect(Collectors.toList());
|
||||
/*
|
||||
List<UTXO> foundOutputs = new ArrayList<UTXO>();
|
||||
Collection<UTXO> outputsList = transactionOutputMap.values();
|
||||
for (UTXO output : outputsList) {
|
||||
for (Address address : addresses) {
|
||||
if (output.getAddress().equals(address.toString())) {
|
||||
foundOutputs.add(output);
|
||||
}
|
||||
}
|
||||
}*/
|
||||
return foundOutputs;
|
||||
return addresses.stream().map(e -> utxoByAddressAsStringMap.get(e.toString())).filter(e -> e != null).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -73,23 +59,4 @@ public class SquUTXOProvider implements UTXOProvider {
|
|||
public NetworkParameters getParams() {
|
||||
return params;
|
||||
}
|
||||
/*
|
||||
private String getScriptAddress(@Nullable Script script) {
|
||||
String address = "";
|
||||
try {
|
||||
if (script != null) {
|
||||
address = script.getToAddress(params, true).toString();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
}
|
||||
return address;
|
||||
}
|
||||
|
||||
private Script getScript(byte[] scriptBytes) {
|
||||
try {
|
||||
return new Script(scriptBytes);
|
||||
} catch (Exception e) {
|
||||
return new Script(new byte[0]);
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
|
||||
package io.bitsquare.btc.wallet;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import io.bitsquare.btc.Restrictions;
|
||||
|
@ -30,6 +29,8 @@ import io.bitsquare.common.handlers.ResultHandler;
|
|||
import io.bitsquare.user.Preferences;
|
||||
import org.bitcoinj.core.*;
|
||||
import org.bitcoinj.script.Script;
|
||||
import org.bitcoinj.wallet.CoinSelection;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -38,6 +39,8 @@ import java.util.List;
|
|||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
/**
|
||||
* WalletService handles all non trade specific wallet and bitcoin related services.
|
||||
* It startup the wallet app kit and initialized the wallet.
|
||||
|
@ -139,16 +142,20 @@ public class SquWalletService extends WalletService {
|
|||
return wallet != null ? wallet.getBalance(Wallet.BalanceType.AVAILABLE) : Coin.ZERO;
|
||||
}
|
||||
|
||||
public void requestSquUtxo(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
|
||||
public void requestSquUtxo(@Nullable ResultHandler resultHandler, @Nullable ErrorMessageHandler errorMessageHandler) {
|
||||
Set<UTXO> utxoSet = squUtxoFeedService.getUtxoSet();
|
||||
if (utxoSet != null)
|
||||
squUTXOProvider.setUtxoSet(utxoSet);
|
||||
|
||||
squUtxoFeedService.requestSquUtxo(utxos -> {
|
||||
squUTXOProvider.setUtxoSet(utxos);
|
||||
resultHandler.handleResult();
|
||||
if (resultHandler != null)
|
||||
resultHandler.handleResult();
|
||||
},
|
||||
(errorMessage, throwable) -> errorMessageHandler.handleErrorMessage(errorMessage));
|
||||
(errorMessage, throwable) -> {
|
||||
if (errorMessageHandler != null)
|
||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||
});
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -161,7 +168,7 @@ public class SquWalletService extends WalletService {
|
|||
InsufficientMoneyException, WalletException, TransactionVerificationException {
|
||||
|
||||
Transaction tx = new Transaction(params);
|
||||
Preconditions.checkArgument(Restrictions.isAboveDust(receiverAmount),
|
||||
checkArgument(Restrictions.isAboveDust(receiverAmount),
|
||||
"The amount is too low (dust limit).");
|
||||
tx.addOutput(receiverAmount, new Address(params, receiverAddress));
|
||||
|
||||
|
@ -203,4 +210,44 @@ public class SquWalletService extends WalletService {
|
|||
return tx;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Burn SQU for proposal fee
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public Transaction getPreparedProposalFeeTx(Coin createProposalFee) throws WalletException, TransactionVerificationException, InsufficientMoneyException, ChangeBelowDustException {
|
||||
Transaction tx = new Transaction(params);
|
||||
|
||||
SquFeeCoinSelector squFeeCoinSelector = new SquFeeCoinSelector(wallet, true);
|
||||
CoinSelection coinSelection = squFeeCoinSelector.getCoinSelection(createProposalFee);
|
||||
coinSelection.gathered.stream().forEach(tx::addInput);
|
||||
Coin change = coinSelection.valueGathered.subtract(createProposalFee);
|
||||
if (change.isPositive())
|
||||
tx.addOutput(change, wallet.freshReceiveAddress());
|
||||
|
||||
printTx("preparedProposalFeeTx", tx);
|
||||
return tx;
|
||||
}
|
||||
|
||||
public void signAndBroadcastProposalFeeTx(Transaction tx, FutureCallback<Transaction> callback) throws WalletException, TransactionVerificationException {
|
||||
// Sign all SQU inputs
|
||||
int endIndex = tx.getInputs().size();
|
||||
for (int i = 0; i < endIndex; i++) {
|
||||
TransactionInput txIn = tx.getInputs().get(i);
|
||||
TransactionOutput connectedOutput = txIn.getConnectedOutput();
|
||||
if (connectedOutput != null && connectedOutput.isMine(wallet)) {
|
||||
signTransactionInput(tx, txIn, i);
|
||||
checkScriptSig(tx, txIn, i);
|
||||
}
|
||||
}
|
||||
|
||||
checkWalletConsistency();
|
||||
verifyTransaction(tx);
|
||||
printTx("signAndBroadcastProposalFeeTx", tx);
|
||||
|
||||
wallet.commitTx(tx);
|
||||
checkWalletConsistency();
|
||||
Futures.addCallback(walletsSetup.getPeerGroup().broadcastTransaction(tx).future(), callback);
|
||||
printTx("signAndBroadcastProposalFeeTx", tx);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -59,13 +59,13 @@ import static com.google.common.base.Preconditions.checkState;
|
|||
public abstract class WalletService {
|
||||
private static final Logger log = LoggerFactory.getLogger(WalletService.class);
|
||||
|
||||
private final CopyOnWriteArraySet<AddressConfidenceListener> addressConfidenceListeners = new CopyOnWriteArraySet<>();
|
||||
private final CopyOnWriteArraySet<TxConfidenceListener> txConfidenceListeners = new CopyOnWriteArraySet<>();
|
||||
private final CopyOnWriteArraySet<BalanceListener> balanceListeners = new CopyOnWriteArraySet<>();
|
||||
protected final CopyOnWriteArraySet<AddressConfidenceListener> addressConfidenceListeners = new CopyOnWriteArraySet<>();
|
||||
protected final CopyOnWriteArraySet<TxConfidenceListener> txConfidenceListeners = new CopyOnWriteArraySet<>();
|
||||
protected final CopyOnWriteArraySet<BalanceListener> balanceListeners = new CopyOnWriteArraySet<>();
|
||||
|
||||
protected final WalletsSetup walletsSetup;
|
||||
private final Preferences preferences;
|
||||
private final FeeService feeService;
|
||||
protected final Preferences preferences;
|
||||
protected final FeeService feeService;
|
||||
|
||||
protected final WalletEventListener walletEventListener = new BitsquareWalletEventListener();
|
||||
protected final NetworkParameters params;
|
||||
|
@ -324,7 +324,7 @@ public abstract class WalletService {
|
|||
return null;
|
||||
}
|
||||
|
||||
private TransactionConfidence getTransactionConfidence(Transaction tx, Address address) {
|
||||
protected TransactionConfidence getTransactionConfidence(Transaction tx, Address address) {
|
||||
List<TransactionOutput> mergedOutputs = getOutputsWithConnectedOutputs(tx);
|
||||
List<TransactionConfidence> transactionConfidenceList = new ArrayList<>();
|
||||
|
||||
|
@ -339,7 +339,7 @@ public abstract class WalletService {
|
|||
}
|
||||
|
||||
|
||||
private List<TransactionOutput> getOutputsWithConnectedOutputs(Transaction tx) {
|
||||
protected List<TransactionOutput> getOutputsWithConnectedOutputs(Transaction tx) {
|
||||
List<TransactionOutput> transactionOutputs = tx.getOutputs();
|
||||
List<TransactionOutput> connectedOutputs = new ArrayList<>();
|
||||
|
||||
|
@ -359,7 +359,7 @@ public abstract class WalletService {
|
|||
}
|
||||
|
||||
|
||||
private TransactionConfidence getMostRecentConfidence(List<TransactionConfidence> transactionConfidenceList) {
|
||||
protected TransactionConfidence getMostRecentConfidence(List<TransactionConfidence> transactionConfidenceList) {
|
||||
TransactionConfidence transactionConfidence = null;
|
||||
for (TransactionConfidence confidence : transactionConfidenceList) {
|
||||
if (confidence != null) {
|
||||
|
@ -390,7 +390,7 @@ public abstract class WalletService {
|
|||
return wallet != null ? getBalance(wallet.calculateAllSpendCandidates(), address) : Coin.ZERO;
|
||||
}
|
||||
|
||||
private Coin getBalance(List<TransactionOutput> transactionOutputs, Address address) {
|
||||
protected Coin getBalance(List<TransactionOutput> transactionOutputs, Address address) {
|
||||
Coin balance = Coin.ZERO;
|
||||
for (TransactionOutput transactionOutput : transactionOutputs) {
|
||||
if (transactionOutput.getScriptPubKey().isSentToAddress() || transactionOutput.getScriptPubKey().isPayToScriptHash()) {
|
||||
|
@ -478,7 +478,7 @@ public abstract class WalletService {
|
|||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("\n").append(tracePrefix).append(":").append("\n").append(tx.toString()).append("\n");
|
||||
sb.append("Size: ").append(tx.bitcoinSerialize().length);
|
||||
log.info(sb.toString());
|
||||
log.error(sb.toString());
|
||||
}
|
||||
|
||||
|
||||
|
|
132
core/src/main/java/io/bitsquare/dao/proposals/Proposal.java
Normal file
132
core/src/main/java/io/bitsquare/dao/proposals/Proposal.java
Normal file
|
@ -0,0 +1,132 @@
|
|||
/*
|
||||
* This file is part of Bitsquare.
|
||||
*
|
||||
* Bitsquare is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.bitsquare.dao.proposals;
|
||||
|
||||
import io.bitsquare.p2p.NodeAddress;
|
||||
import io.bitsquare.p2p.storage.payload.LazyProcessedStoragePayload;
|
||||
import io.bitsquare.p2p.storage.payload.PersistedStoragePayload;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.security.PublicKey;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class Proposal implements LazyProcessedStoragePayload, PersistedStoragePayload {
|
||||
private static final Logger log = LoggerFactory.getLogger(Proposal.class);
|
||||
|
||||
public static final long TTL = TimeUnit.DAYS.toMillis(30);
|
||||
|
||||
public final String uid;
|
||||
public final String text;
|
||||
public final String title;
|
||||
public final String category;
|
||||
public final String description;
|
||||
public final String link;
|
||||
public final Date startDate;
|
||||
public final Date endDate;
|
||||
public final Coin requestedBtc;
|
||||
public final String btcAddress;
|
||||
public final NodeAddress nodeAddress;
|
||||
private PublicKey p2pStorageSignaturePubKey;
|
||||
public final byte[] squPubKey;
|
||||
|
||||
// Signature of proposal data without signature, hash and feeTxId
|
||||
public byte[] signature;
|
||||
// Sha256Hash of proposal data including the signature but without feeTxId and hash
|
||||
public byte[] hash;
|
||||
|
||||
// Set after we signed and set the hash. The hash is used in the OP_RETURN of the fee tx
|
||||
public String feeTxId;
|
||||
|
||||
public Proposal(String uid,
|
||||
String text,
|
||||
String title,
|
||||
String category,
|
||||
String description,
|
||||
String link,
|
||||
Date startDate,
|
||||
Date endDate,
|
||||
Coin requestedBtc,
|
||||
String btcAddress,
|
||||
NodeAddress nodeAddress,
|
||||
PublicKey p2pStorageSignaturePubKey,
|
||||
byte[] squPubKey) {
|
||||
|
||||
this.uid = uid;
|
||||
this.text = text;
|
||||
this.title = title;
|
||||
this.category = category;
|
||||
this.description = description;
|
||||
this.link = link;
|
||||
this.startDate = startDate;
|
||||
this.endDate = endDate;
|
||||
this.requestedBtc = requestedBtc;
|
||||
this.btcAddress = btcAddress;
|
||||
this.nodeAddress = nodeAddress;
|
||||
this.p2pStorageSignaturePubKey = p2pStorageSignaturePubKey;
|
||||
this.squPubKey = squPubKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTTL() {
|
||||
return TTL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PublicKey getOwnerPubKey() {
|
||||
return p2pStorageSignaturePubKey;
|
||||
}
|
||||
|
||||
public void setSignature(byte[] signature) {
|
||||
this.signature = signature;
|
||||
}
|
||||
|
||||
public void setHash(byte[] hash) {
|
||||
this.hash = hash;
|
||||
}
|
||||
|
||||
// Called after sig and hash
|
||||
public void setFeeTxId(String feeTxId) {
|
||||
this.feeTxId = feeTxId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Proposal{" +
|
||||
"uid='" + uid + '\'' +
|
||||
", text='" + text + '\'' +
|
||||
", title='" + title + '\'' +
|
||||
", category='" + category + '\'' +
|
||||
", description='" + description + '\'' +
|
||||
", link='" + link + '\'' +
|
||||
", startDate=" + startDate +
|
||||
", endDate=" + endDate +
|
||||
", requestedBtc=" + requestedBtc +
|
||||
", btcAddress='" + btcAddress + '\'' +
|
||||
", nodeAddress=" + nodeAddress +
|
||||
", pubKey=" + Arrays.toString(squPubKey) +
|
||||
", signature=" + signature +
|
||||
", hash=" + hash +
|
||||
", feeTxId=" + feeTxId +
|
||||
'}';
|
||||
}
|
||||
|
||||
}
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
package io.bitsquare.gui.main.dao;
|
||||
|
||||
import io.bitsquare.btc.wallet.SquWalletService;
|
||||
import io.bitsquare.gui.Navigation;
|
||||
import io.bitsquare.gui.common.model.Activatable;
|
||||
import io.bitsquare.gui.common.view.*;
|
||||
|
@ -45,15 +46,17 @@ public class DaoView extends ActivatableViewAndModel<TabPane, Activatable> {
|
|||
private final ViewLoader viewLoader;
|
||||
private final Navigation navigation;
|
||||
private Preferences preferences;
|
||||
private SquWalletService squWalletService;
|
||||
private Tab selectedTab;
|
||||
private TokenWalletView tokenWalletView;
|
||||
|
||||
|
||||
@Inject
|
||||
private DaoView(CachingViewLoader viewLoader, Navigation navigation, Preferences preferences) {
|
||||
private DaoView(CachingViewLoader viewLoader, Navigation navigation, Preferences preferences, SquWalletService squWalletService) {
|
||||
this.viewLoader = viewLoader;
|
||||
this.navigation = navigation;
|
||||
this.preferences = preferences;
|
||||
this.squWalletService = squWalletService;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -87,6 +90,8 @@ public class DaoView extends ActivatableViewAndModel<TabPane, Activatable> {
|
|||
navigation.addListener(navigationListener);
|
||||
root.getSelectionModel().selectedItemProperty().addListener(tabChangeListener);
|
||||
|
||||
squWalletService.requestSquUtxo(null, null);
|
||||
|
||||
if (navigation.getCurrentPath().size() == 2 && navigation.getCurrentPath().get(1) == DaoView.class) {
|
||||
Tab selectedItem = root.getSelectionModel().getSelectedItem();
|
||||
if (selectedItem == tokenWalletTab)
|
||||
|
|
|
@ -18,21 +18,14 @@
|
|||
-->
|
||||
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<GridPane fx:id="root" fx:controller="io.bitsquare.gui.main.dao.proposals.ProposalsView"
|
||||
hgap="5.0" vgap="5.0"
|
||||
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
|
||||
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"
|
||||
xmlns:fx="http://javafx.com/fxml">
|
||||
<padding>
|
||||
<Insets bottom="10.0" left="25.0" top="30.0" right="25"/>
|
||||
</padding>
|
||||
<AnchorPane fx:id="root" fx:controller="io.bitsquare.gui.main.dao.proposals.ProposalsView"
|
||||
prefHeight="660.0" prefWidth="1000.0"
|
||||
xmlns:fx="http://javafx.com/fxml">
|
||||
|
||||
<VBox fx:id="leftVBox" spacing="5" prefWidth="240" AnchorPane.bottomAnchor="20" AnchorPane.leftAnchor="15"
|
||||
AnchorPane.topAnchor="20"/>
|
||||
<AnchorPane fx:id="content" AnchorPane.bottomAnchor="10" AnchorPane.rightAnchor="25" AnchorPane.leftAnchor="290"
|
||||
AnchorPane.topAnchor="30"/>
|
||||
</AnchorPane>
|
||||
|
||||
<columnConstraints>
|
||||
<ColumnConstraints hgrow="SOMETIMES" halignment="RIGHT" minWidth="140.0"/>
|
||||
<ColumnConstraints hgrow="ALWAYS" minWidth="300.0"/>
|
||||
<ColumnConstraints hgrow="SOMETIMES" halignment="RIGHT" minWidth="140.0"/>
|
||||
<ColumnConstraints hgrow="ALWAYS" minWidth="300.0"/>
|
||||
</columnConstraints>
|
||||
</GridPane>
|
|
@ -17,44 +17,171 @@
|
|||
|
||||
package io.bitsquare.gui.main.dao.proposals;
|
||||
|
||||
import io.bitsquare.gui.common.model.Activatable;
|
||||
import io.bitsquare.gui.common.view.ActivatableView;
|
||||
import io.bitsquare.gui.common.view.FxmlView;
|
||||
import io.bitsquare.gui.util.BSFormatter;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import de.jensd.fx.fontawesome.AwesomeDude;
|
||||
import de.jensd.fx.fontawesome.AwesomeIcon;
|
||||
import io.bitsquare.gui.Navigation;
|
||||
import io.bitsquare.gui.common.view.*;
|
||||
import io.bitsquare.gui.main.MainView;
|
||||
import io.bitsquare.gui.main.dao.DaoView;
|
||||
import io.bitsquare.gui.main.dao.proposals.active.ActiveProposalsView;
|
||||
import io.bitsquare.gui.main.dao.proposals.create.CreateProposalView;
|
||||
import io.bitsquare.gui.main.dao.proposals.past.PastProposalsView;
|
||||
import io.bitsquare.gui.util.Colors;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ToggleButton;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import javafx.scene.layout.AnchorPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.paint.Paint;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@FxmlView
|
||||
public class ProposalsView extends ActivatableView<GridPane, Activatable> {
|
||||
public class ProposalsView extends ActivatableViewAndModel {
|
||||
|
||||
private final ViewLoader viewLoader;
|
||||
private final Navigation navigation;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor, initialisation
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
private MenuItem create, active, past;
|
||||
private Navigation.Listener listener;
|
||||
|
||||
@FXML
|
||||
private VBox leftVBox;
|
||||
@FXML
|
||||
private AnchorPane content;
|
||||
|
||||
private Class<? extends View> selectedViewClass;
|
||||
|
||||
@Inject
|
||||
public ProposalsView(BSFormatter formatter) {
|
||||
super();
|
||||
private ProposalsView(CachingViewLoader viewLoader, Navigation navigation) {
|
||||
this.viewLoader = viewLoader;
|
||||
this.navigation = navigation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
}
|
||||
listener = viewPath -> {
|
||||
if (viewPath.size() != 4 || viewPath.indexOf(ProposalsView.class) != 2)
|
||||
return;
|
||||
|
||||
selectedViewClass = viewPath.tip();
|
||||
loadView(selectedViewClass);
|
||||
};
|
||||
|
||||
ToggleGroup toggleGroup = new ToggleGroup();
|
||||
create = new MenuItem(navigation, toggleGroup, "Create Proposal", CreateProposalView.class, AwesomeIcon.EDIT);
|
||||
active = new MenuItem(navigation, toggleGroup, "Active proposals", ActiveProposalsView.class, AwesomeIcon.ARROW_RIGHT);
|
||||
past = new MenuItem(navigation, toggleGroup, "Past proposals", PastProposalsView.class, AwesomeIcon.LIST);
|
||||
leftVBox.getChildren().addAll(create, active, past);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void activate() {
|
||||
create.activate();
|
||||
active.activate();
|
||||
past.activate();
|
||||
|
||||
navigation.addListener(listener);
|
||||
ViewPath viewPath = navigation.getCurrentPath();
|
||||
if (viewPath.size() == 3 && viewPath.indexOf(ProposalsView.class) == 2 ||
|
||||
viewPath.size() == 2 && viewPath.indexOf(DaoView.class) == 1) {
|
||||
if (selectedViewClass == null)
|
||||
selectedViewClass = CreateProposalView.class;
|
||||
|
||||
loadView(selectedViewClass);
|
||||
|
||||
} else if (viewPath.size() == 4 && viewPath.indexOf(ProposalsView.class) == 2) {
|
||||
selectedViewClass = viewPath.get(3);
|
||||
loadView(selectedViewClass);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deactivate() {
|
||||
navigation.removeListener(listener);
|
||||
|
||||
create.deactivate();
|
||||
active.deactivate();
|
||||
past.deactivate();
|
||||
}
|
||||
|
||||
private void loadView(Class<? extends View> viewClass) {
|
||||
View view = viewLoader.load(viewClass);
|
||||
content.getChildren().setAll(view.getRoot());
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Initialize
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
if (view instanceof CreateProposalView) create.setSelected(true);
|
||||
else if (view instanceof ActiveProposalsView) active.setSelected(true);
|
||||
else if (view instanceof PastProposalsView) past.setSelected(true);
|
||||
}
|
||||
|
||||
public Class<? extends View> getSelectedViewClass() {
|
||||
return selectedViewClass;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class MenuItem extends ToggleButton {
|
||||
|
||||
private final ChangeListener<Boolean> selectedPropertyChangeListener;
|
||||
private final ChangeListener<Boolean> disablePropertyChangeListener;
|
||||
private final Navigation navigation;
|
||||
private final Class<? extends View> viewClass;
|
||||
|
||||
MenuItem(Navigation navigation, ToggleGroup toggleGroup, String title, Class<? extends View> viewClass, AwesomeIcon awesomeIcon) {
|
||||
this.navigation = navigation;
|
||||
this.viewClass = viewClass;
|
||||
|
||||
setToggleGroup(toggleGroup);
|
||||
setText(title);
|
||||
setId("account-settings-item-background-active");
|
||||
setPrefHeight(40);
|
||||
setPrefWidth(240);
|
||||
setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
Label icon = new Label();
|
||||
AwesomeDude.setIcon(icon, awesomeIcon);
|
||||
icon.setTextFill(Paint.valueOf("#333"));
|
||||
icon.setPadding(new Insets(0, 5, 0, 0));
|
||||
icon.setAlignment(Pos.CENTER);
|
||||
icon.setMinWidth(25);
|
||||
icon.setMaxWidth(25);
|
||||
setGraphic(icon);
|
||||
|
||||
selectedPropertyChangeListener = (ov, oldValue, newValue) -> {
|
||||
if (newValue) {
|
||||
setId("account-settings-item-background-selected");
|
||||
icon.setTextFill(Colors.BLUE);
|
||||
} else {
|
||||
setId("account-settings-item-background-active");
|
||||
icon.setTextFill(Paint.valueOf("#333"));
|
||||
}
|
||||
};
|
||||
|
||||
disablePropertyChangeListener = (ov, oldValue, newValue) -> {
|
||||
if (newValue) {
|
||||
setId("account-settings-item-background-disabled");
|
||||
icon.setTextFill(Paint.valueOf("#ccc"));
|
||||
} else {
|
||||
setId("account-settings-item-background-active");
|
||||
icon.setTextFill(Paint.valueOf("#333"));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void activate() {
|
||||
setOnAction((event) -> navigation.navigateTo(MainView.class, DaoView.class, ProposalsView.class, viewClass));
|
||||
selectedProperty().addListener(selectedPropertyChangeListener);
|
||||
disableProperty().addListener(disablePropertyChangeListener);
|
||||
}
|
||||
|
||||
public void deactivate() {
|
||||
setOnAction(null);
|
||||
selectedProperty().removeListener(selectedPropertyChangeListener);
|
||||
disableProperty().removeListener(disablePropertyChangeListener);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!--
|
||||
~ This file is part of Bitsquare.
|
||||
~
|
||||
~ Bitsquare is free software: you can redistribute it and/or modify it
|
||||
~ under the terms of the GNU Affero General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or (at
|
||||
~ your option) any later version.
|
||||
~
|
||||
~ Bitsquare is distributed in the hope that it will be useful, but WITHOUT
|
||||
~ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
~ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
~ License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU Affero General Public License
|
||||
~ along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
|
||||
<?import javafx.scene.layout.*?>
|
||||
<GridPane fx:id="root" fx:controller="io.bitsquare.gui.main.dao.proposals.active.ActiveProposalsView"
|
||||
hgap="5.0" vgap="5.0"
|
||||
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
|
||||
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="-10.0"
|
||||
xmlns:fx="http://javafx.com/fxml">
|
||||
<columnConstraints>
|
||||
<ColumnConstraints hgrow="SOMETIMES" halignment="RIGHT" minWidth="160.0"/>
|
||||
<ColumnConstraints hgrow="ALWAYS" minWidth="300.0"/>
|
||||
</columnConstraints>
|
||||
</GridPane>
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* This file is part of Bitsquare.
|
||||
*
|
||||
* Bitsquare is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.bitsquare.gui.main.dao.proposals.active;
|
||||
|
||||
import io.bitsquare.gui.common.view.ActivatableView;
|
||||
import io.bitsquare.gui.common.view.FxmlView;
|
||||
import javafx.scene.layout.GridPane;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@FxmlView
|
||||
public class ActiveProposalsView extends ActivatableView<GridPane, Void> {
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor, lifecycle
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Inject
|
||||
private ActiveProposalsView() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void activate() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deactivate() {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!--
|
||||
~ This file is part of Bitsquare.
|
||||
~
|
||||
~ Bitsquare is free software: you can redistribute it and/or modify it
|
||||
~ under the terms of the GNU Affero General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or (at
|
||||
~ your option) any later version.
|
||||
~
|
||||
~ Bitsquare is distributed in the hope that it will be useful, but WITHOUT
|
||||
~ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
~ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
~ License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU Affero General Public License
|
||||
~ along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<?import javafx.scene.layout.*?>
|
||||
<GridPane fx:id="root" fx:controller="io.bitsquare.gui.main.dao.proposals.create.CreateProposalView"
|
||||
hgap="5.0" vgap="5.0"
|
||||
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
|
||||
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="-10.0"
|
||||
xmlns:fx="http://javafx.com/fxml">
|
||||
|
||||
<columnConstraints>
|
||||
<ColumnConstraints hgrow="SOMETIMES" halignment="RIGHT" minWidth="140.0"/>
|
||||
<ColumnConstraints hgrow="ALWAYS" minWidth="300.0"/>
|
||||
</columnConstraints>
|
||||
|
||||
</GridPane>
|
|
@ -0,0 +1,195 @@
|
|||
/*
|
||||
* This file is part of Bitsquare.
|
||||
*
|
||||
* Bitsquare is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.bitsquare.gui.main.dao.proposals.create;
|
||||
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import io.bitsquare.btc.InsufficientFundsException;
|
||||
import io.bitsquare.btc.exceptions.TransactionVerificationException;
|
||||
import io.bitsquare.btc.exceptions.WalletException;
|
||||
import io.bitsquare.btc.provider.fee.FeeService;
|
||||
import io.bitsquare.btc.wallet.BtcWalletService;
|
||||
import io.bitsquare.btc.wallet.ChangeBelowDustException;
|
||||
import io.bitsquare.btc.wallet.SquWalletService;
|
||||
import io.bitsquare.common.ByteArrayUtils;
|
||||
import io.bitsquare.common.crypto.KeyRing;
|
||||
import io.bitsquare.dao.proposals.Proposal;
|
||||
import io.bitsquare.gui.common.view.ActivatableView;
|
||||
import io.bitsquare.gui.common.view.FxmlView;
|
||||
import io.bitsquare.gui.components.InputTextField;
|
||||
import io.bitsquare.gui.main.overlays.popups.Popup;
|
||||
import io.bitsquare.gui.util.BSFormatter;
|
||||
import io.bitsquare.gui.util.Layout;
|
||||
import io.bitsquare.p2p.NodeAddress;
|
||||
import io.bitsquare.p2p.P2PService;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.core.InsufficientMoneyException;
|
||||
import org.bitcoinj.core.Sha256Hash;
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.bitcoinj.crypto.DeterministicKey;
|
||||
import org.bitcoinj.wallet.KeyChain;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
import java.security.PublicKey;
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static io.bitsquare.gui.util.FormBuilder.*;
|
||||
|
||||
@FxmlView
|
||||
public class CreateProposalView extends ActivatableView<GridPane, Void> {
|
||||
|
||||
private final NodeAddress nodeAddress;
|
||||
private final PublicKey p2pStorageSignaturePubKey;
|
||||
private InputTextField nameTextField, titleTextField, categoryTextField, descriptionTextField, linkTextField,
|
||||
startDateTextField, endDateTextField, requestedBTCTextField, btcAddressTextField;
|
||||
private Button createButton;
|
||||
|
||||
private final SquWalletService squWalletService;
|
||||
private final BtcWalletService btcWalletService;
|
||||
private FeeService feeService;
|
||||
private final P2PService p2PService;
|
||||
private final BSFormatter btcFormatter;
|
||||
private int gridRow = 0;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor, lifecycle
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Inject
|
||||
private CreateProposalView(SquWalletService squWalletService, BtcWalletService btcWalletService, FeeService feeService, P2PService p2PService, KeyRing keyRing, BSFormatter btcFormatter) {
|
||||
this.squWalletService = squWalletService;
|
||||
this.btcWalletService = btcWalletService;
|
||||
this.feeService = feeService;
|
||||
this.p2PService = p2PService;
|
||||
this.btcFormatter = btcFormatter;
|
||||
|
||||
nodeAddress = p2PService.getAddress();
|
||||
p2pStorageSignaturePubKey = keyRing.getPubKeyRing().getSignaturePubKey();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
addTitledGroupBg(root, gridRow, 9, "Create new funding proposal");
|
||||
nameTextField = addLabelInputTextField(root, gridRow, "Name/nickname:", Layout.FIRST_ROW_DISTANCE).second;
|
||||
titleTextField = addLabelInputTextField(root, ++gridRow, "Title:").second;
|
||||
categoryTextField = addLabelInputTextField(root, ++gridRow, "Category:").second;
|
||||
descriptionTextField = addLabelInputTextField(root, ++gridRow, "Description:").second;
|
||||
linkTextField = addLabelInputTextField(root, ++gridRow, "Link to detail info:").second;
|
||||
startDateTextField = addLabelInputTextField(root, ++gridRow, "Start date:").second;
|
||||
endDateTextField = addLabelInputTextField(root, ++gridRow, "Delivery date:").second;
|
||||
requestedBTCTextField = addLabelInputTextField(root, ++gridRow, "Requested funds in BTC:").second;
|
||||
btcAddressTextField = addLabelInputTextField(root, ++gridRow, "Bitcoin address:").second;
|
||||
createButton = addButtonAfterGroup(root, ++gridRow, "Create proposal");
|
||||
|
||||
//TODO
|
||||
nameTextField.setText("Mock name");
|
||||
titleTextField.setText("Mock Title");
|
||||
categoryTextField.setText("Mock Category");
|
||||
descriptionTextField.setText("Mock Description");
|
||||
linkTextField.setText("Mock Link");
|
||||
startDateTextField.setText("Mock Start date");
|
||||
endDateTextField.setText("Mock Delivery date");
|
||||
requestedBTCTextField.setText("Mock Requested funds");
|
||||
btcAddressTextField.setText("Mock Bitcoin address");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void activate() {
|
||||
createButton.setOnAction(event -> {
|
||||
DeterministicKey squKeyPair = squWalletService.getWallet().freshKey(KeyChain.KeyPurpose.AUTHENTICATION);
|
||||
checkNotNull(squKeyPair, "squKeyPair must not be null");
|
||||
|
||||
//TODO
|
||||
Date startDate = new Date();
|
||||
Date endDate = new Date();
|
||||
|
||||
Proposal proposal = new Proposal(UUID.randomUUID().toString(),
|
||||
nameTextField.getText(),
|
||||
titleTextField.getText(),
|
||||
categoryTextField.getText(),
|
||||
descriptionTextField.getText(),
|
||||
linkTextField.getText(),
|
||||
startDate,
|
||||
endDate,
|
||||
btcFormatter.parseToCoin(requestedBTCTextField.getText()),
|
||||
btcAddressTextField.getText(),
|
||||
nodeAddress,
|
||||
p2pStorageSignaturePubKey,
|
||||
squKeyPair.getPubKey()
|
||||
);
|
||||
Sha256Hash hash = Sha256Hash.of(ByteArrayUtils.objectToByteArray(proposal));
|
||||
proposal.setSignature(squKeyPair.sign(hash).encodeToDER());
|
||||
hash = Sha256Hash.of(ByteArrayUtils.objectToByteArray(proposal));
|
||||
proposal.setHash(hash.getBytes());
|
||||
|
||||
try {
|
||||
Coin createProposalFee = feeService.getCreateProposalFee();
|
||||
Transaction preparedSendTx = squWalletService.getPreparedProposalFeeTx(createProposalFee);
|
||||
Transaction txWithBtcFee = btcWalletService.addInputAndOutputToPreparedSquProposalFeeTx(preparedSendTx, hash.getBytes());
|
||||
squWalletService.signAndBroadcastProposalFeeTx(txWithBtcFee, new FutureCallback<Transaction>() {
|
||||
@Override
|
||||
public void onSuccess(@Nullable Transaction result) {
|
||||
checkNotNull(result, "Transaction must not be null at signAndBroadcastProposalFeeTx callback.");
|
||||
publishToP2PNetwork(proposal);
|
||||
clearForm();
|
||||
new Popup<>().confirmation("Your proposal has been successfully published.").show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NotNull Throwable t) {
|
||||
log.error(t.toString());
|
||||
new Popup<>().warning(t.toString()).show();
|
||||
}
|
||||
});
|
||||
} catch (InsufficientFundsException |
|
||||
TransactionVerificationException | WalletException | InsufficientMoneyException | ChangeBelowDustException e) {
|
||||
log.error(e.toString());
|
||||
e.printStackTrace();
|
||||
new Popup<>().warning(e.toString()).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void clearForm() {
|
||||
nameTextField.setText("");
|
||||
titleTextField.setText("");
|
||||
categoryTextField.setText("");
|
||||
descriptionTextField.setText("");
|
||||
linkTextField.setText("");
|
||||
startDateTextField.setText("");
|
||||
endDateTextField.setText("");
|
||||
requestedBTCTextField.setText("");
|
||||
btcAddressTextField.setText("");
|
||||
}
|
||||
|
||||
private void publishToP2PNetwork(Proposal proposal) {
|
||||
p2PService.addData(proposal, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deactivate() {
|
||||
createButton.setOnAction(null);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!--
|
||||
~ This file is part of Bitsquare.
|
||||
~
|
||||
~ Bitsquare is free software: you can redistribute it and/or modify it
|
||||
~ under the terms of the GNU Affero General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or (at
|
||||
~ your option) any later version.
|
||||
~
|
||||
~ Bitsquare is distributed in the hope that it will be useful, but WITHOUT
|
||||
~ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
~ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
~ License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU Affero General Public License
|
||||
~ along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
|
||||
<?import javafx.scene.layout.*?>
|
||||
<GridPane fx:id="root" fx:controller="io.bitsquare.gui.main.dao.proposals.past.PastProposalsView"
|
||||
hgap="5.0" vgap="5.0"
|
||||
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
|
||||
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="-10.0"
|
||||
xmlns:fx="http://javafx.com/fxml">
|
||||
<columnConstraints>
|
||||
<ColumnConstraints hgrow="SOMETIMES" halignment="RIGHT" minWidth="160.0"/>
|
||||
<ColumnConstraints hgrow="ALWAYS" minWidth="300.0"/>
|
||||
</columnConstraints>
|
||||
</GridPane>
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* This file is part of Bitsquare.
|
||||
*
|
||||
* Bitsquare is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.bitsquare.gui.main.dao.proposals.past;
|
||||
|
||||
import io.bitsquare.gui.common.view.ActivatableView;
|
||||
import io.bitsquare.gui.common.view.FxmlView;
|
||||
import javafx.scene.layout.GridPane;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@FxmlView
|
||||
public class PastProposalsView extends ActivatableView<GridPane, Void> {
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor, lifecycle
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Inject
|
||||
private PastProposalsView() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void activate() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deactivate() {
|
||||
}
|
||||
}
|
||||
|
|
@ -18,21 +18,14 @@
|
|||
-->
|
||||
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<GridPane fx:id="root" fx:controller="io.bitsquare.gui.main.dao.voting.VotingView"
|
||||
hgap="5.0" vgap="5.0"
|
||||
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
|
||||
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"
|
||||
xmlns:fx="http://javafx.com/fxml">
|
||||
<padding>
|
||||
<Insets bottom="10.0" left="25.0" top="30.0" right="25"/>
|
||||
</padding>
|
||||
<AnchorPane fx:id="root" fx:controller="io.bitsquare.gui.main.dao.voting.VotingView"
|
||||
prefHeight="660.0" prefWidth="1000.0"
|
||||
xmlns:fx="http://javafx.com/fxml">
|
||||
|
||||
<VBox fx:id="leftVBox" spacing="5" prefWidth="240" AnchorPane.bottomAnchor="20" AnchorPane.leftAnchor="15"
|
||||
AnchorPane.topAnchor="20"/>
|
||||
<AnchorPane fx:id="content" AnchorPane.bottomAnchor="10" AnchorPane.rightAnchor="25" AnchorPane.leftAnchor="290"
|
||||
AnchorPane.topAnchor="30"/>
|
||||
</AnchorPane>
|
||||
|
||||
<columnConstraints>
|
||||
<ColumnConstraints hgrow="SOMETIMES" halignment="RIGHT" minWidth="140.0"/>
|
||||
<ColumnConstraints hgrow="ALWAYS" minWidth="300.0"/>
|
||||
<ColumnConstraints hgrow="SOMETIMES" halignment="RIGHT" minWidth="140.0"/>
|
||||
<ColumnConstraints hgrow="ALWAYS" minWidth="300.0"/>
|
||||
</columnConstraints>
|
||||
</GridPane>
|
|
@ -17,44 +17,171 @@
|
|||
|
||||
package io.bitsquare.gui.main.dao.voting;
|
||||
|
||||
import io.bitsquare.gui.common.model.Activatable;
|
||||
import io.bitsquare.gui.common.view.ActivatableView;
|
||||
import io.bitsquare.gui.common.view.FxmlView;
|
||||
import io.bitsquare.gui.util.BSFormatter;
|
||||
import javafx.scene.layout.GridPane;
|
||||
import de.jensd.fx.fontawesome.AwesomeDude;
|
||||
import de.jensd.fx.fontawesome.AwesomeIcon;
|
||||
import io.bitsquare.gui.Navigation;
|
||||
import io.bitsquare.gui.common.view.*;
|
||||
import io.bitsquare.gui.main.MainView;
|
||||
import io.bitsquare.gui.main.dao.DaoView;
|
||||
import io.bitsquare.gui.main.dao.voting.dashboard.VotingDashboardView;
|
||||
import io.bitsquare.gui.main.dao.voting.history.VotingHistoryView;
|
||||
import io.bitsquare.gui.main.dao.voting.vote.VoteView;
|
||||
import io.bitsquare.gui.util.Colors;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ToggleButton;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import javafx.scene.layout.AnchorPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.paint.Paint;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@FxmlView
|
||||
public class VotingView extends ActivatableView<GridPane, Activatable> {
|
||||
public class VotingView extends ActivatableViewAndModel {
|
||||
|
||||
private final ViewLoader viewLoader;
|
||||
private final Navigation navigation;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor, initialisation
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
private MenuItem dashboard, vote, history;
|
||||
private Navigation.Listener listener;
|
||||
|
||||
@FXML
|
||||
private VBox leftVBox;
|
||||
@FXML
|
||||
private AnchorPane content;
|
||||
|
||||
private Class<? extends View> selectedViewClass;
|
||||
|
||||
@Inject
|
||||
public VotingView(BSFormatter formatter) {
|
||||
super();
|
||||
private VotingView(CachingViewLoader viewLoader, Navigation navigation) {
|
||||
this.viewLoader = viewLoader;
|
||||
this.navigation = navigation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
}
|
||||
listener = viewPath -> {
|
||||
if (viewPath.size() != 4 || viewPath.indexOf(VotingView.class) != 2)
|
||||
return;
|
||||
|
||||
selectedViewClass = viewPath.tip();
|
||||
loadView(selectedViewClass);
|
||||
};
|
||||
|
||||
ToggleGroup toggleGroup = new ToggleGroup();
|
||||
dashboard = new MenuItem(navigation, toggleGroup, "Dashboard", VotingDashboardView.class, AwesomeIcon.DASHBOARD);
|
||||
vote = new MenuItem(navigation, toggleGroup, "Vote", VoteView.class, AwesomeIcon.EDIT);
|
||||
history = new MenuItem(navigation, toggleGroup, "History", VotingHistoryView.class, AwesomeIcon.TABLE);
|
||||
leftVBox.getChildren().addAll(dashboard, vote, history);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void activate() {
|
||||
dashboard.activate();
|
||||
vote.activate();
|
||||
history.activate();
|
||||
|
||||
navigation.addListener(listener);
|
||||
ViewPath viewPath = navigation.getCurrentPath();
|
||||
if (viewPath.size() == 3 && viewPath.indexOf(VotingView.class) == 2 ||
|
||||
viewPath.size() == 2 && viewPath.indexOf(DaoView.class) == 1) {
|
||||
if (selectedViewClass == null)
|
||||
selectedViewClass = VotingDashboardView.class;
|
||||
|
||||
loadView(selectedViewClass);
|
||||
|
||||
} else if (viewPath.size() == 4 && viewPath.indexOf(VotingView.class) == 2) {
|
||||
selectedViewClass = viewPath.get(3);
|
||||
loadView(selectedViewClass);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deactivate() {
|
||||
navigation.removeListener(listener);
|
||||
|
||||
dashboard.deactivate();
|
||||
vote.deactivate();
|
||||
history.deactivate();
|
||||
}
|
||||
|
||||
private void loadView(Class<? extends View> viewClass) {
|
||||
View view = viewLoader.load(viewClass);
|
||||
content.getChildren().setAll(view.getRoot());
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Initialize
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
if (view instanceof VotingDashboardView) dashboard.setSelected(true);
|
||||
else if (view instanceof VoteView) vote.setSelected(true);
|
||||
else if (view instanceof VotingHistoryView) history.setSelected(true);
|
||||
}
|
||||
|
||||
public Class<? extends View> getSelectedViewClass() {
|
||||
return selectedViewClass;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class MenuItem extends ToggleButton {
|
||||
|
||||
private final ChangeListener<Boolean> selectedPropertyChangeListener;
|
||||
private final ChangeListener<Boolean> disablePropertyChangeListener;
|
||||
private final Navigation navigation;
|
||||
private final Class<? extends View> viewClass;
|
||||
|
||||
MenuItem(Navigation navigation, ToggleGroup toggleGroup, String title, Class<? extends View> viewClass, AwesomeIcon awesomeIcon) {
|
||||
this.navigation = navigation;
|
||||
this.viewClass = viewClass;
|
||||
|
||||
setToggleGroup(toggleGroup);
|
||||
setText(title);
|
||||
setId("account-settings-item-background-active");
|
||||
setPrefHeight(40);
|
||||
setPrefWidth(240);
|
||||
setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
Label icon = new Label();
|
||||
AwesomeDude.setIcon(icon, awesomeIcon);
|
||||
icon.setTextFill(Paint.valueOf("#333"));
|
||||
icon.setPadding(new Insets(0, 5, 0, 0));
|
||||
icon.setAlignment(Pos.CENTER);
|
||||
icon.setMinWidth(25);
|
||||
icon.setMaxWidth(25);
|
||||
setGraphic(icon);
|
||||
|
||||
selectedPropertyChangeListener = (ov, oldValue, newValue) -> {
|
||||
if (newValue) {
|
||||
setId("account-settings-item-background-selected");
|
||||
icon.setTextFill(Colors.BLUE);
|
||||
} else {
|
||||
setId("account-settings-item-background-active");
|
||||
icon.setTextFill(Paint.valueOf("#333"));
|
||||
}
|
||||
};
|
||||
|
||||
disablePropertyChangeListener = (ov, oldValue, newValue) -> {
|
||||
if (newValue) {
|
||||
setId("account-settings-item-background-disabled");
|
||||
icon.setTextFill(Paint.valueOf("#ccc"));
|
||||
} else {
|
||||
setId("account-settings-item-background-active");
|
||||
icon.setTextFill(Paint.valueOf("#333"));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void activate() {
|
||||
setOnAction((event) -> navigation.navigateTo(MainView.class, DaoView.class, VotingView.class, viewClass));
|
||||
selectedProperty().addListener(selectedPropertyChangeListener);
|
||||
disableProperty().addListener(disablePropertyChangeListener);
|
||||
}
|
||||
|
||||
public void deactivate() {
|
||||
setOnAction(null);
|
||||
selectedProperty().removeListener(selectedPropertyChangeListener);
|
||||
disableProperty().removeListener(disablePropertyChangeListener);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!--
|
||||
~ This file is part of Bitsquare.
|
||||
~
|
||||
~ Bitsquare is free software: you can redistribute it and/or modify it
|
||||
~ under the terms of the GNU Affero General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or (at
|
||||
~ your option) any later version.
|
||||
~
|
||||
~ Bitsquare is distributed in the hope that it will be useful, but WITHOUT
|
||||
~ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
~ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
~ License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU Affero General Public License
|
||||
~ along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<?import javafx.scene.layout.*?>
|
||||
<GridPane fx:id="root" fx:controller="io.bitsquare.gui.main.dao.voting.dashboard.VotingDashboardView"
|
||||
hgap="5.0" vgap="5.0"
|
||||
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
|
||||
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="-10.0"
|
||||
xmlns:fx="http://javafx.com/fxml">
|
||||
|
||||
<columnConstraints>
|
||||
<ColumnConstraints hgrow="SOMETIMES" halignment="RIGHT" minWidth="140.0"/>
|
||||
<ColumnConstraints hgrow="ALWAYS" minWidth="300.0"/>
|
||||
</columnConstraints>
|
||||
|
||||
</GridPane>
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* This file is part of Bitsquare.
|
||||
*
|
||||
* Bitsquare is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.bitsquare.gui.main.dao.voting.dashboard;
|
||||
|
||||
import io.bitsquare.gui.common.view.ActivatableView;
|
||||
import io.bitsquare.gui.common.view.FxmlView;
|
||||
import javafx.scene.layout.GridPane;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@FxmlView
|
||||
public class VotingDashboardView extends ActivatableView<GridPane, Void> {
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor, lifecycle
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Inject
|
||||
private VotingDashboardView() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void activate() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deactivate() {
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!--
|
||||
~ This file is part of Bitsquare.
|
||||
~
|
||||
~ Bitsquare is free software: you can redistribute it and/or modify it
|
||||
~ under the terms of the GNU Affero General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or (at
|
||||
~ your option) any later version.
|
||||
~
|
||||
~ Bitsquare is distributed in the hope that it will be useful, but WITHOUT
|
||||
~ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
~ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
~ License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU Affero General Public License
|
||||
~ along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
|
||||
<?import javafx.scene.layout.*?>
|
||||
<GridPane fx:id="root" fx:controller="io.bitsquare.gui.main.dao.voting.history.VotingHistoryView"
|
||||
hgap="5.0" vgap="5.0"
|
||||
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
|
||||
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="-10.0"
|
||||
xmlns:fx="http://javafx.com/fxml">
|
||||
<columnConstraints>
|
||||
<ColumnConstraints hgrow="SOMETIMES" halignment="RIGHT" minWidth="160.0"/>
|
||||
<ColumnConstraints hgrow="ALWAYS" minWidth="300.0"/>
|
||||
</columnConstraints>
|
||||
</GridPane>
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* This file is part of Bitsquare.
|
||||
*
|
||||
* Bitsquare is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.bitsquare.gui.main.dao.voting.history;
|
||||
|
||||
import io.bitsquare.gui.common.view.ActivatableView;
|
||||
import io.bitsquare.gui.common.view.FxmlView;
|
||||
import javafx.scene.layout.GridPane;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@FxmlView
|
||||
public class VotingHistoryView extends ActivatableView<GridPane, Void> {
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor, lifecycle
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Inject
|
||||
private VotingHistoryView() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void activate() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deactivate() {
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!--
|
||||
~ This file is part of Bitsquare.
|
||||
~
|
||||
~ Bitsquare is free software: you can redistribute it and/or modify it
|
||||
~ under the terms of the GNU Affero General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or (at
|
||||
~ your option) any later version.
|
||||
~
|
||||
~ Bitsquare is distributed in the hope that it will be useful, but WITHOUT
|
||||
~ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
~ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
~ License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU Affero General Public License
|
||||
~ along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
|
||||
<?import javafx.scene.layout.*?>
|
||||
<GridPane fx:id="root" fx:controller="io.bitsquare.gui.main.dao.voting.vote.VoteView"
|
||||
hgap="5.0" vgap="5.0"
|
||||
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
|
||||
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="-10.0"
|
||||
xmlns:fx="http://javafx.com/fxml">
|
||||
<columnConstraints>
|
||||
<ColumnConstraints hgrow="SOMETIMES" halignment="RIGHT" minWidth="160.0"/>
|
||||
<ColumnConstraints hgrow="ALWAYS" minWidth="300.0"/>
|
||||
</columnConstraints>
|
||||
</GridPane>
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* This file is part of Bitsquare.
|
||||
*
|
||||
* Bitsquare is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.bitsquare.gui.main.dao.voting.vote;
|
||||
|
||||
import io.bitsquare.gui.common.view.ActivatableView;
|
||||
import io.bitsquare.gui.common.view.FxmlView;
|
||||
import javafx.scene.layout.GridPane;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@FxmlView
|
||||
public class VoteView extends ActivatableView<GridPane, Void> {
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor, lifecycle
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Inject
|
||||
private VoteView() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void activate() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deactivate() {
|
||||
}
|
||||
}
|
||||
|
|
@ -93,7 +93,7 @@ public class TokenWalletView extends ActivatableViewAndModel {
|
|||
if (viewPath.size() == 3 && viewPath.indexOf(TokenWalletView.class) == 2 ||
|
||||
viewPath.size() == 2 && viewPath.indexOf(DaoView.class) == 1) {
|
||||
if (selectedViewClass == null)
|
||||
selectedViewClass = TokenSendView.class;
|
||||
selectedViewClass = TokenDashboardView.class;
|
||||
|
||||
loadView(selectedViewClass);
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue