Add Notification and Instructions popups. Cleanup MainViewModel

This commit is contained in:
Manfred Karrer 2016-02-14 16:45:22 +01:00
parent cdccd57968
commit 6bf2adae7f
23 changed files with 637 additions and 514 deletions

View file

@ -48,6 +48,7 @@ public class Dispute implements Serializable {
///////////////////////////////////////////////////////////////////////////////////////////
private final String tradeId;
private final String id;
private final int traderId;
private final boolean disputeOpenerIsBuyer;
private final boolean disputeOpenerIsOfferer;
@ -123,6 +124,8 @@ public class Dispute implements Serializable {
this.arbitratorPubKeyRing = arbitratorPubKeyRing;
this.isSupportTicket = isSupportTicket;
this.openingDate = new Date().getTime();
id = tradeId + "_" + traderId;
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
@ -177,6 +180,10 @@ public class Dispute implements Serializable {
// Getters
///////////////////////////////////////////////////////////////////////////////////////////
public String getId() {
return id;
}
public String getTradeId() {
return tradeId;
}

View file

@ -381,8 +381,12 @@ public class TradeManager {
return offer.isMyOffer(keyRing);
}
public boolean isMyOfferInBtcBuyerRole(Offer offer) {
return !(isMyOffer(offer) ^ offer.getDirection() == Offer.Direction.BUY);
public boolean isBuyer(Offer offer) {
// If I am the offerer, the offer direction is taken, otherwise the mirrored direction
if (isMyOffer(offer))
return offer.getDirection() == Offer.Direction.BUY;
else
return offer.getDirection() == Offer.Direction.SELL;
}
public Optional<Trade> getTradeById(String tradeId) {

View file

@ -66,8 +66,7 @@ public class SetupDepositBalanceListener extends TradeTask {
|| newValue == Trade.State.DEPOSIT_SEEN_IN_NETWORK) {
walletService.removeBalanceListener(balanceListener);
log.debug(" UserThread.execute(this::unSubscribe);");
// TODO is that allowed?
// hack to remove tradeStateSubscription at callback
UserThread.execute(this::unSubscribe);
}
});
@ -81,8 +80,6 @@ public class SetupDepositBalanceListener extends TradeTask {
}
private void unSubscribe() {
//TODO investigate, seems to not get called sometimes
log.debug("unSubscribe tradeStateSubscription");
tradeStateSubscription.unsubscribe();
}

View file

@ -96,8 +96,8 @@ public class Preferences implements Serializable {
private final ArrayList<TradeCurrency> tradeCurrencies;
private BlockChainExplorer blockChainExplorerMainNet;
private BlockChainExplorer blockChainExplorerTestNet;
private boolean showPlaceOfferConfirmation;
private boolean showTakeOfferConfirmation;
private boolean showNotifications = true;
private boolean showInstructions = true;
private String backupDirectory;
private boolean autoSelectArbitrators = true;
private final Map<String, Boolean> showAgainMap;
@ -140,8 +140,8 @@ public class Preferences implements Serializable {
if (blockChainExplorerMainNet == null)
setBlockChainExplorerTestNet(blockChainExplorersMainNet.get(0));
showPlaceOfferConfirmation = persisted.getShowPlaceOfferConfirmation();
showTakeOfferConfirmation = persisted.getShowTakeOfferConfirmation();
showNotifications = persisted.getShowNotifications();
showInstructions = persisted.getShowInstructions();
backupDirectory = persisted.getBackupDirectory();
autoSelectArbitrators = persisted.getAutoSelectArbitrators();
showAgainMap = persisted.getShowAgainMap();
@ -163,8 +163,6 @@ public class Preferences implements Serializable {
tradeCurrencies = new ArrayList<>(tradeCurrenciesAsObservable);
setBlockChainExplorerTestNet(blockChainExplorersTestNet.get(0));
setBlockChainExplorerMainNet(blockChainExplorersMainNet.get(0));
showPlaceOfferConfirmation = true;
showTakeOfferConfirmation = true;
showAgainMap = new HashMap<>();
showAgainMap.put(PopupId.TRADE_WALLET, true);
@ -253,13 +251,13 @@ public class Preferences implements Serializable {
setBlockChainExplorerTestNet(blockChainExplorer);
}
public void setShowPlaceOfferConfirmation(boolean showPlaceOfferConfirmation) {
this.showPlaceOfferConfirmation = showPlaceOfferConfirmation;
public void setShowNotifications(boolean showNotifications) {
this.showNotifications = showNotifications;
storage.queueUpForSave(2000);
}
public void setShowTakeOfferConfirmation(boolean showTakeOfferConfirmation) {
this.showTakeOfferConfirmation = showTakeOfferConfirmation;
public void setShowInstructions(boolean showInstructions) {
this.showInstructions = showInstructions;
storage.queueUpForSave(2000);
}
@ -356,13 +354,12 @@ public class Preferences implements Serializable {
return blockChainExplorersTestNet;
}
public boolean getShowPlaceOfferConfirmation() {
return showPlaceOfferConfirmation;
public boolean getShowNotifications() {
return showNotifications;
}
public boolean getShowTakeOfferConfirmation() {
return showTakeOfferConfirmation;
public boolean getShowInstructions() {
return showInstructions;
}
public String getBackupDirectory() {

View file

@ -26,6 +26,7 @@ import io.bitsquare.common.crypto.KeyStorage;
import io.bitsquare.crypto.EncryptionServiceModule;
import io.bitsquare.gui.GuiModule;
import io.bitsquare.gui.common.view.CachingViewLoader;
import io.bitsquare.gui.main.intructions.InstructionCenter;
import io.bitsquare.gui.main.notifications.NotificationCenter;
import io.bitsquare.p2p.P2PModule;
import io.bitsquare.storage.Storage;
@ -60,6 +61,7 @@ class BitsquareAppModule extends AppModule {
bind(User.class).in(Singleton.class);
bind(Preferences.class).in(Singleton.class);
bind(NotificationCenter.class).in(Singleton.class);
bind(InstructionCenter.class).in(Singleton.class);
File storageDir = new File(env.getRequiredProperty(Storage.DIR_KEY));
bind(File.class).annotatedWith(named(Storage.DIR_KEY)).toInstance(storageDir);

View file

@ -26,21 +26,23 @@ import io.bitsquare.app.Version;
import io.bitsquare.arbitration.ArbitratorManager;
import io.bitsquare.arbitration.Dispute;
import io.bitsquare.arbitration.DisputeManager;
import io.bitsquare.btc.*;
import io.bitsquare.btc.AddressEntry;
import io.bitsquare.btc.FeePolicy;
import io.bitsquare.btc.TradeWalletService;
import io.bitsquare.btc.WalletService;
import io.bitsquare.btc.listeners.BalanceListener;
import io.bitsquare.btc.pricefeed.MarketPriceFeed;
import io.bitsquare.common.UserThread;
import io.bitsquare.gui.Navigation;
import io.bitsquare.gui.common.model.ViewModel;
import io.bitsquare.gui.common.view.ViewPath;
import io.bitsquare.gui.components.BalanceTextField;
import io.bitsquare.gui.components.BalanceWithConfirmationTextField;
import io.bitsquare.gui.components.TxIdTextField;
import io.bitsquare.gui.main.notifications.NotificationCenter;
import io.bitsquare.gui.main.popups.DisplayAlertMessagePopup;
import io.bitsquare.gui.main.popups.Popup;
import io.bitsquare.gui.main.popups.TacPopup;
import io.bitsquare.gui.main.popups.WalletPasswordPopup;
import io.bitsquare.gui.main.portfolio.PortfolioView;
import io.bitsquare.gui.main.portfolio.pendingtrades.PendingTradesView;
import io.bitsquare.gui.util.BSFormatter;
import io.bitsquare.locale.CountryUtil;
import io.bitsquare.locale.CurrencyUtil;
@ -69,9 +71,9 @@ import org.reactfx.util.Timer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@ -89,6 +91,8 @@ public class MainViewModel implements ViewModel {
private final Preferences preferences;
private final AlertManager alertManager;
private final WalletPasswordPopup walletPasswordPopup;
private NotificationCenter notificationCenter;
private TacPopup tacPopup;
private Navigation navigation;
private final BSFormatter formatter;
@ -133,8 +137,7 @@ public class MainViewModel implements ViewModel {
private java.util.Timer numberofBtcPeersTimer;
private java.util.Timer numberofP2PNetworkPeersTimer;
private Timer startupTimeout;
private Set<Subscription> tradeStateSubscriptions = new HashSet<>();
private Set<Subscription> disputeStateSubscriptions = new HashSet<>();
private final Map<String, Subscription> disputeIsClosedSubscriptionsMap = new HashMap<>();
///////////////////////////////////////////////////////////////////////////////////////////
@ -147,6 +150,7 @@ public class MainViewModel implements ViewModel {
ArbitratorManager arbitratorManager, P2PService p2PService, TradeManager tradeManager,
OpenOfferManager openOfferManager, DisputeManager disputeManager, Preferences preferences,
User user, AlertManager alertManager, WalletPasswordPopup walletPasswordPopup,
NotificationCenter notificationCenter, TacPopup tacPopup,
Navigation navigation, BSFormatter formatter) {
this.marketPriceFeed = marketPriceFeed;
this.user = user;
@ -160,6 +164,8 @@ public class MainViewModel implements ViewModel {
this.preferences = preferences;
this.alertManager = alertManager;
this.walletPasswordPopup = walletPasswordPopup;
this.notificationCenter = notificationCenter;
this.tacPopup = tacPopup;
this.navigation = navigation;
this.formatter = formatter;
@ -172,8 +178,6 @@ public class MainViewModel implements ViewModel {
BalanceWithConfirmationTextField.setWalletService(walletService);
if (BitsquareApp.DEV_MODE) {
preferences.setShowPlaceOfferConfirmation(false);
preferences.setShowTakeOfferConfirmation(false);
preferences.setUseAnimations(false);
preferences.setUseEffects(false);
}
@ -221,6 +225,16 @@ public class MainViewModel implements ViewModel {
}
///////////////////////////////////////////////////////////////////////////////////////////
// UI handlers
///////////////////////////////////////////////////////////////////////////////////////////
void onSplashScreenRemoved() {
isSplashScreenRemoved.set(true);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Initialisation
///////////////////////////////////////////////////////////////////////////////////////////
@ -360,33 +374,23 @@ public class MainViewModel implements ViewModel {
startupTimeout.stop();
// disputeManager
disputeManager.onAllServicesInitialized();
disputeManager.getDisputesAsObservableList().addListener((ListChangeListener<Dispute>) change -> {
change.next();
addDisputeClosedChangeListener(change.getAddedSubList());
updateDisputeStates();
onDisputesChangeListener(change.getAddedSubList(), change.getRemoved());
});
addDisputeClosedChangeListener(disputeManager.getDisputesAsObservableList());
updateDisputeStates();
disputeManager.onAllServicesInitialized();
onDisputesChangeListener(disputeManager.getDisputesAsObservableList(), null);
// tradeManager
tradeManager.getTrades().addListener((ListChangeListener<Trade>) c -> updateBalance());
tradeManager.getTrades().addListener((ListChangeListener<Trade>) change -> {
change.next();
setDisputeStateSubscriptions();
setTradeStateSubscriptions();
pendingTradesChanged();
tradeManager.getTrades().addListener((ListChangeListener<Trade>) change -> onTradesChanged());
onTradesChanged();
// We handle the trade period here as we display a global popup if we reached dispute time
tradesAndUIReady = EasyBind.combine(isSplashScreenRemoved, tradeManager.pendingTradesInitializedProperty(), (a, b) -> a && b);
tradesAndUIReady.subscribe((observable, oldValue, newValue) -> {
if (newValue)
applyTradePeriodState();
});
pendingTradesChanged();
setDisputeStateSubscriptions();
setTradeStateSubscriptions();
// arbitratorManager
arbitratorManager.onAllServicesInitialized();
// walletService
// In case we have any offers open or a pending trade we need to unlock our trading wallet so a trade can be executed automatically
@ -398,77 +402,120 @@ public class MainViewModel implements ViewModel {
|| disputeManager.getDisputesAsObservableList().size() > 0)) {
walletPasswordPopup.onAesKey(aesKey -> tradeWalletService.setAesKey(aesKey)).show();
}
// We handle the trade period here as we display a global popup if we reached dispute time
tradesAndUIReady = EasyBind.combine(isSplashScreenRemoved, tradeManager.pendingTradesInitializedProperty(), (a, b) -> a && b);
tradesAndUIReady.subscribe((observable, oldValue, newValue) -> {
if (newValue)
applyTradePeriodState();
});
walletService.addBalanceListener(new BalanceListener() {
@Override
public void onBalanceChanged(Coin balance, Transaction tx) {
updateBalance();
}
});
updateBalance();
setBitcoinNetworkSyncProgress(walletService.downloadPercentageProperty().get());
checkPeriodicallyForBtcSyncState();
// openOfferManager
openOfferManager.getOpenOffers().addListener((ListChangeListener<OpenOffer>) c -> updateBalance());
openOfferManager.onAllServicesInitialized();
// alertManager
arbitratorManager.onAllServicesInitialized();
alertManager.alertMessageProperty().addListener((observable, oldValue, newValue) -> displayAlertIfPresent(newValue));
displayAlertIfPresent(alertManager.alertMessageProperty().get());
setupP2PPeersInfo();
updateBalance();
setupDevDummyPaymentAccount();
setupMarketPriceFeed();
// tac
// TODO add link: https://bitsquare.io/arbitration_system.pdf
String text = "1. This software is experimental and provided \"as is\", without warranty of any kind, " +
"express or implied, including but not limited to the warranties of " +
"merchantability, fitness for a particular purpose and non-infringement.\n" +
"In no event shall the authors or copyright holders be liable for any claim, damages or other " +
"liability, whether in an action of contract, tort or otherwise, " +
"arising from, out of or in connection with the software or the use or other dealings in the software.\n\n" +
"2. The user is responsible to use the software in compliance with local laws.\n\n" +
"3. The user confirms that he has read and agreed to the rules defined in our " +
"Wiki regrading the dispute process\n" +
"(https://github.com/bitsquare/bitsquare/wiki/Arbitration-system).";
if (!preferences.getTacAccepted() && !BitsquareApp.DEV_MODE) {
new Popup().headLine("USER AGREEMENT")
.message(text)
.actionButtonText("I agree")
.closeButtonText("I disagree and quit")
.onAction(() -> {
preferences.setTacAccepted(true);
if (preferences.getBitcoinNetwork() == BitcoinNetwork.MAINNET)
UserThread.runAfter(() -> new Popup()
.warning("This software is still in alpha version.\n" +
"Please be aware that using Mainnet comes with the risk to lose funds " +
"in case of software bugs.\n" +
"To limit the possible losses the maximum allowed trading amount and the " +
"security deposit have been reduced to 0.01 BTC for the alpha version " +
"when using Mainnet.")
.headLine("Important information!")
.actionButtonText("I understand and want to use Mainnet")
.closeButtonText("Restart and use Testnet")
.onClose(() -> {
UserThread.execute(() -> preferences.setBitcoinNetwork(BitcoinNetwork.TESTNET));
UserThread.runAfter(BitsquareApp.shutDownHandler::run, 300, TimeUnit.MILLISECONDS);
})
.width(600)
.show(), 300, TimeUnit.MILLISECONDS);
})
.onClose(BitsquareApp.shutDownHandler::run)
.show();
}
tacPopup.showIfNeeded();
notificationCenter.onAllServicesInitialized();
// update nr of peers in footer
// now show app
showAppScreen.set(true);
}
///////////////////////////////////////////////////////////////////////////////////////////
// States
///////////////////////////////////////////////////////////////////////////////////////////
private void applyTradePeriodState() {
updateTradePeriodState();
tradeWalletService.addBlockChainListener(new BlockChainListener() {
@Override
public void notifyNewBestBlock(StoredBlock block) throws VerificationException {
updateTradePeriodState();
}
@Override
public void reorganize(StoredBlock splitPoint, List<StoredBlock> oldBlocks, List<StoredBlock> newBlocks)
throws VerificationException {
}
@Override
public boolean isTransactionRelevant(Transaction tx) throws ScriptException {
return false;
}
@Override
public void receiveFromBlock(Transaction tx, StoredBlock block, AbstractBlockChain.NewBlockType blockType, int relativityOffset)
throws VerificationException {
}
@Override
public boolean notifyTransactionIsInBlock(Sha256Hash txHash, StoredBlock block, AbstractBlockChain.NewBlockType blockType,
int relativityOffset) throws VerificationException {
return false;
}
});
}
private void updateTradePeriodState() {
tradeManager.getTrades().stream().forEach(trade -> {
int bestChainHeight = tradeWalletService.getBestChainHeight();
if (trade.getOpenDisputeTimeAsBlockHeight() > 0 && bestChainHeight >= trade.getOpenDisputeTimeAsBlockHeight())
trade.setTradePeriodState(Trade.TradePeriodState.TRADE_PERIOD_OVER);
else if (trade.getCheckPaymentTimeAsBlockHeight() > 0 && bestChainHeight >= trade.getCheckPaymentTimeAsBlockHeight())
trade.setTradePeriodState(Trade.TradePeriodState.HALF_REACHED);
String id;
String limitDate = formatter.addBlocksToNowDateFormatted(trade.getOpenDisputeTimeAsBlockHeight() - tradeWalletService.getBestChainHeight());
switch (trade.getTradePeriodState()) {
case NORMAL:
break;
case HALF_REACHED:
id = "displayHalfTradePeriodOver" + trade.getId();
if (preferences.showAgain(id) && !BitsquareApp.DEV_MODE) {
preferences.dontShowAgain(id);
new Popup().warning("Your trade with ID " + trade.getShortId() +
" has reached the half of the max. allowed trading period and " +
"is still not completed.\n\n" +
"The trade period ends on " + limitDate + "\n\n" +
"Please check your trade state at \"Portfolio/Open trades\" for further information.")
.show();
}
break;
case TRADE_PERIOD_OVER:
id = "displayTradePeriodOver" + trade.getId();
if (preferences.showAgain(id) && !BitsquareApp.DEV_MODE) {
preferences.dontShowAgain(id);
new Popup().warning("Your trade with ID " + trade.getShortId() +
" has reached the max. allowed trading period and is " +
"not completed.\n\n" +
"The trade period ended on " + limitDate + "\n\n" +
"Please check your trade at \"Portfolio/Open trades\" for contacting " +
"the arbitrator.")
.show();
}
break;
}
});
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
private void setupP2PPeersInfo() {
numConnectedPeersListener = (observable, oldValue, newValue) -> {
if ((int) oldValue > 0 && (int) newValue == 0) {
// give a bit of tolerance
@ -492,19 +539,10 @@ public class MainViewModel implements ViewModel {
updateP2pNetworkInfoWithPeersChanged((int) newValue);
};
p2PService.getNumConnectedPeers().addListener(numConnectedPeersListener);
}
// now show app
showAppScreen.set(true);
if (BitsquareApp.DEV_MODE && user.getPaymentAccounts().isEmpty()) {
OKPayAccount okPayAccount = new OKPayAccount();
okPayAccount.setAccountNr("dummy");
okPayAccount.setAccountName("OKPay dummy");
okPayAccount.setSelectedTradeCurrency(CurrencyUtil.getDefaultTradeCurrency());
okPayAccount.setCountry(CountryUtil.getDefaultCountry());
user.addPaymentAccount(okPayAccount);
}
private void setupMarketPriceFeed() {
if (marketPriceFeed.getCurrencyCode() == null)
marketPriceFeed.setCurrencyCode(preferences.getPreferredTradeCurrency().getCode());
if (marketPriceFeed.getType() == null)
@ -605,130 +643,39 @@ public class MainViewModel implements ViewModel {
}
///////////////////////////////////////////////////////////////////////////////////////////
// UI callbacks
///////////////////////////////////////////////////////////////////////////////////////////
void onSplashScreenRemoved() {
isSplashScreenRemoved.set(true);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Apply states
///////////////////////////////////////////////////////////////////////////////////////////
private void applyTradePeriodState() {
updateTradePeriodState();
tradeWalletService.addBlockChainListener(new BlockChainListener() {
@Override
public void notifyNewBestBlock(StoredBlock block) throws VerificationException {
updateTradePeriodState();
}
@Override
public void reorganize(StoredBlock splitPoint, List<StoredBlock> oldBlocks, List<StoredBlock> newBlocks)
throws VerificationException {
}
@Override
public boolean isTransactionRelevant(Transaction tx) throws ScriptException {
return false;
}
@Override
public void receiveFromBlock(Transaction tx, StoredBlock block, AbstractBlockChain.NewBlockType blockType, int relativityOffset)
throws VerificationException {
}
@Override
public boolean notifyTransactionIsInBlock(Sha256Hash txHash, StoredBlock block, AbstractBlockChain.NewBlockType blockType,
int relativityOffset) throws VerificationException {
return false;
}
});
}
private void setWalletServiceException(Throwable error) {
setBitcoinNetworkSyncProgress(0);
btcSplashInfo.set("Nr. of Bitcoin network peers: " + numBTCPeers + " / connecting to " + btcNetworkAsString + " failed");
btcFooterInfo.set(btcSplashInfo.get());
if (error instanceof TimeoutException) {
walletServiceErrorMsg.set("Connecting to the bitcoin network failed because of a timeout.");
} else if (error.getCause() instanceof BlockStoreException) {
new Popup().warning("Bitsquare is already running. You cannot run 2 instances of Bitsquare.")
.closeButtonText("Shut down")
.onClose(BitsquareApp.shutDownHandler::run)
.show();
} else if (error.getMessage() != null) {
walletServiceErrorMsg.set("Connection to the bitcoin network failed because of an error:" + error.getMessage());
} else {
walletServiceErrorMsg.set("Connection to the bitcoin network failed because of an error:" + error.toString());
private void onDisputesChangeListener(List<? extends Dispute> addedList, @Nullable List<? extends Dispute> removedList) {
if (removedList != null) {
removedList.stream().forEach(dispute -> {
String id = dispute.getId();
if (disputeIsClosedSubscriptionsMap.containsKey(id)) {
disputeIsClosedSubscriptionsMap.get(id).unsubscribe();
disputeIsClosedSubscriptionsMap.remove(id);
}
});
}
}
addedList.stream().forEach(dispute -> {
String id = dispute.getId();
if (disputeIsClosedSubscriptionsMap.containsKey(id)) {
log.warn("We have already an entry in disputeStateSubscriptionsMap. That should never happen.");
} else {
Subscription disputeStateSubscription = EasyBind.subscribe(dispute.isClosedProperty(),
disputeState -> {
int openDisputes = disputeManager.getDisputesAsObservableList().stream()
.filter(e -> !e.isClosed())
.collect(Collectors.toList()).size();
if (openDisputes > 0)
numOpenDisputesAsString.set(String.valueOf(openDisputes));
if (openDisputes > 9)
numOpenDisputesAsString.set("?");
private void updateTradePeriodState() {
tradeManager.getTrades().stream().forEach(trade -> {
int bestChainHeight = tradeWalletService.getBestChainHeight();
if (trade.getOpenDisputeTimeAsBlockHeight() > 0 && bestChainHeight >= trade.getOpenDisputeTimeAsBlockHeight())
trade.setTradePeriodState(Trade.TradePeriodState.TRADE_PERIOD_OVER);
else if (trade.getCheckPaymentTimeAsBlockHeight() > 0 && bestChainHeight >= trade.getCheckPaymentTimeAsBlockHeight())
trade.setTradePeriodState(Trade.TradePeriodState.HALF_REACHED);
String id;
String limitDate = formatter.addBlocksToNowDateFormatted(trade.getOpenDisputeTimeAsBlockHeight() - tradeWalletService.getBestChainHeight());
switch (trade.getTradePeriodState()) {
case NORMAL:
break;
case HALF_REACHED:
id = "displayHalfTradePeriodOver" + trade.getId();
if (preferences.showAgain(id) && !BitsquareApp.DEV_MODE) {
preferences.dontShowAgain(id);
new Popup().warning("Your trade with ID " + trade.getShortId() +
" has reached the half of the max. allowed trading period and " +
"is still not completed.\n\n" +
"The trade period ends on " + limitDate + "\n\n" +
"Please check your trade state at \"Portfolio/Open trades\" for further information.")
.show();
}
break;
case TRADE_PERIOD_OVER:
id = "displayTradePeriodOver" + trade.getId();
if (preferences.showAgain(id) && !BitsquareApp.DEV_MODE) {
preferences.dontShowAgain(id);
new Popup().warning("Your trade with ID " + trade.getShortId() +
" has reached the max. allowed trading period and is " +
"not completed.\n\n" +
"The trade period ended on " + limitDate + "\n\n" +
"Please check your trade at \"Portfolio/Open trades\" for contacting " +
"the arbitrator.")
.show();
}
break;
showOpenDisputesNotification.set(openDisputes > 0);
});
disputeIsClosedSubscriptionsMap.put(id, disputeStateSubscription);
}
});
}
private void addDisputeClosedChangeListener(List<? extends Dispute> list) {
list.stream().forEach(e -> e.isClosedProperty().addListener((observable, oldValue, newValue) -> {
if (newValue)
updateDisputeStates();
}));
}
private void updateDisputeStates() {
int openDisputes = disputeManager.getDisputesAsObservableList().stream().filter(e -> !e.isClosed()).collect(Collectors.toList()).size();
if (openDisputes > 0)
numOpenDisputesAsString.set(String.valueOf(openDisputes));
if (openDisputes > 9)
numOpenDisputesAsString.set("?");
showOpenDisputesNotification.set(openDisputes > 0);
}
private void pendingTradesChanged() {
private void onTradesChanged() {
long numPendingTrades = tradeManager.getTrades().size();
if (numPendingTrades > 0)
numPendingTradesAsString.set(String.valueOf(numPendingTrades));
@ -738,132 +685,6 @@ public class MainViewModel implements ViewModel {
showPendingTradesNotification.set(numPendingTrades > 0);
}
private void setTradeStateSubscriptions() {
tradeStateSubscriptions.stream().forEach(Subscription::unsubscribe);
tradeStateSubscriptions.clear();
tradeManager.getTrades().stream().forEach(trade -> {
Subscription tradeStateSubscription = EasyBind.subscribe(trade.stateProperty(), newValue -> {
if (newValue != null) {
applyTradeState(trade);
}
});
tradeStateSubscriptions.add(tradeStateSubscription);
});
}
private void applyTradeState(Trade trade) {
Trade.State state = trade.getState();
log.debug("addTradeStateListeners " + state);
boolean isBtcBuyer = tradeManager.isMyOfferInBtcBuyerRole(trade.getOffer());
String headLine = "Notification for trade with ID " + trade.getShortId();
String message = null;
String id = "notificationPopup_" + state + trade.getId();
if (isBtcBuyer) {
switch (state) {
case DEPOSIT_PUBLISHED_MSG_RECEIVED:
message = "Your offer has been accepted by a seller.\n" +
"You need to wait for one blockchain confirmation before starting the payment.";
break;
case DEPOSIT_CONFIRMED:
message = "The deposit transaction of your trade has got the first blockchain confirmation.\n" +
"You have to start the payment to the bitcoin seller now.";
break;
/* case FIAT_PAYMENT_RECEIPT_MSG_RECEIVED:
case PAYOUT_TX_COMMITTED:
case PAYOUT_TX_SENT:*/
case PAYOUT_BROAD_CASTED:
message = "The bitcoin seller has confirmed the receipt of your payment and the payout transaction has been published.\n" +
"The trade is now completed and you can withdraw your funds.";
break;
}
} else {
switch (state) {
case DEPOSIT_PUBLISHED_MSG_RECEIVED:
message = "Your offer has been accepted by a buyer.\n" +
"You need to wait for one blockchain confirmation before starting the payment.";
break;
case FIAT_PAYMENT_STARTED_MSG_RECEIVED:
message = "The bitcoin buyer has started the payment.\n" +
"Please check your payment account if you have received his payment.";
break;
/* case FIAT_PAYMENT_RECEIPT_MSG_SENT:
case PAYOUT_TX_RECEIVED:
case PAYOUT_TX_COMMITTED:*/
case PAYOUT_BROAD_CASTED:
message = "The payout transaction has been published.\n" +
"The trade is now completed and you can withdraw your funds.";
}
}
ViewPath currentPath = navigation.getCurrentPath();
boolean isPendingTradesViewCurrentView = currentPath != null &&
currentPath.size() == 3 &&
currentPath.get(2).equals(PendingTradesView.class);
if (message != null) {
//TODO we get that called initially before the navigation is inited
if (isPendingTradesViewCurrentView || currentPath == null) {
if (preferences.showAgain(id) && !BitsquareApp.DEV_MODE)
new Popup().headLine(headLine)
.message(message)
.show();
preferences.dontShowAgain(id);
} else {
if (preferences.showAgain(id) && !BitsquareApp.DEV_MODE)
new Popup().headLine(headLine)
.message(message)
.actionButtonText("Go to \"Portfolio/Open trades\"")
.onAction(() -> {
FxTimer.runLater(Duration.ofMillis(100),
() -> navigation.navigateTo(MainView.class, PortfolioView.class, PendingTradesView.class)
);
})
.show();
preferences.dontShowAgain(id);
}
}
}
private void setDisputeStateSubscriptions() {
disputeStateSubscriptions.stream().forEach(Subscription::unsubscribe);
disputeStateSubscriptions.clear();
tradeManager.getTrades().stream().forEach(trade -> {
Subscription disputeStateSubscription = EasyBind.subscribe(trade.disputeStateProperty(), disputeState -> {
if (disputeState != null) {
applyDisputeState(trade, disputeState);
}
});
disputeStateSubscriptions.add(disputeStateSubscription);
});
}
private void applyDisputeState(Trade trade, Trade.DisputeState disputeState) {
switch (disputeState) {
case NONE:
break;
case DISPUTE_REQUESTED:
break;
case DISPUTE_STARTED_BY_PEER:
disputeManager.findOwnDispute(trade.getId()).ifPresent(dispute -> {
String msg;
if (dispute.isSupportTicket())
msg = "Your trading peer has encountered technical problems and requested support for trade with ID " + trade.getShortId() + ".\n" +
"Please await further instructions from the arbitrator.\n" +
"Your funds are safe and will be refunded as soon the problem is resolved.";
else
msg = "Your trading peer has requested a dispute for trade with ID " + trade.getShortId() + ".";
new Popup().information(msg).show();
});
break;
case DISPUTE_CLOSED:
new Popup().information("A support ticket for trade with ID " + trade.getShortId() + " has been closed.").show();
break;
}
}
private void setBitcoinNetworkSyncProgress(double value) {
btcSyncProgress.set(value);
String numPeers = "Nr. of Bitcoin network peers: " + numBTCPeers;
@ -885,10 +706,39 @@ public class MainViewModel implements ViewModel {
}
}
private void setWalletServiceException(Throwable error) {
setBitcoinNetworkSyncProgress(0);
btcSplashInfo.set("Nr. of Bitcoin network peers: " + numBTCPeers + " / connecting to " + btcNetworkAsString + " failed");
btcFooterInfo.set(btcSplashInfo.get());
if (error instanceof TimeoutException) {
walletServiceErrorMsg.set("Connecting to the bitcoin network failed because of a timeout.");
} else if (error.getCause() instanceof BlockStoreException) {
new Popup().warning("Bitsquare is already running. You cannot run 2 instances of Bitsquare.")
.closeButtonText("Shut down")
.onClose(BitsquareApp.shutDownHandler::run)
.show();
} else if (error.getMessage() != null) {
walletServiceErrorMsg.set("Connection to the bitcoin network failed because of an error:" + error.getMessage());
} else {
walletServiceErrorMsg.set("Connection to the bitcoin network failed because of an error:" + error.toString());
}
}
private void stopCheckForBtcSyncStateTimer() {
if (checkForBtcSyncStateTimer != null) {
checkForBtcSyncStateTimer.stop();
checkForBtcSyncStateTimer = null;
}
}
private void setupDevDummyPaymentAccount() {
if (BitsquareApp.DEV_MODE && user.getPaymentAccounts().isEmpty()) {
OKPayAccount okPayAccount = new OKPayAccount();
okPayAccount.setAccountNr("dummy");
okPayAccount.setAccountName("OKPay dummy");
okPayAccount.setSelectedTradeCurrency(CurrencyUtil.getDefaultTradeCurrency());
okPayAccount.setCountry(CountryUtil.getDefaultCountry());
user.addPaymentAccount(okPayAccount);
}
}
}

View file

@ -575,7 +575,7 @@ public class TraderDisputeView extends ActivatableView<VBox, Void> {
// TODO There are still some cell rendering issues on updates
setGraphic(messageAnchorPane);
} else {
if (sendMsgProgressIndicator != null)
if (sendMsgProgressIndicator != null && sendMsgProgressIndicatorListener != null)
sendMsgProgressIndicator.progressProperty().removeListener(sendMsgProgressIndicatorListener);
messageAnchorPane.prefWidthProperty().unbind();
@ -796,7 +796,7 @@ public class TraderDisputeView extends ActivatableView<VBox, Void> {
} else {
if (closedProperty != null)
closedProperty.removeListener(listener);
setText("");
}
}

View file

@ -0,0 +1,15 @@
package io.bitsquare.gui.main.intructions;
import com.google.inject.Inject;
import io.bitsquare.gui.main.popups.Popup;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Instruction extends Popup {
private static final Logger log = LoggerFactory.getLogger(Instruction.class);
@Inject
public Instruction() {
}
}

View file

@ -0,0 +1,49 @@
package io.bitsquare.gui.main.intructions;
import com.google.inject.Inject;
import io.bitsquare.gui.main.notifications.Notification;
import io.bitsquare.trade.TradeManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;
public class InstructionCenter {
private final Logger log = LoggerFactory.getLogger(InstructionCenter.class);
private Queue<io.bitsquare.gui.main.notifications.Notification> notifications = new LinkedBlockingQueue<>(3);
private io.bitsquare.gui.main.notifications.Notification displayedNotification;
private TradeManager tradeManager;
@Inject
public InstructionCenter(TradeManager tradeManager) {
this.tradeManager = tradeManager;
}
void queueForDisplay(io.bitsquare.gui.main.notifications.Notification notification) {
boolean result = notifications.offer(notification);
if (!result)
log.warn("The capacity is full with popups in the queue.\n\t" +
"Not added new notification=" + notification);
displayNext();
}
void isHidden(Notification notification) {
if (displayedNotification == null || displayedNotification == notification) {
displayedNotification = null;
displayNext();
} else {
log.warn("We got a isHidden called with a wrong notification.\n\t" +
"notification (argument)=" + notification + "\n\tdisplayedPopup=" + displayedNotification);
}
}
private void displayNext() {
if (displayedNotification == null) {
if (!notifications.isEmpty()) {
displayedNotification = notifications.poll();
displayedNotification.display();
}
}
}
}

View file

@ -0,0 +1,43 @@
package io.bitsquare.gui.main.notifications;
import io.bitsquare.gui.main.popups.Popup;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Notification extends Popup {
private static final Logger log = LoggerFactory.getLogger(Notification.class);
private boolean hasBeenDisplayed;
public Notification() {
NotificationCenter.add(this);
}
public Notification headLine(String headLine) {
return (Notification) super.headLine(headLine);
}
public Notification tradeHeadLine(String tradeId) {
return headLine("Notification for trade with ID " + tradeId);
}
public Notification disputeHeadLine(String tradeId) {
return headLine("Support ticket for trade with ID " + tradeId);
}
public Notification message(String message) {
return (Notification) super.message(message);
}
public void show() {
super.show();
hasBeenDisplayed = true;
}
public void hide() {
super.hide();
}
public boolean isHasBeenDisplayed() {
return hasBeenDisplayed;
}
}

View file

@ -1,14 +1,195 @@
package io.bitsquare.gui.main.notifications;
import com.google.inject.Inject;
import io.bitsquare.app.Log;
import io.bitsquare.arbitration.DisputeManager;
import io.bitsquare.trade.Trade;
import io.bitsquare.trade.TradeManager;
import javafx.collections.ListChangeListener;
import org.fxmisc.easybind.EasyBind;
import org.fxmisc.easybind.Subscription;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class NotificationCenter {
private static final Logger log = LoggerFactory.getLogger(NotificationCenter.class);
@Inject
public NotificationCenter() {
///////////////////////////////////////////////////////////////////////////////////////////
// Static
///////////////////////////////////////////////////////////////////////////////////////////
private final static List<Notification> notifications = new ArrayList<>();
static void add(Notification notification) {
notifications.add(notification);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Instance fields
///////////////////////////////////////////////////////////////////////////////////////////
private TradeManager tradeManager;
private DisputeManager disputeManager;
private final Map<String, Subscription> disputeStateSubscriptionsMap = new HashMap<>();
private final Map<String, Subscription> tradeStateSubscriptionsMap = new HashMap<>();
private String selectedTradeId;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, initialisation
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public NotificationCenter(TradeManager tradeManager, DisputeManager disputeManager) {
this.tradeManager = tradeManager;
this.disputeManager = disputeManager;
}
public void onAllServicesInitialized() {
tradeManager.getTrades().addListener((ListChangeListener<Trade>) change -> {
change.next();
log.error("change getRemoved " + change.getRemoved());
log.error("change getAddedSubList " + change.getAddedSubList());
if (change.wasRemoved()) {
change.getRemoved().stream().forEach(trade -> {
String tradeId = trade.getId();
if (disputeStateSubscriptionsMap.containsKey(tradeId)) {
disputeStateSubscriptionsMap.get(tradeId).unsubscribe();
disputeStateSubscriptionsMap.remove(tradeId);
}
if (tradeStateSubscriptionsMap.containsKey(tradeId)) {
tradeStateSubscriptionsMap.get(tradeId).unsubscribe();
tradeStateSubscriptionsMap.remove(tradeId);
}
});
}
if (change.wasAdded()) {
change.getAddedSubList().stream().forEach(trade -> {
String tradeId = trade.getId();
if (disputeStateSubscriptionsMap.containsKey(tradeId)) {
log.warn("We have already an entry in disputeStateSubscriptionsMap. That should never happen.");
} else {
Subscription disputeStateSubscription = EasyBind.subscribe(trade.disputeStateProperty(), disputeState -> onDisputeStateChanged(trade, disputeState));
disputeStateSubscriptionsMap.put(tradeId, disputeStateSubscription);
}
if (tradeStateSubscriptionsMap.containsKey(tradeId)) {
log.warn("We have already an entry in tradeStateSubscriptionsMap. That should never happen.");
} else {
Subscription tradeStateSubscription = EasyBind.subscribe(trade.stateProperty(), tradeState -> onTradeStateChanged(trade, tradeState));
tradeStateSubscriptionsMap.put(tradeId, tradeStateSubscription);
}
});
}
});
tradeManager.getTrades().stream()
.forEach(trade -> {
String tradeId = trade.getId();
Subscription disputeStateSubscription = EasyBind.subscribe(trade.disputeStateProperty(), disputeState -> onDisputeStateChanged(trade, disputeState));
disputeStateSubscriptionsMap.put(tradeId, disputeStateSubscription);
Subscription tradeStateSubscription = EasyBind.subscribe(trade.stateProperty(), tradeState -> onTradeStateChanged(trade, tradeState));
tradeStateSubscriptionsMap.put(tradeId, tradeStateSubscription);
});
}
///////////////////////////////////////////////////////////////////////////////////////////
// Setter/Getter
///////////////////////////////////////////////////////////////////////////////////////////
public String getSelectedTradeId() {
return selectedTradeId;
}
public void setSelectedTradeId(String selectedTradeId) {
this.selectedTradeId = selectedTradeId;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
private void onTradeStateChanged(Trade trade, Trade.State tradeState) {
Log.traceCall(tradeState.toString());
String message = null;
if (tradeManager.isBuyer(trade.getOffer())) {
switch (tradeState) {
case DEPOSIT_PUBLISHED_MSG_RECEIVED:
message = "Your offer has been accepted by a seller.\n" +
"You need to wait for one blockchain confirmation before starting the payment.";
break;
case DEPOSIT_CONFIRMED:
message = "The deposit transaction of your trade has got the first blockchain confirmation.\n" +
"You have to start the payment to the bitcoin seller now.";
break;
/* case FIAT_PAYMENT_RECEIPT_MSG_RECEIVED:
case PAYOUT_TX_COMMITTED:
case PAYOUT_TX_SENT:*/
case PAYOUT_BROAD_CASTED:
message = "The bitcoin seller has confirmed the receipt of your payment and the payout transaction has been published.\n" +
"The trade is now completed and you can withdraw your funds.";
break;
}
} else {
switch (tradeState) {
case DEPOSIT_PUBLISHED_MSG_RECEIVED:
message = "Your offer has been accepted by a buyer.\n" +
"You need to wait for one blockchain confirmation before starting the payment.";
break;
case FIAT_PAYMENT_STARTED_MSG_RECEIVED:
message = "The bitcoin buyer has started the payment.\n" +
"Please check your payment account if you have received his payment.";
break;
/* case FIAT_PAYMENT_RECEIPT_MSG_SENT:
case PAYOUT_TX_RECEIVED:
case PAYOUT_TX_COMMITTED:*/
case PAYOUT_BROAD_CASTED:
message = "The payout transaction has been published.\n" +
"The trade is now completed and you can withdraw your funds.";
}
}
if (message != null && !trade.getId().equals(selectedTradeId))
new Notification().tradeHeadLine(trade.getShortId()).message(message).show();
}
private void onDisputeStateChanged(Trade trade, Trade.DisputeState disputeState) {
Log.traceCall(disputeState.toString());
String message = null;
switch (disputeState) {
case NONE:
break;
case DISPUTE_REQUESTED:
break;
case DISPUTE_STARTED_BY_PEER:
if (disputeManager.findOwnDispute(trade.getId()).isPresent()) {
if (disputeManager.findOwnDispute(trade.getId()).get().isSupportTicket())
message = "Your trading peer has encountered technical problems and requested support for trade with ID " + trade.getShortId() + ".\n" +
"Please await further instructions from the arbitrator.\n" +
"Your funds are safe and will be refunded as soon the problem is resolved.";
else
message = "Your trading peer has requested a dispute for trade with ID " + trade.getShortId() + ".";
}
break;
case DISPUTE_CLOSED:
message = "A support ticket for trade with ID " + trade.getShortId() + " has been closed.";
break;
}
if (message != null)
new Notification().tradeHeadLine(trade.getShortId()).message(message).show();
}
}

View file

@ -391,14 +391,6 @@ class CreateOfferDataModel extends ActivatableDataModel {
return user.getAcceptedArbitrators();
}
public void setShowPlaceOfferConfirmation(boolean selected) {
preferences.setShowPlaceOfferConfirmation(selected);
}
public boolean getShowPlaceOfferConfirmation() {
return preferences.getShowPlaceOfferConfirmation();
}
public Preferences getPreferences() {
return preferences;
}

View file

@ -47,6 +47,7 @@ import io.bitsquare.locale.BSResources;
import io.bitsquare.locale.TradeCurrency;
import io.bitsquare.payment.PaymentAccount;
import io.bitsquare.trade.offer.Offer;
import io.bitsquare.user.Preferences;
import javafx.beans.value.ChangeListener;
import javafx.collections.FXCollections;
import javafx.event.ActionEvent;
@ -109,6 +110,7 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
private EventHandler<ActionEvent> currencyComboBoxSelectionHandler;
private int gridRow = 0;
private Preferences preferences;
///////////////////////////////////////////////////////////////////////////////////////////
@ -116,11 +118,12 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
private CreateOfferView(CreateOfferViewModel model, Navigation navigation, OfferDetailsPopup offerDetailsPopup) {
private CreateOfferView(CreateOfferViewModel model, Navigation navigation, OfferDetailsPopup offerDetailsPopup, Preferences preferences) {
super(model);
this.navigation = navigation;
this.offerDetailsPopup = offerDetailsPopup;
this.preferences = preferences;
}
@Override
@ -210,8 +213,11 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
private void onPlaceOffer() {
if (model.isBootstrapped()) {
Offer offer = model.createAndGetOffer();
if (model.getShowPlaceOfferConfirmation()) {
offerDetailsPopup.onPlaceOffer(o -> model.onPlaceOffer(o)).show(offer);
String id = "CreatOfferConfirmation";
if (preferences.showAgain(id)) {
offerDetailsPopup.onPlaceOffer(model::onPlaceOffer)
.dontShowAgainId(id)
.show(offer);
} else {
if (model.hasAcceptedArbitrators()) {
model.onPlaceOffer(offer);

View file

@ -577,13 +577,4 @@ class CreateOfferViewModel extends ActivatableWithDataModel<CreateOfferDataModel
public List<Arbitrator> getArbitrators() {
return dataModel.getArbitrators();
}
public void setShowPlaceOfferConfirmation(boolean selected) {
dataModel.setShowPlaceOfferConfirmation(selected);
}
public boolean getShowPlaceOfferConfirmation() {
return dataModel.getShowPlaceOfferConfirmation();
}
}

View file

@ -377,14 +377,6 @@ class TakeOfferDataModel extends ActivatableDataModel {
return addressEntry;
}
public boolean getShowTakeOfferConfirmation() {
return preferences.getShowTakeOfferConfirmation();
}
public void setShowTakeOfferConfirmation(boolean selected) {
preferences.setShowTakeOfferConfirmation(selected);
}
public List<Arbitrator> getArbitrators() {
return user.getAcceptedArbitrators();
}

View file

@ -43,6 +43,7 @@ import io.bitsquare.gui.util.Layout;
import io.bitsquare.locale.BSResources;
import io.bitsquare.payment.PaymentAccount;
import io.bitsquare.trade.offer.Offer;
import io.bitsquare.user.Preferences;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.geometry.*;
@ -71,6 +72,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
private final Navigation navigation;
private final BSFormatter formatter;
private final OfferDetailsPopup offerDetailsPopup;
private Preferences preferences;
private ScrollPane scrollPane;
private GridPane gridPane;
private ImageView imageView;
@ -108,12 +110,14 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
private TakeOfferView(TakeOfferViewModel model, Navigation navigation, BSFormatter formatter, OfferDetailsPopup offerDetailsPopup) {
private TakeOfferView(TakeOfferViewModel model, Navigation navigation, BSFormatter formatter,
OfferDetailsPopup offerDetailsPopup, Preferences preferences) {
super(model);
this.navigation = navigation;
this.formatter = formatter;
this.offerDetailsPopup = offerDetailsPopup;
this.preferences = preferences;
}
@Override
@ -349,8 +353,11 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
private void onTakeOffer() {
Offer offer = model.getOffer();
if (model.getShowTakeOfferConfirmation()) {
offerDetailsPopup.onTakeOffer(() -> model.onTakeOffer()).show(offer, model.dataModel.amountAsCoin.get());
String id = "TakeOfferConfirmation";
if (preferences.showAgain(id)) {
offerDetailsPopup.onTakeOffer(() -> model.onTakeOffer())
.dontShowAgainId(id)
.show(offer, model.dataModel.amountAsCoin.get());
} else {
if (model.hasAcceptedArbitrators()) {
model.onTakeOffer();

View file

@ -543,10 +543,6 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
return dataModel.getTradeCurrency();
}
public boolean getShowTakeOfferConfirmation() {
return dataModel.getShowTakeOfferConfirmation();
}
public List<Arbitrator> getArbitrators() {
return dataModel.getArbitrators();
}

View file

@ -33,7 +33,6 @@ import io.bitsquare.user.Preferences;
import io.bitsquare.user.User;
import javafx.geometry.Insets;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.TextField;
import javafx.scene.control.Tooltip;
import org.bitcoinj.core.Coin;
@ -51,7 +50,7 @@ public class OfferDetailsPopup extends Popup {
protected static final Logger log = LoggerFactory.getLogger(OfferDetailsPopup.class);
private final BSFormatter formatter;
private final Preferences preferences;
protected final Preferences preferences;
private final User user;
private KeyRing keyRing;
private final Navigation navigation;
@ -97,6 +96,11 @@ public class OfferDetailsPopup extends Popup {
return this;
}
public OfferDetailsPopup dontShowAgainId(String dontShowAgainId) {
this.dontShowAgainId = dontShowAgainId;
return this;
}
public OfferDetailsPopup onPlaceOffer(Consumer<Offer> placeOfferHandler) {
this.placeOfferHandlerOptional = Optional.of(placeOfferHandler);
return this;
@ -189,13 +193,13 @@ public class OfferDetailsPopup extends Popup {
addLabelTextField(gridPane, rowIndex, "Please note:", Offer.TAC_OFFERER, Layout.FIRST_ROW_AND_GROUP_DISTANCE);
Button cancelButton = addConfirmButton(true);
addCancelButton(cancelButton, true);
addCancelButton(cancelButton);
} else if (takeOfferHandlerOptional.isPresent()) {
addTitledGroupBg(gridPane, ++rowIndex, 1, "Contract", Layout.GROUP_DISTANCE);
addLabelTextField(gridPane, rowIndex, "Terms and conditions:", Offer.TAC_TAKER, Layout.FIRST_ROW_AND_GROUP_DISTANCE);
Button cancelButton = addConfirmButton(false);
addCancelButton(cancelButton, false);
addCancelButton(cancelButton);
} else {
Button cancelButton = addButtonAfterGroup(gridPane, ++rowIndex, "Close");
cancelButton.setOnAction(e -> {
@ -229,19 +233,10 @@ public class OfferDetailsPopup extends Popup {
return tuple.second;
}
private void addCancelButton(Button cancelButton, boolean isPlaceOffer) {
private void addCancelButton(Button cancelButton) {
cancelButton.setOnAction(e -> {
closeHandlerOptional.ifPresent(closeHandler -> closeHandler.run());
hide();
});
CheckBox checkBox = addCheckBox(gridPane, ++rowIndex, "Don't show again", 5);
if (isPlaceOffer) {
checkBox.setSelected(!preferences.getShowPlaceOfferConfirmation());
checkBox.setOnAction(e -> preferences.setShowPlaceOfferConfirmation(!checkBox.isSelected()));
} else {
checkBox.setSelected(!preferences.getShowTakeOfferConfirmation());
checkBox.setOnAction(e -> preferences.setShowTakeOfferConfirmation(!checkBox.isSelected()));
}
}
}

View file

@ -70,7 +70,7 @@ public class Popup {
private boolean showProgressIndicator;
private Button actionButton;
protected Label headLineLabel;
private String dontShowAgainId;
protected String dontShowAgainId;
private Preferences preferences;
private ChangeListener<Number> positionListener;
private Timer centerTime;

View file

@ -0,0 +1,63 @@
package io.bitsquare.gui.main.popups;
import com.google.inject.Inject;
import io.bitsquare.app.BitsquareApp;
import io.bitsquare.btc.BitcoinNetwork;
import io.bitsquare.common.UserThread;
import io.bitsquare.user.Preferences;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.TimeUnit;
public class TacPopup extends Popup {
private static final Logger log = LoggerFactory.getLogger(TacPopup.class);
private Preferences preferences;
@Inject
public TacPopup(Preferences preferences) {
this.preferences = preferences;
}
public void showIfNeeded() {
if (!preferences.getTacAccepted() && !BitsquareApp.DEV_MODE) {
// TODO add link: https://bitsquare.io/arbitration_system.pdf
headLine("USER AGREEMENT");
String text = "1. This software is experimental and provided \"as is\", without warranty of any kind, " +
"express or implied, including but not limited to the warranties of " +
"merchantability, fitness for a particular purpose and non-infringement.\n" +
"In no event shall the authors or copyright holders be liable for any claim, damages or other " +
"liability, whether in an action of contract, tort or otherwise, " +
"arising from, out of or in connection with the software or the use or other dealings in the software.\n\n" +
"2. The user is responsible to use the software in compliance with local laws.\n\n" +
"3. The user confirms that he has read and agreed to the rules defined in our " +
"Wiki regrading the dispute process\n" +
"(https://github.com/bitsquare/bitsquare/wiki/Arbitration-system).";
message(text);
actionButtonText("I agree");
closeButtonText("I disagree and quit");
onAction(() -> {
preferences.setTacAccepted(true);
if (preferences.getBitcoinNetwork() == BitcoinNetwork.MAINNET)
UserThread.runAfter(() -> new Popup()
.warning("This software is still in alpha version.\n" +
"Please be aware that using Mainnet comes with the risk to lose funds " +
"in case of software bugs.\n" +
"To limit the possible losses the maximum allowed trading amount and the " +
"security deposit have been reduced to 0.01 BTC for the alpha version " +
"when using Mainnet.")
.headLine("Important information!")
.actionButtonText("I understand and want to use Mainnet")
.closeButtonText("Restart and use Testnet")
.onClose(() -> {
UserThread.execute(() -> preferences.setBitcoinNetwork(BitcoinNetwork.TESTNET));
UserThread.runAfter(BitsquareApp.shutDownHandler::run, 300, TimeUnit.MILLISECONDS);
})
.width(600)
.show(), 300, TimeUnit.MILLISECONDS);
});
onClose(BitsquareApp.shutDownHandler::run);
super.show();
}
}
}

View file

@ -127,12 +127,8 @@ public class PendingTradesView extends ActivatableViewAndModel<VBox, PendingTrad
selectedSubView.deactivate();
if (selectedItem.getTrade() != null) {
// If we are the offerer the direction is like expected
// If we are the taker the direction is mirrored
if (model.dataModel.isOfferer())
selectedSubView = model.dataModel.isBuyOffer() ? new BuyerSubView(model) : new SellerSubView(model);
else
selectedSubView = model.dataModel.isBuyOffer() ? new SellerSubView(model) : new BuyerSubView(model);
selectedSubView = model.dataModel.tradeManager.isBuyer(model.dataModel.getOffer()) ?
new BuyerSubView(model) : new SellerSubView(model);
selectedSubView.setMinHeight(430);
VBox.setVgrow(selectedSubView, Priority.ALWAYS);

View file

@ -23,6 +23,7 @@ import io.bitsquare.gui.util.Layout;
import io.bitsquare.locale.LanguageUtil;
import io.bitsquare.locale.TradeCurrency;
import io.bitsquare.user.BlockChainExplorer;
import io.bitsquare.user.Preferences;
import javafx.beans.value.ChangeListener;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
@ -38,30 +39,32 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
// not supported yet
//private ComboBox<String> btcDenominationComboBox;
private ComboBox<BlockChainExplorer> blockExplorerComboBox;
private ComboBox<BlockChainExplorer> blockChainExplorerComboBox;
private ComboBox<String> languageComboBox;
private ComboBox<TradeCurrency> tradeCurrencyComboBox;
private ComboBox<TradeCurrency> preferredTradeCurrencyComboBox;
private CheckBox useAnimationsCheckBox, useEffectsCheckBox, showPlaceOfferConfirmationCheckBox, showTakeOfferConfirmationCheckBox,
private CheckBox useAnimationsCheckBox, useEffectsCheckBox, showNotificationsCheckBox, showInstructionsCheckBox,
autoSelectArbitratorsCheckBox;
private int gridRow = 0;
//private InputTextField transactionFeeInputTextField;
private ChangeListener<Boolean> transactionFeeFocusedListener;
private Preferences preferences;
@Inject
public PreferencesView(PreferencesViewModel model) {
public PreferencesView(PreferencesViewModel model, Preferences preferences) {
super(model);
this.preferences = preferences;
}
@Override
public void initialize() {
addTitledGroupBg(root, gridRow, 4, "Preferences");
tradeCurrencyComboBox = addLabelComboBox(root, gridRow, "Preferred currency:", Layout.FIRST_ROW_DISTANCE).second;
preferredTradeCurrencyComboBox = addLabelComboBox(root, gridRow, "Preferred currency:", Layout.FIRST_ROW_DISTANCE).second;
languageComboBox = addLabelComboBox(root, ++gridRow, "Language:").second;
// btcDenominationComboBox = addLabelComboBox(root, ++gridRow, "Bitcoin denomination:").second;
blockExplorerComboBox = addLabelComboBox(root, ++gridRow, "Bitcoin block explorer:").second;
blockChainExplorerComboBox = addLabelComboBox(root, ++gridRow, "Bitcoin block explorer:").second;
autoSelectArbitratorsCheckBox = addLabelCheckBox(root, ++gridRow, "Auto select arbitrators by language:", "").second;
// TODO need a bit extra work to separate trade and non trade tx fees before it can be used
/*transactionFeeInputTextField = addLabelInputTextField(root, ++gridRow, "Transaction fee (satoshi/byte):").second;
transactionFeeFocusedListener = (o, oldValue, newValue) -> {
@ -71,8 +74,8 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
addTitledGroupBg(root, ++gridRow, 4, "Display options", Layout.GROUP_DISTANCE);
useAnimationsCheckBox = addLabelCheckBox(root, gridRow, "Use animations:", "", Layout.FIRST_ROW_AND_GROUP_DISTANCE).second;
useEffectsCheckBox = addLabelCheckBox(root, ++gridRow, "Use effects:", "").second;
showPlaceOfferConfirmationCheckBox = addLabelCheckBox(root, ++gridRow, "Show confirmation at place offer:", "").second;
showTakeOfferConfirmationCheckBox = addLabelCheckBox(root, ++gridRow, "Show confirmation at take offer:", "").second;
showNotificationsCheckBox = addLabelCheckBox(root, ++gridRow, "Show notifications:", "").second;
showInstructionsCheckBox = addLabelCheckBox(root, ++gridRow, "Show instruction popups:", "").second;
}
@Override
@ -82,9 +85,9 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
btcDenominationComboBox.getSelectionModel().select(model.getBtcDenomination());
btcDenominationComboBox.setOnAction(e -> model.onSelectBtcDenomination(btcDenominationComboBox.getSelectionModel().getSelectedItem()));*/
tradeCurrencyComboBox.setItems(model.tradeCurrencies);
tradeCurrencyComboBox.getSelectionModel().select(model.getTradeCurrency());
tradeCurrencyComboBox.setConverter(new StringConverter<TradeCurrency>() {
preferredTradeCurrencyComboBox.setItems(model.tradeCurrencies);
preferredTradeCurrencyComboBox.getSelectionModel().select(preferences.getPreferredTradeCurrency());
preferredTradeCurrencyComboBox.setConverter(new StringConverter<TradeCurrency>() {
@Override
public String toString(TradeCurrency tradeCurrency) {
return tradeCurrency.getNameAndCode();
@ -95,7 +98,7 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
return null;
}
});
tradeCurrencyComboBox.setOnAction(e -> model.onSelectTradeCurrency(tradeCurrencyComboBox.getSelectionModel().getSelectedItem()));
preferredTradeCurrencyComboBox.setOnAction(e -> preferences.setPreferredTradeCurrency(preferredTradeCurrencyComboBox.getSelectionModel().getSelectedItem()));
languageComboBox.setItems(model.languageCodes);
languageComboBox.getSelectionModel().select(model.getLanguageCode());
@ -113,9 +116,9 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
languageComboBox.setOnAction(e -> model.onSelectLanguageCode(languageComboBox.getSelectionModel().getSelectedItem()));
blockExplorerComboBox.setItems(model.blockExplorers);
blockExplorerComboBox.getSelectionModel().select(model.getBlockExplorer());
blockExplorerComboBox.setConverter(new StringConverter<BlockChainExplorer>() {
blockChainExplorerComboBox.setItems(model.blockExplorers);
blockChainExplorerComboBox.getSelectionModel().select(preferences.getBlockChainExplorer());
blockChainExplorerComboBox.setConverter(new StringConverter<BlockChainExplorer>() {
@Override
public String toString(BlockChainExplorer blockChainExplorer) {
return blockChainExplorer.name;
@ -126,39 +129,39 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
return null;
}
});
blockExplorerComboBox.setOnAction(e -> model.onSelectBlockExplorer(blockExplorerComboBox.getSelectionModel().getSelectedItem()));
blockChainExplorerComboBox.setOnAction(e -> preferences.setBlockChainExplorer(blockChainExplorerComboBox.getSelectionModel().getSelectedItem()));
// transactionFeeInputTextField.textProperty().bindBidirectional(model.transactionFeePerByte);
// transactionFeeInputTextField.focusedProperty().addListener(transactionFeeFocusedListener);
useAnimationsCheckBox.setSelected(model.getUseAnimations());
useAnimationsCheckBox.setOnAction(e -> model.onSelectUseAnimations(useAnimationsCheckBox.isSelected()));
useAnimationsCheckBox.setSelected(preferences.getUseAnimations());
useAnimationsCheckBox.setOnAction(e -> preferences.setUseAnimations(useAnimationsCheckBox.isSelected()));
useEffectsCheckBox.setSelected(model.getUseEffects());
useEffectsCheckBox.setOnAction(e -> model.onSelectUseEffects(useEffectsCheckBox.isSelected()));
useEffectsCheckBox.setSelected(preferences.getUseEffects());
useEffectsCheckBox.setOnAction(e -> preferences.setUseEffects(useEffectsCheckBox.isSelected()));
showPlaceOfferConfirmationCheckBox.setSelected(model.getShowPlaceOfferConfirmation());
showPlaceOfferConfirmationCheckBox.setOnAction(e -> model.onSelectShowPlaceOfferConfirmation(showPlaceOfferConfirmationCheckBox.isSelected()));
showNotificationsCheckBox.setSelected(preferences.getShowNotifications());
showNotificationsCheckBox.setOnAction(e -> preferences.setShowNotifications(showNotificationsCheckBox.isSelected()));
showTakeOfferConfirmationCheckBox.setSelected(model.getShowTakeOfferConfirmation());
showTakeOfferConfirmationCheckBox.setOnAction(e -> model.onSelectShowTakeOfferConfirmation(showTakeOfferConfirmationCheckBox.isSelected()));
showInstructionsCheckBox.setSelected(preferences.getShowInstructions());
showInstructionsCheckBox.setOnAction(e -> preferences.setShowInstructions(showInstructionsCheckBox.isSelected()));
autoSelectArbitratorsCheckBox.setSelected(model.getAutoSelectArbitrators());
autoSelectArbitratorsCheckBox.setOnAction(e -> model.onSelectAutoSelectArbitratorsCheckBox(autoSelectArbitratorsCheckBox.isSelected()));
autoSelectArbitratorsCheckBox.setSelected(preferences.getAutoSelectArbitrators());
autoSelectArbitratorsCheckBox.setOnAction(e -> preferences.setAutoSelectArbitrators(autoSelectArbitratorsCheckBox.isSelected()));
}
@Override
protected void deactivate() {
//btcDenominationComboBox.setOnAction(null);
languageComboBox.setOnAction(null);
tradeCurrencyComboBox.setOnAction(null);
blockExplorerComboBox.setOnAction(null);
preferredTradeCurrencyComboBox.setOnAction(null);
blockChainExplorerComboBox.setOnAction(null);
showNotificationsCheckBox.setOnAction(null);
showInstructionsCheckBox.setOnAction(null);
// transactionFeeInputTextField.textProperty().unbind();
/// transactionFeeInputTextField.focusedProperty().removeListener(transactionFeeFocusedListener);
useAnimationsCheckBox.setOnAction(null);
useEffectsCheckBox.setOnAction(null);
showPlaceOfferConfirmationCheckBox.setOnAction(null);
showTakeOfferConfirmationCheckBox.setOnAction(null);
autoSelectArbitratorsCheckBox.setOnAction(null);
}
}

View file

@ -65,42 +65,11 @@ class PreferencesViewModel extends ActivatableViewModel {
protected void deactivate() {
}
///////////////////////////////////////////////////////////////////////////////////////////
// UI actions
///////////////////////////////////////////////////////////////////////////////////////////
public void onSelectBtcDenomination(String selectedItem) {
preferences.setBtcDenomination(selectedItem);
}
public void onSelectUseEffects(boolean selected) {
preferences.setUseEffects(selected);
}
public void onSelectUseAnimations(boolean selected) {
preferences.setUseAnimations(selected);
}
public void onSelectShowPlaceOfferConfirmation(boolean selected) {
preferences.setShowPlaceOfferConfirmation(selected);
}
public void onSelectShowTakeOfferConfirmation(boolean selected) {
preferences.setShowTakeOfferConfirmation(selected);
}
public void onSelectAutoSelectArbitratorsCheckBox(boolean selected) {
preferences.setAutoSelectArbitrators(selected);
}
public void onSelectBlockExplorer(BlockChainExplorer selectedItem) {
preferences.setBlockChainExplorer(selectedItem);
}
public void onSelectTradeCurrency(TradeCurrency selectedItem) {
preferences.setPreferredTradeCurrency(selectedItem);
}
public void onSelectLanguageCode(String code) {
preferences.setPreferredLocale(new Locale(code, preferences.getPreferredLocale().getCountry()));
}
@ -124,40 +93,8 @@ class PreferencesViewModel extends ActivatableViewModel {
// Getters
///////////////////////////////////////////////////////////////////////////////////////////
public String getBtcDenomination() {
return preferences.getBtcDenomination();
}
public boolean getUseAnimations() {
return preferences.getUseAnimations();
}
public boolean getUseEffects() {
return preferences.getUseEffects();
}
public boolean getShowPlaceOfferConfirmation() {
return preferences.getShowPlaceOfferConfirmation();
}
public boolean getShowTakeOfferConfirmation() {
return preferences.getShowTakeOfferConfirmation();
}
public boolean getAutoSelectArbitrators() {
return preferences.getAutoSelectArbitrators();
}
public BlockChainExplorer getBlockExplorer() {
return preferences.getBlockChainExplorer();
}
public String getLanguageCode() {
return preferences.getPreferredLocale().getLanguage();
}
public TradeCurrency getTradeCurrency() {
return preferences.getPreferredTradeCurrency();
}
}