mirror of
https://github.com/sparrowwallet/sparrow.git
synced 2026-08-10 16:43:14 +00:00
add icons for external sources in settings and recent blocks view
This commit is contained in:
@@ -3,6 +3,8 @@ package com.sparrowwallet.sparrow.control;
|
||||
import com.sparrowwallet.drongo.Network;
|
||||
import com.sparrowwallet.drongo.protocol.Transaction;
|
||||
import com.sparrowwallet.sparrow.BlockSummary;
|
||||
import com.sparrowwallet.sparrow.io.Config;
|
||||
import com.sparrowwallet.sparrow.net.FeeRatesSource;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.animation.Timeline;
|
||||
@@ -15,6 +17,7 @@ import javafx.scene.text.FontWeight;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.scene.text.TextFlow;
|
||||
import javafx.util.Duration;
|
||||
import org.girod.javafx.svgimage.SVGImage;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
@@ -32,6 +35,7 @@ public class BlockCube extends Group {
|
||||
private final LongProperty timestampProperty = new SimpleLongProperty(System.currentTimeMillis());
|
||||
private final StringProperty elapsedProperty = new SimpleStringProperty("");
|
||||
private final BooleanProperty confirmedProperty = new SimpleBooleanProperty(false);
|
||||
private final ObjectProperty<FeeRatesSource> feeRatesSource = new SimpleObjectProperty<>(null);
|
||||
|
||||
private Polygon front;
|
||||
private Rectangle unusedArea;
|
||||
@@ -43,10 +47,12 @@ public class BlockCube extends Group {
|
||||
private final TextFlow medianFeeTextFlow = new TextFlow();
|
||||
private final Text txCountText = new Text();
|
||||
private final Text elapsedText = new Text();
|
||||
private final Group feeRateIcon = new Group();
|
||||
|
||||
public BlockCube(Integer weight, Double medianFee, Integer height, Integer txCount, Long timestamp, boolean confirmed) {
|
||||
getStyleClass().addAll("block-" + Network.getCanonical().getName(), "block-cube");
|
||||
this.confirmedProperty.set(confirmed);
|
||||
this.feeRatesSource.set(Config.get().getFeeRatesSource());
|
||||
|
||||
this.weightProperty.addListener((_, _, _) -> {
|
||||
if(front != null) {
|
||||
@@ -79,6 +85,11 @@ public class BlockCube extends Group {
|
||||
updateFill();
|
||||
}
|
||||
});
|
||||
this.feeRatesSource.addListener((_, _, _) -> {
|
||||
if(front != null) {
|
||||
updateFill();
|
||||
}
|
||||
});
|
||||
this.medianFeeText.textProperty().addListener((_, _, _) -> {
|
||||
pulse();
|
||||
});
|
||||
@@ -145,12 +156,15 @@ public class BlockCube extends Group {
|
||||
txCountText.setX((CUBE_SIZE - txCountText.getLayoutBounds().getWidth()) / 2);
|
||||
txCountText.setY(34);
|
||||
|
||||
feeRateIcon.setTranslateX(((CUBE_SIZE * 0.7) - 14) / 2);
|
||||
feeRateIcon.setTranslateY(-36);
|
||||
|
||||
elapsedText.getStyleClass().add("block-text");
|
||||
elapsedText.setFont(new Font(10));
|
||||
elapsedText.setX((CUBE_SIZE - elapsedText.getLayoutBounds().getWidth()) / 2);
|
||||
elapsedText.setY(50);
|
||||
|
||||
getChildren().addAll(frontFaceGroup, top, left, heightText, medianFeeTextFlow, txCountText, elapsedText);
|
||||
getChildren().addAll(frontFaceGroup, top, left, heightText, medianFeeTextFlow, txCountText, feeRateIcon, elapsedText);
|
||||
}
|
||||
|
||||
private void updateFill() {
|
||||
@@ -167,6 +181,7 @@ public class BlockCube extends Group {
|
||||
usedArea.setHeight(CUBE_SIZE - startYAbsolute);
|
||||
usedArea.setVisible(true);
|
||||
heightText.setVisible(true);
|
||||
feeRateIcon.getChildren().clear();
|
||||
} else {
|
||||
getStyleClass().removeAll("block-confirmed");
|
||||
if(!getStyleClass().contains("block-unconfirmed")) {
|
||||
@@ -175,6 +190,14 @@ public class BlockCube extends Group {
|
||||
usedArea.setVisible(false);
|
||||
unusedArea.setStyle("-fx-fill: " + getFeeRateStyleName() + ";");
|
||||
heightText.setVisible(false);
|
||||
if(feeRatesSource.get() != null) {
|
||||
SVGImage svgImage = feeRatesSource.get().getSVGImage();
|
||||
if(svgImage != null) {
|
||||
feeRateIcon.getChildren().setAll(feeRatesSource.get().getSVGImage());
|
||||
} else {
|
||||
feeRateIcon.getChildren().clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -324,6 +347,18 @@ public class BlockCube extends Group {
|
||||
confirmedProperty.set(confirmed);
|
||||
}
|
||||
|
||||
public FeeRatesSource getFeeRatesSource() {
|
||||
return feeRatesSource.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<FeeRatesSource> feeRatesSourceProperty() {
|
||||
return feeRatesSource;
|
||||
}
|
||||
|
||||
public void setFeeRatesSource(FeeRatesSource feeRatesSource) {
|
||||
this.feeRatesSource.set(feeRatesSource);
|
||||
}
|
||||
|
||||
public static BlockCube fromBlockSummary(BlockSummary blockSummary) {
|
||||
return new BlockCube(blockSummary.getWeight().orElse(0), blockSummary.getMedianFee().orElse(-1.0d), blockSummary.getHeight(),
|
||||
blockSummary.getTransactionCount().orElse(0), blockSummary.getTimestamp().getTime(), true);
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
package com.sparrowwallet.sparrow.control;
|
||||
|
||||
import com.sparrowwallet.sparrow.BlockSummary;
|
||||
import com.sparrowwallet.sparrow.io.Config;
|
||||
import com.sparrowwallet.sparrow.net.FeeRatesSource;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import io.reactivex.rxjavafx.schedulers.JavaFxScheduler;
|
||||
import javafx.animation.TranslateTransition;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.shape.Line;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
@@ -16,6 +19,8 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static com.sparrowwallet.sparrow.control.BlockCube.CUBE_SIZE;
|
||||
|
||||
public class RecentBlocksView extends Pane {
|
||||
private static final double CUBE_SPACING = 100;
|
||||
private static final double ANIMATION_DURATION_MILLIS = 1000;
|
||||
@@ -24,6 +29,7 @@ public class RecentBlocksView extends Pane {
|
||||
private final CompositeDisposable disposables = new CompositeDisposable();
|
||||
|
||||
private final ObjectProperty<List<BlockCube>> cubesProperty = new SimpleObjectProperty<>(new ArrayList<>());
|
||||
private final Tooltip tooltip = new Tooltip();
|
||||
|
||||
public RecentBlocksView() {
|
||||
cubesProperty.addListener((_, _, newValue) -> {
|
||||
@@ -41,6 +47,16 @@ public class RecentBlocksView extends Pane {
|
||||
cube.setElapsed(BlockCube.getElapsed(cube.getTimestamp()));
|
||||
}
|
||||
}));
|
||||
|
||||
updateFeeRatesSource(Config.get().getFeeRatesSource());
|
||||
Tooltip.install(this, tooltip);
|
||||
}
|
||||
|
||||
public void updateFeeRatesSource(FeeRatesSource feeRatesSource) {
|
||||
tooltip.setText("Fee rate estimate from " + feeRatesSource.getDescription());
|
||||
if(getCubes() != null && !getCubes().isEmpty()) {
|
||||
getCubes().getFirst().setFeeRatesSource(feeRatesSource);
|
||||
}
|
||||
}
|
||||
|
||||
public void drawView() {
|
||||
@@ -54,7 +70,7 @@ public class RecentBlocksView extends Pane {
|
||||
}
|
||||
|
||||
private void createSeparator() {
|
||||
Line separator = new Line(SEPARATOR_X, -9, SEPARATOR_X, 80);
|
||||
Line separator = new Line(SEPARATOR_X, -9, SEPARATOR_X, CUBE_SIZE);
|
||||
separator.getStyleClass().add("blocks-separator");
|
||||
separator.getStrokeDashArray().addAll(5.0, 5.0); // Create dotted line pattern
|
||||
separator.setStrokeWidth(1.0);
|
||||
|
||||
@@ -1,12 +1,22 @@
|
||||
package com.sparrowwallet.sparrow.net;
|
||||
|
||||
import com.sparrowwallet.sparrow.AppServices;
|
||||
import com.sparrowwallet.sparrow.io.Server;
|
||||
import org.girod.javafx.svgimage.SVGImage;
|
||||
import org.girod.javafx.svgimage.SVGLoader;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.Locale;
|
||||
|
||||
public enum BlockExplorer {
|
||||
MEMPOOL_SPACE("https://mempool.space"),
|
||||
BLOCKSTREAM_INFO("https://blockstream.info"),
|
||||
NONE("http://none");
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(BlockExplorer.class);
|
||||
|
||||
private final Server server;
|
||||
|
||||
BlockExplorer(String url) {
|
||||
@@ -16,4 +26,17 @@ public enum BlockExplorer {
|
||||
public Server getServer() {
|
||||
return server;
|
||||
}
|
||||
|
||||
public static SVGImage getSVGImage(Server server) {
|
||||
try {
|
||||
URL url = AppServices.class.getResource("/image/blockexplorer/" + server.getHost().toLowerCase(Locale.ROOT) + "-icon.svg");
|
||||
if(url != null) {
|
||||
return SVGLoader.load(url);
|
||||
}
|
||||
} catch(Exception e) {
|
||||
log.error("Could not load block explorer image for " + server.getHost());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,9 +9,12 @@ import javafx.concurrent.ScheduledService;
|
||||
import javafx.concurrent.Service;
|
||||
import javafx.concurrent.Task;
|
||||
import org.apache.commons.lang3.time.DateUtils;
|
||||
import org.girod.javafx.svgimage.SVGImage;
|
||||
import org.girod.javafx.svgimage.SVGLoader;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.net.URL;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.Instant;
|
||||
@@ -297,6 +300,19 @@ public enum ExchangeSource {
|
||||
return name;
|
||||
}
|
||||
|
||||
public SVGImage getSVGImage() {
|
||||
try {
|
||||
URL url = AppServices.class.getResource("/image/exchangesource/" + name.toLowerCase(Locale.ROOT) + "-icon.svg");
|
||||
if(url != null) {
|
||||
return SVGLoader.load(url);
|
||||
}
|
||||
} catch(Exception e) {
|
||||
log.error("Could not load exchange source image for " + name);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static class CurrenciesService extends Service<List<Currency>> {
|
||||
private final ExchangeSource exchangeSource;
|
||||
|
||||
|
||||
@@ -7,9 +7,12 @@ import com.sparrowwallet.drongo.wallet.BlockTransaction;
|
||||
import com.sparrowwallet.drongo.wallet.BlockTransactionHash;
|
||||
import com.sparrowwallet.sparrow.AppServices;
|
||||
import com.sparrowwallet.sparrow.BlockSummary;
|
||||
import org.girod.javafx.svgimage.SVGImage;
|
||||
import org.girod.javafx.svgimage.SVGLoader;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.*;
|
||||
|
||||
public enum FeeRatesSource {
|
||||
@@ -275,6 +278,27 @@ public enum FeeRatesSource {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return switch(this) {
|
||||
case ELECTRUM_SERVER -> "server";
|
||||
case MINIMUM -> "settings";
|
||||
default -> getName().toLowerCase(Locale.ROOT);
|
||||
};
|
||||
}
|
||||
|
||||
public SVGImage getSVGImage() {
|
||||
try {
|
||||
URL url = AppServices.class.getResource("/image/feeratesource/" + getDescription() + "-icon.svg");
|
||||
if(url != null) {
|
||||
return SVGLoader.load(url);
|
||||
}
|
||||
} catch(Exception e) {
|
||||
log.error("Could not load fee rates source image for " + name);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected record ThreeTierRates(Double fastestFee, Double halfHourFee, Double hourFee, Double minimumFee) {}
|
||||
|
||||
private record OxtRates(OxtRatesData[] data) {}
|
||||
|
||||
@@ -87,6 +87,8 @@ public class GeneralSettingsController extends SettingsDetailController {
|
||||
config.setFeeRatesSource(feeRatesSource.getValue());
|
||||
}
|
||||
|
||||
feeRatesSource.setCellFactory(_ -> new FeeRatesSourceListCell());
|
||||
feeRatesSource.setButtonCell(feeRatesSource.getCellFactory().call(null));
|
||||
feeRatesSource.valueProperty().addListener((observable, oldValue, newValue) -> {
|
||||
config.setFeeRatesSource(newValue);
|
||||
EventManager.get().post(new FeeRatesSourceChangedEvent(newValue));
|
||||
@@ -96,25 +98,8 @@ public class GeneralSettingsController extends SettingsDetailController {
|
||||
currenciesLoadWarning.setVisible(false);
|
||||
|
||||
blockExplorers.setItems(getBlockExplorerList());
|
||||
blockExplorers.setConverter(new StringConverter<>() {
|
||||
@Override
|
||||
public String toString(Server server) {
|
||||
if(server == null || server == BlockExplorer.NONE.getServer()) {
|
||||
return "None";
|
||||
}
|
||||
|
||||
if(server == CUSTOM_BLOCK_EXPLORER) {
|
||||
return "Custom...";
|
||||
}
|
||||
|
||||
return server.getHost();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Server fromString(String string) {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
blockExplorers.setCellFactory(_ -> new BlockExplorerListCell());
|
||||
blockExplorers.setButtonCell(blockExplorers.getCellFactory().call(null));
|
||||
blockExplorers.valueProperty().addListener((observable, oldValue, newValue) -> {
|
||||
if(newValue != null) {
|
||||
if(newValue == CUSTOM_BLOCK_EXPLORER) {
|
||||
@@ -258,14 +243,50 @@ public class GeneralSettingsController extends SettingsDetailController {
|
||||
fiatCurrency.valueProperty().addListener(fiatCurrencyListener);
|
||||
}
|
||||
|
||||
private static class FeeRatesSourceListCell extends ListCell<FeeRatesSource> {
|
||||
@Override
|
||||
protected void updateItem(FeeRatesSource item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if(empty || item == null) {
|
||||
setText(null);
|
||||
setGraphic(null);
|
||||
} else {
|
||||
setText(item.toString());
|
||||
setGraphic(item.getSVGImage());
|
||||
setGraphicTextGap(8.0d);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class BlockExplorerListCell extends ListCell<Server> {
|
||||
@Override
|
||||
protected void updateItem(Server server, boolean empty) {
|
||||
super.updateItem(server, empty);
|
||||
if(empty || server == null || server == BlockExplorer.NONE.getServer()) {
|
||||
setText("None");
|
||||
setGraphic(null);
|
||||
} else if(server == CUSTOM_BLOCK_EXPLORER) {
|
||||
setText("Custom...");
|
||||
setGraphic(null);
|
||||
} else {
|
||||
setText(server.getHost());
|
||||
setGraphic(BlockExplorer.getSVGImage(server));
|
||||
setGraphicTextGap(8.0d);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class ExchangeSourceButtonCell extends ListCell<ExchangeSource> {
|
||||
@Override
|
||||
protected void updateItem(ExchangeSource exchangeSource, boolean empty) {
|
||||
super.updateItem(exchangeSource, empty);
|
||||
if(exchangeSource == null || empty) {
|
||||
setText("");
|
||||
setGraphic(null);
|
||||
} else {
|
||||
setText(exchangeSource.getName());
|
||||
setGraphic(exchangeSource.getSVGImage());
|
||||
setGraphicTextGap(8.0d);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -276,12 +297,15 @@ public class GeneralSettingsController extends SettingsDetailController {
|
||||
super.updateItem(exchangeSource, empty);
|
||||
if(exchangeSource == null || empty) {
|
||||
setText("");
|
||||
setGraphic(null);
|
||||
} else {
|
||||
String text = exchangeSource.getName();
|
||||
if(exchangeSource.getDescription() != null && !exchangeSource.getDescription().isEmpty()) {
|
||||
text += " (" + exchangeSource.getDescription() + ")";
|
||||
}
|
||||
setText(text);
|
||||
setGraphic(exchangeSource.getSVGImage());
|
||||
setGraphicTextGap(8.0d);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1600,6 +1600,11 @@ public class SendController extends WalletFormController implements Initializabl
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void feeRateSourceChanged(FeeRatesSourceChangedEvent event) {
|
||||
recentBlocksView.updateFeeRatesSource(event.getFeeRateSource());
|
||||
}
|
||||
|
||||
private class PrivacyAnalysisTooltip extends VBox {
|
||||
private final List<Label> analysisLabels = new ArrayList<>();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user