diff --git a/common/src/main/proto/pb.proto b/common/src/main/proto/pb.proto index 11e8c230f8..0f911a249f 100644 --- a/common/src/main/proto/pb.proto +++ b/common/src/main/proto/pb.proto @@ -1257,6 +1257,7 @@ message PreferencesPayload { bool use_animations = 31; PaymentAccount selectedPayment_account_for_createOffer = 32; bool pay_fee_in_Btc = 33; + repeated string bridge_addresses = 34; } diff --git a/core/src/main/java/io/bisq/core/app/AppSetupWithP2P.java b/core/src/main/java/io/bisq/core/app/AppSetupWithP2P.java index 22849645f8..294fb618df 100644 --- a/core/src/main/java/io/bisq/core/app/AppSetupWithP2P.java +++ b/core/src/main/java/io/bisq/core/app/AppSetupWithP2P.java @@ -167,6 +167,11 @@ public class AppSetupWithP2P extends AppSetup { public void onSetupFailed(Throwable throwable) { log.error(throwable.toString()); } + + @Override + public void onRequestCustomBridges(Runnable resultHandler) { + + } }); return p2pNetworkInitialized; diff --git a/core/src/main/java/io/bisq/core/trade/statistics/TradeStatisticsMigrationTool.java b/core/src/main/java/io/bisq/core/trade/statistics/TradeStatisticsMigrationTool.java index a1a1742694..b5e0865456 100644 --- a/core/src/main/java/io/bisq/core/trade/statistics/TradeStatisticsMigrationTool.java +++ b/core/src/main/java/io/bisq/core/trade/statistics/TradeStatisticsMigrationTool.java @@ -112,6 +112,11 @@ public class TradeStatisticsMigrationTool { @Override public void onSetupFailed(Throwable throwable) { } + + @Override + public void onRequestCustomBridges(Runnable resultHandler) { + + } }); } } diff --git a/core/src/main/java/io/bisq/core/user/Preferences.java b/core/src/main/java/io/bisq/core/user/Preferences.java index 966ccdf32b..f707bee9cc 100644 --- a/core/src/main/java/io/bisq/core/user/Preferences.java +++ b/core/src/main/java/io/bisq/core/user/Preferences.java @@ -10,6 +10,7 @@ import io.bisq.core.btc.BaseCurrencyNetwork; import io.bisq.core.btc.BtcOptionKeys; import io.bisq.core.btc.Restrictions; import io.bisq.core.payment.PaymentAccount; +import io.bisq.network.BridgeProvider; import javafx.beans.property.BooleanProperty; import javafx.beans.property.LongProperty; import javafx.beans.property.SimpleBooleanProperty; @@ -175,6 +176,7 @@ public final class Preferences implements PersistedDataHost { setPreferredTradeCurrency(preferredTradeCurrency); setFiatCurrencies(prefPayload.getFiatCurrencies()); setCryptoCurrencies(prefPayload.getCryptoCurrencies()); + setBridgeAddresses(prefPayload.getBridgeAddresses()); } else { prefPayload = new PreferencesPayload(); @@ -474,6 +476,13 @@ public final class Preferences implements PersistedDataHost { storage.queueUpForSave(prefPayload, 1); } + public void setBridgeAddresses(List bridgeAddresses) { + prefPayload.setBridgeAddresses(bridgeAddresses); + BridgeProvider.setBridges(bridgeAddresses); + // We call that before shutdown so we dont want a delay here + storage.queueUpForSave(prefPayload, 1); + } + // Only used from PB but keep it explicit as maybe it get used from the client and then we want to persist public void setPeerTagMap(Map peerTagMap) { prefPayload.setPeerTagMap(peerTagMap); @@ -650,5 +659,7 @@ public final class Preferences implements PersistedDataHost { void setDontShowAgainMap(Map dontShowAgainMap); void setPeerTagMap(Map peerTagMap); + + void setBridgeAddresses(List bridgeAddresses); } } diff --git a/core/src/main/java/io/bisq/core/user/PreferencesPayload.java b/core/src/main/java/io/bisq/core/user/PreferencesPayload.java index c61b7aef97..423715272b 100644 --- a/core/src/main/java/io/bisq/core/user/PreferencesPayload.java +++ b/core/src/main/java/io/bisq/core/user/PreferencesPayload.java @@ -12,6 +12,7 @@ import io.bisq.core.btc.Restrictions; import io.bisq.core.payment.PaymentAccount; import io.bisq.core.proto.CoreProtoResolver; import io.bisq.generated.protobuffer.PB; +import lombok.AllArgsConstructor; import lombok.Data; import lombok.extern.slf4j.Slf4j; @@ -21,6 +22,7 @@ import java.util.stream.Collectors; @Slf4j @Data +@AllArgsConstructor public final class PreferencesPayload implements PersistableEnvelope { private String userLanguage; private Country userCountry; @@ -64,6 +66,8 @@ public final class PreferencesPayload implements PersistableEnvelope { @Nullable private PaymentAccount selectedPaymentAccountForCreateOffer; private boolean payFeeInBtc = true; + @Nullable + private List bridgeAddresses = new ArrayList<>(); /////////////////////////////////////////////////////////////////////////////////////////// @@ -78,74 +82,6 @@ public final class PreferencesPayload implements PersistableEnvelope { // PROTO BUFFER /////////////////////////////////////////////////////////////////////////////////////////// - private PreferencesPayload(String userLanguage, - Country userCountry, - List fiatCurrencies, - List cryptoCurrencies, - BlockChainExplorer blockChainExplorerMainNet, - BlockChainExplorer blockChainExplorerTestNet, - BlockChainExplorer bsqBlockChainExplorer, - @Nullable String backupDirectory, - boolean autoSelectArbitrators, - Map dontShowAgainMap, - boolean tacAccepted, - boolean useTorForBitcoinJ, - boolean showOwnOffersInOfferBook, - @Nullable TradeCurrency preferredTradeCurrency, - long withdrawalTxFeeInBytes, - boolean useCustomWithdrawalTxFee, - double maxPriceDistanceInPercent, - @Nullable String offerBookChartScreenCurrencyCode, - @Nullable String tradeChartsScreenCurrencyCode, - @Nullable String buyScreenCurrencyCode, - @Nullable String sellScreenCurrencyCode, - int tradeStatisticsTickUnitIndex, - boolean resyncSpvRequested, - boolean sortMarketCurrenciesNumerically, - boolean usePercentageBasedPrice, - Map peerTagMap, - String bitcoinNodes, - List ignoreTradersList, - String directoryChooserPath, - long buyerSecurityDepositAsLong, - boolean useAnimations, - @Nullable PaymentAccount selectedPaymentAccountForCreateOffer, - boolean payFeeInBtc) { - this.userLanguage = userLanguage; - this.userCountry = userCountry; - this.fiatCurrencies = fiatCurrencies; - this.cryptoCurrencies = cryptoCurrencies; - this.blockChainExplorerMainNet = blockChainExplorerMainNet; - this.blockChainExplorerTestNet = blockChainExplorerTestNet; - this.bsqBlockChainExplorer = bsqBlockChainExplorer; - this.backupDirectory = backupDirectory; - this.autoSelectArbitrators = autoSelectArbitrators; - this.dontShowAgainMap = dontShowAgainMap; - this.tacAccepted = tacAccepted; - this.useTorForBitcoinJ = useTorForBitcoinJ; - this.showOwnOffersInOfferBook = showOwnOffersInOfferBook; - this.preferredTradeCurrency = preferredTradeCurrency; - this.withdrawalTxFeeInBytes = withdrawalTxFeeInBytes; - this.useCustomWithdrawalTxFee = useCustomWithdrawalTxFee; - this.maxPriceDistanceInPercent = maxPriceDistanceInPercent; - this.offerBookChartScreenCurrencyCode = offerBookChartScreenCurrencyCode; - this.tradeChartsScreenCurrencyCode = tradeChartsScreenCurrencyCode; - this.buyScreenCurrencyCode = buyScreenCurrencyCode; - this.sellScreenCurrencyCode = sellScreenCurrencyCode; - this.tradeStatisticsTickUnitIndex = tradeStatisticsTickUnitIndex; - this.resyncSpvRequested = resyncSpvRequested; - this.sortMarketCurrenciesNumerically = sortMarketCurrenciesNumerically; - this.usePercentageBasedPrice = usePercentageBasedPrice; - this.peerTagMap = peerTagMap; - this.bitcoinNodes = bitcoinNodes; - this.ignoreTradersList = ignoreTradersList; - this.directoryChooserPath = directoryChooserPath; - this.buyerSecurityDepositAsLong = buyerSecurityDepositAsLong; - this.useAnimations = useAnimations; - this.selectedPaymentAccountForCreateOffer = selectedPaymentAccountForCreateOffer; - this.payFeeInBtc = payFeeInBtc; - } - @Override public Message toProtoMessage() { PB.PreferencesPayload.Builder builder = PB.PreferencesPayload.newBuilder() @@ -178,8 +114,8 @@ public final class PreferencesPayload implements PersistableEnvelope { .setDirectoryChooserPath(directoryChooserPath) .setBuyerSecurityDepositAsLong(buyerSecurityDepositAsLong) .setUseAnimations(useAnimations) - .setPayFeeInBtc(payFeeInBtc); - + .setPayFeeInBtc(payFeeInBtc) + .addAllBridgeAddresses(bridgeAddresses); Optional.ofNullable(backupDirectory).ifPresent(builder::setBackupDirectory); Optional.ofNullable(preferredTradeCurrency).ifPresent(e -> builder.setPreferredTradeCurrency((PB.TradeCurrency) e.toProtoMessage())); Optional.ofNullable(offerBookChartScreenCurrencyCode).ifPresent(builder::setOfferBookChartScreenCurrencyCode); @@ -188,7 +124,6 @@ public final class PreferencesPayload implements PersistableEnvelope { Optional.ofNullable(sellScreenCurrencyCode).ifPresent(builder::setSellScreenCurrencyCode); Optional.ofNullable(selectedPaymentAccountForCreateOffer).ifPresent( account -> builder.setSelectedPaymentAccountForCreateOffer(selectedPaymentAccountForCreateOffer.toProtoMessage())); - return PB.PersistableEnvelope.newBuilder().setPreferencesPayload(builder).build(); } @@ -199,44 +134,45 @@ public final class PreferencesPayload implements PersistableEnvelope { paymentAccount = PaymentAccount.fromProto(proto.getSelectedPaymentAccountForCreateOffer(), coreProtoResolver); return new PreferencesPayload( - proto.getUserLanguage(), - Country.fromProto(userCountry), - proto.getFiatCurrenciesList().isEmpty() ? new ArrayList<>() : - new ArrayList<>(proto.getFiatCurrenciesList().stream() - .map(FiatCurrency::fromProto) - .collect(Collectors.toList())), - proto.getCryptoCurrenciesList().isEmpty() ? new ArrayList<>() : - new ArrayList<>(proto.getCryptoCurrenciesList().stream() - .map(CryptoCurrency::fromProto) - .collect(Collectors.toList())), - BlockChainExplorer.fromProto(proto.getBlockChainExplorerMainNet()), - BlockChainExplorer.fromProto(proto.getBlockChainExplorerTestNet()), - BlockChainExplorer.fromProto(proto.getBsqBlockChainExplorer()), - ProtoUtil.stringOrNullFromProto(proto.getBackupDirectory()), - proto.getAutoSelectArbitrators(), - Maps.newHashMap(proto.getDontShowAgainMapMap()), - proto.getTacAccepted(), - proto.getUseTorForBitcoinJ(), - proto.getShowOwnOffersInOfferBook(), - proto.hasPreferredTradeCurrency() ? TradeCurrency.fromProto(proto.getPreferredTradeCurrency()) : null, - proto.getWithdrawalTxFeeInBytes(), - proto.getUseCustomWithdrawalTxFee(), - proto.getMaxPriceDistanceInPercent(), - ProtoUtil.stringOrNullFromProto(proto.getOfferBookChartScreenCurrencyCode()), - ProtoUtil.stringOrNullFromProto(proto.getTradeChartsScreenCurrencyCode()), - ProtoUtil.stringOrNullFromProto(proto.getBuyScreenCurrencyCode()), - ProtoUtil.stringOrNullFromProto(proto.getSellScreenCurrencyCode()), - proto.getTradeStatisticsTickUnitIndex(), - proto.getResyncSpvRequested(), - proto.getSortMarketCurrenciesNumerically(), - proto.getUsePercentageBasedPrice(), - Maps.newHashMap(proto.getPeerTagMapMap()), - proto.getBitcoinNodes(), - proto.getIgnoreTradersListList(), - proto.getDirectoryChooserPath(), - proto.getBuyerSecurityDepositAsLong(), - proto.getUseAnimations(), - paymentAccount, - proto.getPayFeeInBtc()); + proto.getUserLanguage(), + Country.fromProto(userCountry), + proto.getFiatCurrenciesList().isEmpty() ? new ArrayList<>() : + new ArrayList<>(proto.getFiatCurrenciesList().stream() + .map(FiatCurrency::fromProto) + .collect(Collectors.toList())), + proto.getCryptoCurrenciesList().isEmpty() ? new ArrayList<>() : + new ArrayList<>(proto.getCryptoCurrenciesList().stream() + .map(CryptoCurrency::fromProto) + .collect(Collectors.toList())), + BlockChainExplorer.fromProto(proto.getBlockChainExplorerMainNet()), + BlockChainExplorer.fromProto(proto.getBlockChainExplorerTestNet()), + BlockChainExplorer.fromProto(proto.getBsqBlockChainExplorer()), + ProtoUtil.stringOrNullFromProto(proto.getBackupDirectory()), + proto.getAutoSelectArbitrators(), + Maps.newHashMap(proto.getDontShowAgainMapMap()), + proto.getTacAccepted(), + proto.getUseTorForBitcoinJ(), + proto.getShowOwnOffersInOfferBook(), + proto.hasPreferredTradeCurrency() ? TradeCurrency.fromProto(proto.getPreferredTradeCurrency()) : null, + proto.getWithdrawalTxFeeInBytes(), + proto.getUseCustomWithdrawalTxFee(), + proto.getMaxPriceDistanceInPercent(), + ProtoUtil.stringOrNullFromProto(proto.getOfferBookChartScreenCurrencyCode()), + ProtoUtil.stringOrNullFromProto(proto.getTradeChartsScreenCurrencyCode()), + ProtoUtil.stringOrNullFromProto(proto.getBuyScreenCurrencyCode()), + ProtoUtil.stringOrNullFromProto(proto.getSellScreenCurrencyCode()), + proto.getTradeStatisticsTickUnitIndex(), + proto.getResyncSpvRequested(), + proto.getSortMarketCurrenciesNumerically(), + proto.getUsePercentageBasedPrice(), + Maps.newHashMap(proto.getPeerTagMapMap()), + proto.getBitcoinNodes(), + proto.getIgnoreTradersListList(), + proto.getDirectoryChooserPath(), + proto.getBuyerSecurityDepositAsLong(), + proto.getUseAnimations(), + paymentAccount, + proto.getPayFeeInBtc(), + proto.getBridgeAddressesList()); } } diff --git a/gui/src/main/java/io/bisq/gui/main/MainViewModel.java b/gui/src/main/java/io/bisq/gui/main/MainViewModel.java index 2eadebe0aa..2668e54394 100644 --- a/gui/src/main/java/io/bisq/gui/main/MainViewModel.java +++ b/gui/src/main/java/io/bisq/gui/main/MainViewModel.java @@ -72,12 +72,14 @@ import io.bisq.gui.components.BalanceWithConfirmationTextField; import io.bisq.gui.components.TxIdTextField; import io.bisq.gui.main.overlays.notifications.NotificationCenter; import io.bisq.gui.main.overlays.popups.Popup; +import io.bisq.gui.main.overlays.windows.AddBridgeEntriesWindow; import io.bisq.gui.main.overlays.windows.DisplayAlertMessageWindow; import io.bisq.gui.main.overlays.windows.TacWindow; import io.bisq.gui.main.overlays.windows.WalletPasswordWindow; import io.bisq.gui.main.overlays.windows.downloadupdate.DisplayUpdateDownloadWindow; import io.bisq.gui.util.BSFormatter; import io.bisq.gui.util.GUIUtil; +import io.bisq.gui.util.Transitions; import io.bisq.network.crypto.DecryptedDataTuple; import io.bisq.network.crypto.EncryptionService; import io.bisq.network.p2p.BootstrapListener; @@ -251,7 +253,7 @@ public class MainViewModel implements ViewModel { this.formatter = formatter; btcNetworkAsString = Res.get(BisqEnvironment.getBaseCurrencyNetwork().name()) + - (preferences.getUseTorForBitcoinJ() ? (" " + Res.get("mainView.footer.usingTor")) : ""); + (preferences.getUseTorForBitcoinJ() ? (" " + Res.get("mainView.footer.usingTor")) : ""); TxIdTextField.setPreferences(preferences); @@ -284,10 +286,10 @@ public class MainViewModel implements ViewModel { private void readMapsFromResources() { readMapsFromResourcesBinding = EasyBind.combine(SetupUtils.readPersistableNetworkPayloadMapFromResources(p2PService), - SetupUtils.readEntryMapFromResources(p2PService), - (result1, result2) -> { - return result1 && result2; - }); + SetupUtils.readEntryMapFromResources(p2PService), + (result1, result2) -> { + return result1 && result2; + }); readMapsFromResourcesBindingSubscription = readMapsFromResourcesBinding.subscribe((observable, oldValue, newValue) -> { if (newValue) startBasicServices(); @@ -324,12 +326,12 @@ public class MainViewModel implements ViewModel { // need to store it to not get garbage collected allServicesDone = EasyBind.combine(walletInitialized, p2pNetWorkReady, - (a, b) -> { - log.debug("\nwalletInitialized={}\n" + - "p2pNetWorkReady={}", - a, b); - return a && b; - }); + (a, b) -> { + log.debug("\nwalletInitialized={}\n" + + "p2pNetWorkReady={}", + a, b); + return a && b; + }); allServicesDone.subscribe((observable, oldValue, newValue) -> { if (newValue) { startupTimeout.stop(); @@ -354,8 +356,8 @@ public class MainViewModel implements ViewModel { } startupTimeoutPopup = new Popup<>(); startupTimeoutPopup.warning(Res.get("popup.warning.startupFailed.timeout", details)) - .useShutDownButton() - .show(); + .useShutDownButton() + .show(); } @@ -372,22 +374,22 @@ public class MainViewModel implements ViewModel { BooleanProperty initialP2PNetworkDataReceived = new SimpleBooleanProperty(); p2PNetworkInfoBinding = EasyBind.combine(bootstrapState, bootstrapWarning, p2PService.getNumConnectedPeers(), hiddenServicePublished, initialP2PNetworkDataReceived, - (state, warning, numPeers, hiddenService, dataReceived) -> { - String result = ""; - int peers = (int) numPeers; - if (warning != null && peers == 0) { - result = warning; - } else { - String p2pInfo = Res.get("mainView.footer.p2pInfo", numPeers); - if (dataReceived && hiddenService) { - result = p2pInfo; - } else if (peers == 0) - result = state; - else - result = state + " / " + p2pInfo; - } - return result; - }); + (state, warning, numPeers, hiddenService, dataReceived) -> { + String result = ""; + int peers = (int) numPeers; + if (warning != null && peers == 0) { + result = warning; + } else { + String p2pInfo = Res.get("mainView.footer.p2pInfo", numPeers); + if (dataReceived && hiddenService) { + result = p2pInfo; + } else if (peers == 0) + result = state; + else + result = state + " / " + p2pInfo; + } + return result; + }); p2PNetworkInfoBinding.subscribe((observable, oldValue, newValue) -> { p2PNetworkInfo.set(newValue); }); @@ -404,7 +406,7 @@ public class MainViewModel implements ViewModel { // We only check at seed nodes as they are running the latest version // Other disconnects might be caused by peers running an older version if (connection.getPeerType() == Connection.PeerType.SEED_NODE && - closeConnectionReason == CloseConnectionReason.RULE_VIOLATION) { + closeConnectionReason == CloseConnectionReason.RULE_VIOLATION) { log.warn("RULE_VIOLATION onDisconnect closeConnectionReason=" + closeConnectionReason); log.warn("RULE_VIOLATION onDisconnect connection=" + connection); } @@ -485,6 +487,13 @@ public class MainViewModel implements ViewModel { bootstrapWarning.set(Res.get("mainView.bootstrapWarning.bootstrappingToP2PFailed")); p2pNetworkLabelId.set("splash-error-state-msg"); } + + @Override + public void onRequestCustomBridges(Runnable resultHandler) { + AddBridgeEntriesWindow addBridgeEntriesWindow = new AddBridgeEntriesWindow(preferences) + .onAction(resultHandler::run); + UserThread.execute(addBridgeEntriesWindow::show); + } }); return p2pNetworkInitialized; @@ -495,99 +504,99 @@ public class MainViewModel implements ViewModel { ObjectProperty walletServiceException = new SimpleObjectProperty<>(); btcInfoBinding = EasyBind.combine(walletsSetup.downloadPercentageProperty(), walletsSetup.numPeersProperty(), walletServiceException, - (downloadPercentage, numPeers, exception) -> { - String result = ""; - if (exception == null) { - double percentage = (double) downloadPercentage; - int peers = (int) numPeers; - btcSyncProgress.set(percentage); - if (percentage == 1) { - result = Res.get("mainView.footer.btcInfo", - peers, - Res.get("mainView.footer.btcInfo.synchronizedWith"), - btcNetworkAsString); - btcSplashSyncIconId.set("image-connection-synced"); + (downloadPercentage, numPeers, exception) -> { + String result = ""; + if (exception == null) { + double percentage = (double) downloadPercentage; + int peers = (int) numPeers; + btcSyncProgress.set(percentage); + if (percentage == 1) { + result = Res.get("mainView.footer.btcInfo", + peers, + Res.get("mainView.footer.btcInfo.synchronizedWith"), + btcNetworkAsString); + btcSplashSyncIconId.set("image-connection-synced"); - if (allBasicServicesInitialized) - checkForLockedUpFunds(); - } else if (percentage > 0.0) { - result = Res.get("mainView.footer.btcInfo", - peers, - Res.get("mainView.footer.btcInfo.synchronizedWith"), - btcNetworkAsString + ": " + formatter.formatToPercentWithSymbol(percentage)); - } else { - result = Res.get("mainView.footer.btcInfo", - peers, - Res.get("mainView.footer.btcInfo.connectingTo"), - btcNetworkAsString); - } - } else { - result = Res.get("mainView.footer.btcInfo", - numBtcPeers, - Res.get("mainView.footer.btcInfo.connectionFailed"), - btcNetworkAsString); - log.error(exception.getMessage()); - if (exception instanceof TimeoutException) { - walletServiceErrorMsg.set(Res.get("mainView.walletServiceErrorMsg.timeout")); - } else if (exception.getCause() instanceof BlockStoreException) { - if (exception.getCause().getCause() instanceof ChainFileLockedException) { - new Popup<>().warning(Res.get("popup.warning.startupFailed.twoInstances")) - .useShutDownButton() - .show(); + if (allBasicServicesInitialized) + checkForLockedUpFunds(); + } else if (percentage > 0.0) { + result = Res.get("mainView.footer.btcInfo", + peers, + Res.get("mainView.footer.btcInfo.synchronizedWith"), + btcNetworkAsString + ": " + formatter.formatToPercentWithSymbol(percentage)); } else { - new Popup<>().warning(Res.get("error.spvFileCorrupted", - exception.getMessage())) - .actionButtonText(Res.get("settings.net.reSyncSPVChainButton")) - .onAction(() -> { - if (walletsSetup.reSyncSPVChain()) - new Popup<>().feedback(Res.get("settings.net.reSyncSPVSuccess")) - .useShutDownButton().show(); - else - new Popup<>().error(Res.get("settings.net.reSyncSPVFailed")).show(); - }) - .show(); + result = Res.get("mainView.footer.btcInfo", + peers, + Res.get("mainView.footer.btcInfo.connectingTo"), + btcNetworkAsString); } } else { - walletServiceErrorMsg.set(Res.get("mainView.walletServiceErrorMsg.connectionError", exception.toString())); + result = Res.get("mainView.footer.btcInfo", + numBtcPeers, + Res.get("mainView.footer.btcInfo.connectionFailed"), + btcNetworkAsString); + log.error(exception.getMessage()); + if (exception instanceof TimeoutException) { + walletServiceErrorMsg.set(Res.get("mainView.walletServiceErrorMsg.timeout")); + } else if (exception.getCause() instanceof BlockStoreException) { + if (exception.getCause().getCause() instanceof ChainFileLockedException) { + new Popup<>().warning(Res.get("popup.warning.startupFailed.twoInstances")) + .useShutDownButton() + .show(); + } else { + new Popup<>().warning(Res.get("error.spvFileCorrupted", + exception.getMessage())) + .actionButtonText(Res.get("settings.net.reSyncSPVChainButton")) + .onAction(() -> { + if (walletsSetup.reSyncSPVChain()) + new Popup<>().feedback(Res.get("settings.net.reSyncSPVSuccess")) + .useShutDownButton().show(); + else + new Popup<>().error(Res.get("settings.net.reSyncSPVFailed")).show(); + }) + .show(); + } + } else { + walletServiceErrorMsg.set(Res.get("mainView.walletServiceErrorMsg.connectionError", exception.toString())); + } } - } - return result; + return result; - }); + }); btcInfoBinding.subscribe((observable, oldValue, newValue) -> { btcInfo.set(newValue); }); walletsSetup.initialize(null, - () -> { - log.debug("walletsSetup.onInitialized"); - numBtcPeers = walletsSetup.numPeersProperty().get(); + () -> { + log.debug("walletsSetup.onInitialized"); + numBtcPeers = walletsSetup.numPeersProperty().get(); - // We only check one as we apply encryption to all or none - if (walletsManager.areWalletsEncrypted()) { - if (p2pNetWorkReady.get()) - splashP2PNetworkAnimationVisible.set(false); + // We only check one as we apply encryption to all or none + if (walletsManager.areWalletsEncrypted()) { + if (p2pNetWorkReady.get()) + splashP2PNetworkAnimationVisible.set(false); - walletPasswordWindow - .onAesKey(aesKey -> { - walletsManager.setAesKey(aesKey); - if (preferences.isResyncSpvRequested()) { - showFirstPopupIfResyncSPVRequested(); - } else { - walletInitialized.set(true); - } - }) - .hideCloseButton() - .show(); - } else { - if (preferences.isResyncSpvRequested()) { - showFirstPopupIfResyncSPVRequested(); + walletPasswordWindow + .onAesKey(aesKey -> { + walletsManager.setAesKey(aesKey); + if (preferences.isResyncSpvRequested()) { + showFirstPopupIfResyncSPVRequested(); + } else { + walletInitialized.set(true); + } + }) + .hideCloseButton() + .show(); } else { - walletInitialized.set(true); + if (preferences.isResyncSpvRequested()) { + showFirstPopupIfResyncSPVRequested(); + } else { + walletInitialized.set(true); + } } - } - }, - walletServiceException::set); + }, + walletServiceException::set); } private void onBasicServicesInitialized() { @@ -617,8 +626,8 @@ public class MainViewModel implements ViewModel { applyTradePeriodState(); }); tradeManager.setTakeOfferRequestErrorMessageHandler(errorMessage -> new Popup<>() - .warning(Res.get("popup.error.takeOfferRequestFailed", errorMessage)) - .show()); + .warning(Res.get("popup.error.takeOfferRequestFailed", errorMessage)) + .show()); // walletService btcWalletService.addBalanceListener(new BalanceListener() { @@ -680,9 +689,9 @@ public class MainViewModel implements ViewModel { user.getPaymentAccountsAsObservable().addListener((SetChangeListener) change -> { if (!walletsManager.areWalletsEncrypted() && preferences.showAgain(key) && change.wasAdded()) { new Popup<>().headLine(Res.get("popup.securityRecommendation.headline")) - .information(Res.get("popup.securityRecommendation.msg")) - .dontShowAgainId(key) - .show(); + .information(Res.get("popup.securityRecommendation.msg")) + .dontShowAgainId(key) + .show(); } }); @@ -711,9 +720,9 @@ public class MainViewModel implements ViewModel { firstPopup.hide(); preferences.setResyncSpvRequested(false); new Popup<>().information(Res.get("settings.net.reSyncSPVAfterRestartCompleted")) - .hideCloseButton() - .useShutDownButton() - .show(); + .hideCloseButton() + .useShutDownButton() + .show(); } private void checkIfLocalHostNodeIsRunning() { @@ -725,7 +734,7 @@ public class MainViewModel implements ViewModel { try { socket = new Socket(); socket.connect(new InetSocketAddress(InetAddresses.forString("127.0.0.1"), - BisqEnvironment.getBaseCurrencyNetwork().getParameters().getPort()), 5000); + BisqEnvironment.getBaseCurrencyNetwork().getParameters().getPort()), 5000); log.info("Localhost peer detected."); UserThread.execute(() -> { bisqEnvironment.setBitcoinLocalhostNodeRunning(true); @@ -763,11 +772,11 @@ public class MainViewModel implements ViewModel { // just use any simple dummy msg Ping payload = new Ping(1, 1); SealedAndSigned sealedAndSigned = EncryptionService.encryptHybridWithSignature(payload, - keyRing.getSignatureKeyPair(), keyRing.getPubKeyRing().getEncryptionPubKey()); + keyRing.getSignatureKeyPair(), keyRing.getPubKeyRing().getEncryptionPubKey()); DecryptedDataTuple tuple = encryptionService.decryptHybridWithSignature(sealedAndSigned, keyRing.getEncryptionKeyPair().getPrivate()); if (tuple.getNetworkEnvelope() instanceof Ping && - ((Ping) tuple.getNetworkEnvelope()).getNonce() == payload.getNonce() && - ((Ping) tuple.getNetworkEnvelope()).getLastRoundTripTime() == payload.getLastRoundTripTime()) { + ((Ping) tuple.getNetworkEnvelope()).getNonce() == payload.getNonce() && + ((Ping) tuple.getNetworkEnvelope()).getLastRoundTripTime() == payload.getLastRoundTripTime()) { log.debug("Crypto test succeeded"); if (Security.getProvider("BC") != null) { @@ -783,9 +792,9 @@ public class MainViewModel implements ViewModel { String msg = Res.get("popup.warning.cryptoTestFailed", e.getMessage()); log.error(msg); UserThread.execute(() -> new Popup<>().warning(msg) - .useShutDownButton() - .useReportBugButton() - .show()); + .useShutDownButton() + .useReportBugButton() + .show()); } } }; @@ -794,20 +803,20 @@ public class MainViewModel implements ViewModel { private void checkIfOpenOffersMatchTradeProtocolVersion() { List outDatedOffers = openOfferManager.getObservableList() - .stream() - .filter(e -> e.getOffer().getProtocolVersion() != Version.TRADE_PROTOCOL_VERSION) - .collect(Collectors.toList()); + .stream() + .filter(e -> e.getOffer().getProtocolVersion() != Version.TRADE_PROTOCOL_VERSION) + .collect(Collectors.toList()); if (!outDatedOffers.isEmpty()) { String offers = outDatedOffers.stream() - .map(e -> e.getId() + "\n") - .collect(Collectors.toList()).toString() - .replace("[", "").replace("]", ""); + .map(e -> e.getId() + "\n") + .collect(Collectors.toList()).toString() + .replace("[", "").replace("]", ""); new Popup<>() - .warning(Res.get("popup.warning.oldOffers.msg", offers)) - .actionButtonText(Res.get("popup.warning.oldOffers.buttonText")) - .onAction(() -> openOfferManager.removeOpenOffers(outDatedOffers, null)) - .useShutDownButton() - .show(); + .warning(Res.get("popup.warning.oldOffers.msg", offers)) + .actionButtonText(Res.get("popup.warning.oldOffers.buttonText")) + .onAction(() -> openOfferManager.removeOpenOffers(outDatedOffers, null)) + .useShutDownButton() + .show(); } } @@ -869,9 +878,9 @@ public class MainViewModel implements ViewModel { if (DontShowAgainLookup.showAgain(key)) { DontShowAgainLookup.dontShowAgain(key, true); new Popup<>().warning(Res.get("popup.warning.tradePeriod.halfReached", - trade.getShortId(), - formatter.formatDateTime(maxTradePeriodDate))) - .show(); + trade.getShortId(), + formatter.formatDateTime(maxTradePeriodDate))) + .show(); } break; case TRADE_PERIOD_OVER: @@ -879,9 +888,9 @@ public class MainViewModel implements ViewModel { if (DontShowAgainLookup.showAgain(key)) { DontShowAgainLookup.dontShowAgain(key, true); new Popup<>().warning(Res.get("popup.warning.tradePeriod.ended", - trade.getShortId(), - formatter.formatDateTime(maxTradePeriodDate))) - .show(); + trade.getShortId(), + formatter.formatDateTime(maxTradePeriodDate))) + .show(); } break; } @@ -948,11 +957,11 @@ public class MainViewModel implements ViewModel { private void setupMarketPriceFeed() { priceFeedService.requestPriceFeed(price -> marketPrice.set(formatter.formatMarketPrice(price, priceFeedService.getCurrencyCode())), - (errorMessage, throwable) -> marketPrice.set(Res.get("shared.na"))); + (errorMessage, throwable) -> marketPrice.set(Res.get("shared.na"))); marketPriceBinding = EasyBind.combine( - marketPriceCurrencyCode, marketPrice, - (currencyCode, price) -> formatter.getCurrencyPair(currencyCode) + ": " + price); + marketPriceCurrencyCode, marketPrice, + (currencyCode, price) -> formatter.getCurrencyPair(currencyCode) + ": " + price); marketPriceBinding.subscribe((observable, oldValue, newValue) -> { if (newValue != null && !newValue.equals(oldValue)) { @@ -1016,12 +1025,12 @@ public class MainViewModel implements ViewModel { selectedPriceFeedComboBoxItemProperty.set(itemOptional.get()); else findPriceFeedComboBoxItem(preferences.getPreferredTradeCurrency().getCode()) - .ifPresent(selectedPriceFeedComboBoxItemProperty::set); + .ifPresent(selectedPriceFeedComboBoxItemProperty::set); priceFeedService.setCurrencyCode(item.currencyCode); } else { findPriceFeedComboBoxItem(preferences.getPreferredTradeCurrency().getCode()) - .ifPresent(selectedPriceFeedComboBoxItemProperty::set); + .ifPresent(selectedPriceFeedComboBoxItemProperty::set); } // Need a delay a bit as we get item.isPriceAvailable() set after that call. @@ -1039,15 +1048,15 @@ public class MainViewModel implements ViewModel { private Optional findPriceFeedComboBoxItem(String currencyCode) { return priceFeedComboBoxItems.stream() - .filter(item -> item.currencyCode.equals(currencyCode)) - .findAny(); + .filter(item -> item.currencyCode.equals(currencyCode)) + .findAny(); } private void fillPriceFeedComboBoxItems() { List currencyItems = preferences.getTradeCurrenciesAsObservable() - .stream() - .map(tradeCurrency -> new PriceFeedComboBoxItem(tradeCurrency.getCode())) - .collect(Collectors.toList()); + .stream() + .map(tradeCurrency -> new PriceFeedComboBoxItem(tradeCurrency.getCode())) + .collect(Collectors.toList()); priceFeedComboBoxItems.setAll(currencyItems); } @@ -1066,20 +1075,20 @@ public class MainViewModel implements ViewModel { private void displayPrivateNotification(PrivateNotificationPayload privateNotification) { new Popup<>().headLine(Res.get("popup.privateNotification.headline")) - .attention(privateNotification.getMessage()) - .setHeadlineStyle("-fx-text-fill: -bs-error-red; -fx-font-weight: bold; -fx-font-size: 16;") - .onClose(privateNotificationManager::removePrivateNotification) - .useIUnderstandButton() - .show(); + .attention(privateNotification.getMessage()) + .setHeadlineStyle("-fx-text-fill: -bs-error-red; -fx-font-weight: bold; -fx-font-size: 16;") + .onClose(privateNotificationManager::removePrivateNotification) + .useIUnderstandButton() + .show(); } private void swapPendingOfferFundingEntries() { tradeManager.getAddressEntriesForAvailableBalanceStream() - .filter(addressEntry -> addressEntry.getOfferId() != null) - .forEach(addressEntry -> { - log.debug("swapPendingOfferFundingEntries, offerId={}, OFFER_FUNDING", addressEntry.getOfferId()); - btcWalletService.swapTradeEntryToAvailableEntry(addressEntry.getOfferId(), AddressEntry.Context.OFFER_FUNDING); - }); + .filter(addressEntry -> addressEntry.getOfferId() != null) + .forEach(addressEntry -> { + log.debug("swapPendingOfferFundingEntries, offerId={}, OFFER_FUNDING", addressEntry.getOfferId()); + btcWalletService.swapTradeEntryToAvailableEntry(addressEntry.getOfferId(), AddressEntry.Context.OFFER_FUNDING); + }); } private void updateBalance() { @@ -1094,8 +1103,8 @@ public class MainViewModel implements ViewModel { private void updateAvailableBalance() { Coin totalAvailableBalance = Coin.valueOf(tradeManager.getAddressEntriesForAvailableBalanceStream() - .mapToLong(addressEntry -> btcWalletService.getBalanceForAddress(addressEntry.getAddress()).getValue()) - .sum()); + .mapToLong(addressEntry -> btcWalletService.getBalanceForAddress(addressEntry.getAddress()).getValue()) + .sum()); String value = formatter.formatCoinWithCode(totalAvailableBalance); // If we get full precision the BTC postfix breaks layout so we omit it if (value.length() > 11) @@ -1105,18 +1114,18 @@ public class MainViewModel implements ViewModel { private void updateReservedBalance() { Coin sum = Coin.valueOf(openOfferManager.getObservableList().stream() - .map(openOffer -> { - final Optional addressEntryOptional = btcWalletService.getAddressEntry(openOffer.getId(), AddressEntry.Context.RESERVED_FOR_TRADE); - if (addressEntryOptional.isPresent()) { - Address address = addressEntryOptional.get().getAddress(); - return btcWalletService.getBalanceForAddress(address); - } else { - return null; - } - }) - .filter(e -> e != null) - .mapToLong(Coin::getValue) - .sum()); + .map(openOffer -> { + final Optional addressEntryOptional = btcWalletService.getAddressEntry(openOffer.getId(), AddressEntry.Context.RESERVED_FOR_TRADE); + if (addressEntryOptional.isPresent()) { + Address address = addressEntryOptional.get().getAddress(); + return btcWalletService.getBalanceForAddress(address); + } else { + return null; + } + }) + .filter(e -> e != null) + .mapToLong(Coin::getValue) + .sum()); reservedBalance.set(formatter.formatCoinWithCode(sum)); } @@ -1125,42 +1134,42 @@ public class MainViewModel implements ViewModel { Stream lockedTrades = Stream.concat(closedTradableManager.getLockedTradesStream(), failedTradesManager.getLockedTradesStream()); lockedTrades = Stream.concat(lockedTrades, tradeManager.getLockedTradesStream()); Coin sum = Coin.valueOf(lockedTrades - .mapToLong(trade -> { - final Optional addressEntryOptional = btcWalletService.getAddressEntry(trade.getId(), AddressEntry.Context.MULTI_SIG); - if (addressEntryOptional.isPresent()) - return addressEntryOptional.get().getCoinLockedInMultiSig().getValue(); - else - return 0; - }) - .sum()); + .mapToLong(trade -> { + final Optional addressEntryOptional = btcWalletService.getAddressEntry(trade.getId(), AddressEntry.Context.MULTI_SIG); + if (addressEntryOptional.isPresent()) + return addressEntryOptional.get().getCoinLockedInMultiSig().getValue(); + else + return 0; + }) + .sum()); lockedBalance.set(formatter.formatCoinWithCode(sum)); } private void checkForLockedUpFunds() { Set tradesIdSet = tradeManager.getLockedTradesStream() - .filter(Trade::hasFailed) - .map(Trade::getId) - .collect(Collectors.toSet()); + .filter(Trade::hasFailed) + .map(Trade::getId) + .collect(Collectors.toSet()); tradesIdSet.addAll(failedTradesManager.getLockedTradesStream() - .map(Trade::getId) - .collect(Collectors.toSet())); + .map(Trade::getId) + .collect(Collectors.toSet())); tradesIdSet.addAll(closedTradableManager.getLockedTradesStream() - .map(e -> { - log.warn("We found a closed trade with locked up funds. " + - "That should never happen. trade ID=" + e.getId()); - return e.getId(); - }) - .collect(Collectors.toSet())); + .map(e -> { + log.warn("We found a closed trade with locked up funds. " + + "That should never happen. trade ID=" + e.getId()); + return e.getId(); + }) + .collect(Collectors.toSet())); btcWalletService.getAddressEntriesForTrade().stream() - .filter(e -> tradesIdSet.contains(e.getOfferId()) && e.getContext() == AddressEntry.Context.MULTI_SIG) - .forEach(e -> { - final Coin balance = e.getCoinLockedInMultiSig(); - final String message = Res.get("popup.warning.lockedUpFunds", - formatter.formatCoinWithCode(balance), e.getAddressString(), e.getOfferId()); - log.warn(message); - new Popup<>().warning(message).show(); - }); + .filter(e -> tradesIdSet.contains(e.getOfferId()) && e.getContext() == AddressEntry.Context.MULTI_SIG) + .forEach(e -> { + final Coin balance = e.getCoinLockedInMultiSig(); + final String message = Res.get("popup.warning.lockedUpFunds", + formatter.formatCoinWithCode(balance), e.getAddressString(), e.getOfferId()); + log.warn(message); + new Popup<>().warning(message).show(); + }); } @@ -1177,20 +1186,20 @@ public class MainViewModel implements ViewModel { addedList.stream().forEach(dispute -> { String id = dispute.getId(); Subscription disputeStateSubscription = EasyBind.subscribe(dispute.isClosedProperty(), - isClosed -> { - // We get event before list gets updated, so we execute on next frame - UserThread.execute(() -> { - 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("★"); + isClosed -> { + // We get event before list gets updated, so we execute on next frame + UserThread.execute(() -> { + 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); + showOpenDisputesNotification.set(openDisputes > 0); + }); }); - }); disputeIsClosedSubscriptionsMap.put(id, disputeStateSubscription); }); } @@ -1208,21 +1217,21 @@ public class MainViewModel implements ViewModel { private void removeOffersWithoutAccountAgeWitness() { if (new Date().after(AccountAgeWitnessService.FULL_ACTIVATION)) { openOfferManager.getObservableList().stream() - .filter(e -> CurrencyUtil.isFiatCurrency(e.getOffer().getCurrencyCode())) - .filter(e -> !e.getOffer().getAccountAgeWitnessHashAsHex().isPresent()) - .forEach(e -> { - new Popup<>().warning(Res.get("popup.warning.offerWithoutAccountAgeWitness", e.getId())) - .actionButtonText(Res.get("popup.warning.offerWithoutAccountAgeWitness.confirm")) - .onAction(() -> { - openOfferManager.removeOffer(e.getOffer(), - () -> { - log.info("Offer with ID {} is removed", e.getId()); - }, - log::error); - }) - .hideCloseButton() - .show(); - }); + .filter(e -> CurrencyUtil.isFiatCurrency(e.getOffer().getCurrencyCode())) + .filter(e -> !e.getOffer().getAccountAgeWitnessHashAsHex().isPresent()) + .forEach(e -> { + new Popup<>().warning(Res.get("popup.warning.offerWithoutAccountAgeWitness", e.getId())) + .actionButtonText(Res.get("popup.warning.offerWithoutAccountAgeWitness.confirm")) + .onAction(() -> { + openOfferManager.removeOffer(e.getOffer(), + () -> { + log.info("Offer with ID {} is removed", e.getId()); + }, + log::error); + }) + .hideCloseButton() + .show(); + }); } } diff --git a/gui/src/main/java/io/bisq/gui/main/overlays/windows/AddBridgeEntriesWindow.java b/gui/src/main/java/io/bisq/gui/main/overlays/windows/AddBridgeEntriesWindow.java new file mode 100644 index 0000000000..a6f38ed13e --- /dev/null +++ b/gui/src/main/java/io/bisq/gui/main/overlays/windows/AddBridgeEntriesWindow.java @@ -0,0 +1,173 @@ +/* + * This file is part of bisq. + * + * bisq 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. + * + * bisq 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 bisq. If not, see . + */ + + +/** This file is part of bisq. + * + * bisq 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. + * + * bisq 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 bisq. If not, see . + */ + +package io.bisq.gui.main.overlays.windows; + +import io.bisq.common.util.Tuple2; +import io.bisq.common.util.Utilities; +import io.bisq.core.alert.Alert; +import io.bisq.core.user.Preferences; +import io.bisq.gui.main.overlays.Overlay; +import javafx.geometry.HPos; +import javafx.geometry.Insets; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.TextArea; +import javafx.scene.input.KeyCode; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Pane; +import javafx.scene.layout.Priority; +import lombok.extern.slf4j.Slf4j; + +import javax.inject.Inject; +import java.io.IOException; +import java.net.URI; +import java.util.Arrays; +import java.util.List; + +import static io.bisq.gui.util.FormBuilder.addLabel; +import static io.bisq.gui.util.FormBuilder.addLabelTextArea; + +@Slf4j +public class AddBridgeEntriesWindow extends Overlay { + private TextArea bridgeEntriesTextArea; + private final Preferences preferences; + + @Inject + public AddBridgeEntriesWindow(Preferences preferences) { + this.preferences = preferences; + type = Type.Attention; + } + + /////////////////////////////////////////////////////////////////////////////////////////// + // Public API + /////////////////////////////////////////////////////////////////////////////////////////// + + public void show() { + if (headLine == null) + headLine = "Add Tor bridge entries"; + + width = 900; + createGridPane(); + addHeadLine(); + addSeparator(); + addContent(); + addCloseButton(); + applyStyles(); + display(); + } + + protected void addCloseButton() { + closeButton = new Button(closeButtonText == null ? "Close" : closeButtonText); + closeButton.setOnAction(event -> doClose()); + + if (actionHandlerOptional.isPresent() || actionButtonText != null) { + actionButton = new Button("Save and retry"); + actionButton.setDefaultButton(true); + //TODO app wide focus + //actionButton.requestFocus(); + actionButton.setOnAction(event -> save()); + + Button urlButton = new Button("Open Tor project web page"); + urlButton.setOnAction(event -> { + try { + Utilities.openURI(URI.create("https://bridges.torproject.org/bridges")); + } catch (IOException e) { + e.printStackTrace(); + } + }); + + Pane spacer = new Pane(); + HBox hBox = new HBox(); + hBox.setSpacing(10); + hBox.getChildren().addAll(spacer, closeButton, urlButton, actionButton); + HBox.setHgrow(spacer, Priority.ALWAYS); + + GridPane.setHalignment(hBox, HPos.RIGHT); + GridPane.setRowIndex(hBox, rowIndex); + GridPane.setColumnSpan(hBox, 2); + GridPane.setMargin(hBox, new Insets(buttonDistance, 0, 0, 0)); + gridPane.getChildren().add(hBox); + } else if (!hideCloseButton) { + closeButton.setDefaultButton(true); + GridPane.setHalignment(closeButton, HPos.RIGHT); + GridPane.setMargin(closeButton, new Insets(buttonDistance, 0, 0, 0)); + GridPane.setRowIndex(closeButton, rowIndex); + GridPane.setColumnIndex(closeButton, 1); + gridPane.getChildren().add(closeButton); + } + } + + /////////////////////////////////////////////////////////////////////////////////////////// + // Protected + /////////////////////////////////////////////////////////////////////////////////////////// + + @Override + protected void setupKeyHandler(Scene scene) { + if (!hideCloseButton) { + scene.setOnKeyPressed(e -> { + if (e.getCode() == KeyCode.ESCAPE) { + e.consume(); + doClose(); + } else if (e.getCode() == KeyCode.ENTER) { + e.consume(); + save(); + } + }); + } + } + + private void addContent() { + Label label = addLabel(gridPane, ++rowIndex, "We could not connect to the Tor network.\n" + + "If Tor is blocked at your internet provider, you can try to add Tor bridge address entries from the Tor project:\n" + + "https://bridges.torproject.org/bridges\n\n" + + "Add one address entry in each line.\n"); + GridPane.setColumnIndex(label, 0); + GridPane.setColumnSpan(label, 2); + GridPane.setHalignment(label, HPos.LEFT); + Tuple2 labelTextAreaTuple2 = addLabelTextArea(gridPane, rowIndex, "Bridge entries:", ""); + bridgeEntriesTextArea = labelTextAreaTuple2.second; + } + + private void save() { + if (!bridgeEntriesTextArea.getText().isEmpty()) { + List list = Arrays.asList(bridgeEntriesTextArea.getText().split("\\n")); + preferences.setBridgeAddresses(list); + actionHandlerOptional.ifPresent(Runnable::run); + hide(); + } + } +} \ No newline at end of file diff --git a/gui/src/main/resources/logback.xml b/gui/src/main/resources/logback.xml index 0cb3e0abcf..8860959ab6 100644 --- a/gui/src/main/resources/logback.xml +++ b/gui/src/main/resources/logback.xml @@ -9,24 +9,25 @@ + - - - - - - - - - - - +