all walletconfig for wallet scope configuration variables
|
@ -110,6 +110,7 @@ dependencies {
|
|||
implementation('net.sourceforge.streamsupport:streamsupport:1.7.0')
|
||||
implementation('com.github.librepdf:openpdf:1.3.27')
|
||||
implementation('com.googlecode.lanterna:lanterna:3.1.1')
|
||||
implementation('net.coobird:thumbnailator:0.4.18')
|
||||
testImplementation('junit:junit:4.12')
|
||||
}
|
||||
|
||||
|
@ -579,6 +580,10 @@ extraJavaModuleInfo {
|
|||
module('jcip-annotations-1.0.jar', 'net.jcip.annotations', '1.0') {
|
||||
exports('net.jcip.annotations')
|
||||
}
|
||||
module('thumbnailator-0.4.18.jar', 'net.coobird.thumbnailator', '0.4.18') {
|
||||
exports('net.coobird.thumbnailator')
|
||||
requires('java.desktop')
|
||||
}
|
||||
module("netlayer-jpms-${osName}${targetName}-0.6.8.jar", 'netlayer.jpms', '0.6.8') {
|
||||
exports('org.berndpruenster.netlayer.tor')
|
||||
requires('com.github.ravn.jsocks')
|
||||
|
|
2
drongo
|
@ -1 +1 @@
|
|||
Subproject commit 7c34ec7c3b818634b7819f1b27a5f9c57f5554c8
|
||||
Subproject commit fa18ec9d458bb17221fb01b6be9f4eceb354a156
|
|
@ -1463,11 +1463,10 @@ public class AppController implements Initializable {
|
|||
wallet.setName(name);
|
||||
}
|
||||
Tab tab = new Tab("");
|
||||
Glyph glyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.WALLET);
|
||||
glyph.setFontSize(10.0);
|
||||
glyph.setOpacity(TAB_LABEL_GRAPHIC_OPACITY_ACTIVE);
|
||||
WalletIcon walletIcon = new WalletIcon(storage, wallet);
|
||||
walletIcon.setOpacity(TAB_LABEL_GRAPHIC_OPACITY_ACTIVE);
|
||||
Label tabLabel = new Label(name);
|
||||
tabLabel.setGraphic(glyph);
|
||||
tabLabel.setGraphic(walletIcon);
|
||||
tabLabel.setGraphicTextGap(5.0);
|
||||
tab.setGraphic(tabLabel);
|
||||
tab.setClosable(true);
|
||||
|
@ -1856,17 +1855,61 @@ public class AppController implements Initializable {
|
|||
|
||||
contextMenu.getItems().addAll(new SeparatorMenuItem(), close, closeOthers, closeAll);
|
||||
|
||||
if(tab.getUserData() instanceof WalletTabData) {
|
||||
if(tab.getUserData() instanceof WalletTabData walletTabData) {
|
||||
Menu walletIcon = new Menu("Wallet Icon");
|
||||
MenuItem custom = new MenuItem("Custom...");
|
||||
custom.setOnAction(event -> {
|
||||
setCustomIcon(walletTabData.getWallet());
|
||||
});
|
||||
MenuItem reset = new MenuItem("Reset");
|
||||
reset.setOnAction(event -> {
|
||||
resetIcon(walletTabData.getWalletForm());
|
||||
});
|
||||
walletIcon.getItems().addAll(custom, reset);
|
||||
|
||||
MenuItem delete = new MenuItem("Delete...");
|
||||
delete.setOnAction(event -> {
|
||||
deleteWallet(getSelectedWalletForm());
|
||||
deleteWallet(walletTabData.getWalletForm());
|
||||
});
|
||||
contextMenu.getItems().addAll(new SeparatorMenuItem(), delete);
|
||||
contextMenu.getItems().addAll(new SeparatorMenuItem(), walletIcon, delete);
|
||||
}
|
||||
|
||||
return contextMenu;
|
||||
}
|
||||
|
||||
private void setCustomIcon(Wallet wallet) {
|
||||
Stage window = new Stage();
|
||||
|
||||
FileChooser fileChooser = new FileChooser();
|
||||
fileChooser.setTitle("Open Image");
|
||||
fileChooser.getExtensionFilters().addAll(
|
||||
new FileChooser.ExtensionFilter("All Files", org.controlsfx.tools.Platform.getCurrent().equals(org.controlsfx.tools.Platform.UNIX) ? "*" : "*.*"),
|
||||
new FileChooser.ExtensionFilter("Images", "*.png", "*.jpg", "*.jpeg", "*.gif")
|
||||
);
|
||||
|
||||
AppServices.moveToActiveWindowScreen(window, 800, 450);
|
||||
File file = fileChooser.showOpenDialog(window);
|
||||
if(file != null) {
|
||||
try {
|
||||
byte[] iconData = ImageUtils.resize(file, WalletIcon.SAVE_WIDTH, WalletIcon.SAVE_HEIGHT);
|
||||
WalletConfig walletConfig = wallet.getMasterWalletConfig();
|
||||
walletConfig.setIconData(iconData, true);
|
||||
EventManager.get().post(new WalletConfigChangedEvent(wallet));
|
||||
} catch(Exception e) {
|
||||
log.error("Error creating custom wallet icon", e);
|
||||
showErrorDialog("Error creating custom wallet icon", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void resetIcon(WalletForm walletForm) {
|
||||
Wallet masterWallet = walletForm.getMasterWallet();
|
||||
if(masterWallet.getWalletConfig() != null && masterWallet.getWalletConfig().isUserIcon()) {
|
||||
masterWallet.getWalletConfig().setIconData(null, false);
|
||||
EventManager.get().post(new WalletConfigChangedEvent(masterWallet));
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteWallet(WalletForm selectedWalletForm) {
|
||||
Optional<ButtonType> optButtonType = AppServices.showWarningDialog("Delete " + selectedWalletForm.getWallet().getMasterName() + "?", "The wallet file and any backups will be deleted. Are you sure?", ButtonType.NO, ButtonType.YES);
|
||||
if(optButtonType.isPresent() && optButtonType.get() == ButtonType.YES) {
|
||||
|
@ -2071,8 +2114,8 @@ public class AppController implements Initializable {
|
|||
|
||||
private void tabLabelAddFailure(Tab tab) {
|
||||
Label tabLabel = (Label)tab.getGraphic();
|
||||
if(!tabLabel.getStyleClass().contains("failure")) {
|
||||
tabLabel.getGraphic().getStyleClass().add("failure");
|
||||
WalletIcon walletIcon = (WalletIcon)tabLabel.getGraphic();
|
||||
if(walletIcon.addFailure()) {
|
||||
tabLabel.setTooltip(new Tooltip("Error loading transaction history from server"));
|
||||
}
|
||||
}
|
||||
|
@ -2105,7 +2148,8 @@ public class AppController implements Initializable {
|
|||
|
||||
private void tabLabelRemoveFailure(Tab tab) {
|
||||
Label tabLabel = (Label)tab.getGraphic();
|
||||
tabLabel.getGraphic().getStyleClass().remove("failure");
|
||||
WalletIcon walletIcon = (WalletIcon)tabLabel.getGraphic();
|
||||
walletIcon.removeFailure();
|
||||
tabLabel.setTooltip(null);
|
||||
}
|
||||
|
||||
|
@ -2222,6 +2266,9 @@ public class AppController implements Initializable {
|
|||
Tab masterTab = subTabs.getTabs().stream().filter(tab -> ((WalletTabData)tab.getUserData()).getWallet().isMasterWallet()).findFirst().orElse(subTabs.getTabs().get(0));
|
||||
Label masterLabel = (Label)masterTab.getGraphic();
|
||||
masterLabel.setText(event.getWallet().getLabel() != null ? event.getWallet().getLabel() : event.getWallet().getAutomaticName());
|
||||
Label tabLabel = (Label)walletTab.getGraphic();
|
||||
WalletIcon walletIcon = (WalletIcon)tabLabel.getGraphic();
|
||||
walletIcon.setWallet(event.getWallet());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2749,4 +2796,17 @@ public class AppController implements Initializable {
|
|||
lockAllWallets.setDisable(false);
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void walletConfigChanged(WalletConfigChangedEvent event) {
|
||||
for(Tab tab : tabs.getTabs()) {
|
||||
if(tab.getUserData() instanceof WalletTabData walletTabData) {
|
||||
if(walletTabData.getWallet() == event.getWallet()) {
|
||||
Label tabLabel = (Label)tab.getGraphic();
|
||||
WalletIcon walletIcon = (WalletIcon)tabLabel.getGraphic();
|
||||
walletIcon.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package com.sparrowwallet.sparrow;
|
|||
|
||||
import com.sparrowwallet.drongo.Network;
|
||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||
import com.sparrowwallet.sparrow.control.WalletIcon;
|
||||
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
|
||||
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5Brands;
|
||||
import com.sparrowwallet.sparrow.io.Config;
|
||||
|
@ -18,6 +19,7 @@ import org.controlsfx.tools.Platform;
|
|||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
@ -43,6 +45,7 @@ public class SparrowDesktop extends Application {
|
|||
GlyphFontRegistry.register(new FontAwesome5());
|
||||
GlyphFontRegistry.register(new FontAwesome5Brands());
|
||||
Font.loadFont(AppServices.class.getResourceAsStream("/font/RobotoMono-Regular.ttf"), 13);
|
||||
URL.setURLStreamHandlerFactory(protocol -> WalletIcon.PROTOCOL.equals(protocol) ? new WalletIcon.WalletIconStreamHandler() : null);
|
||||
|
||||
AppServices.initialize(this);
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@ package com.sparrowwallet.sparrow.control;
|
|||
|
||||
import com.sparrowwallet.drongo.bip47.PaymentCode;
|
||||
import com.sparrowwallet.sparrow.AppServices;
|
||||
import com.sparrowwallet.sparrow.EventManager;
|
||||
import com.sparrowwallet.sparrow.event.PayNymImageLoadedEvent;
|
||||
import com.sparrowwallet.sparrow.io.Config;
|
||||
import com.sparrowwallet.sparrow.paynym.PayNymService;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
|
@ -47,6 +49,7 @@ public class PayNymAvatar extends StackPane {
|
|||
});
|
||||
payNymAvatarService.setOnSucceeded(successEvent -> {
|
||||
setImage(payNymAvatarService.getValue());
|
||||
EventManager.get().post(new PayNymImageLoadedEvent(paymentCode, payNymAvatarService.getValue()));
|
||||
});
|
||||
payNymAvatarService.setOnFailed(failedEvent -> {
|
||||
log.debug("Error loading PayNym avatar", failedEvent.getSource().getException());
|
||||
|
|
167
src/main/java/com/sparrowwallet/sparrow/control/WalletIcon.java
Normal file
|
@ -0,0 +1,167 @@
|
|||
package com.sparrowwallet.sparrow.control;
|
||||
|
||||
import com.sparrowwallet.drongo.wallet.*;
|
||||
import com.sparrowwallet.sparrow.AppServices;
|
||||
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
|
||||
import com.sparrowwallet.sparrow.io.ImageUtils;
|
||||
import com.sparrowwallet.sparrow.io.Storage;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.paint.ImagePattern;
|
||||
import javafx.scene.shape.Circle;
|
||||
import org.controlsfx.glyphfont.Glyph;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.net.URLStreamHandler;
|
||||
|
||||
public class WalletIcon extends StackPane {
|
||||
public static final String PROTOCOL = "walleticon";
|
||||
private static final String QUERY = "icon";
|
||||
public static final int WIDTH = 15;
|
||||
public static final int HEIGHT = 15;
|
||||
public static final int SAVE_WIDTH = WIDTH * 2;
|
||||
public static final int SAVE_HEIGHT = HEIGHT * 2;
|
||||
|
||||
private final Storage storage;
|
||||
private final ObjectProperty<Wallet> walletProperty = new SimpleObjectProperty<>();
|
||||
|
||||
public WalletIcon(Storage storage, Wallet wallet) {
|
||||
super();
|
||||
this.storage = storage;
|
||||
setPrefSize(WIDTH, HEIGHT);
|
||||
walletProperty.addListener((observable, oldValue, newValue) -> {
|
||||
refresh();
|
||||
});
|
||||
walletProperty.set(wallet);
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
Wallet wallet = getWallet();
|
||||
|
||||
getChildren().clear();
|
||||
if(wallet.getWalletConfig() != null && wallet.getWalletConfig().getIconData() != null) {
|
||||
String walletId = storage.getWalletId(wallet);
|
||||
if(AppServices.get().getWallet(walletId) != null) {
|
||||
addWalletIcon(walletId);
|
||||
} else {
|
||||
Platform.runLater(() -> addWalletIcon(walletId));
|
||||
}
|
||||
} else if(wallet.getKeystores().size() == 1) {
|
||||
Keystore keystore = wallet.getKeystores().get(0);
|
||||
if(keystore.getSource() == KeystoreSource.HW_USB || keystore.getSource() == KeystoreSource.HW_AIRGAPPED) {
|
||||
WalletModel walletModel = keystore.getWalletModel();
|
||||
|
||||
Image image = null;
|
||||
try {
|
||||
image = new Image("image/" + walletModel.getType() + "-icon.png", 15, 15, true, true);
|
||||
} catch(Exception e) {
|
||||
//ignore
|
||||
}
|
||||
|
||||
if(image == null) {
|
||||
try {
|
||||
image = new Image("image/" + walletModel.getType() + ".png", 15, 15, true, true);
|
||||
} catch(Exception e) {
|
||||
//ignore
|
||||
}
|
||||
}
|
||||
|
||||
if(image != null && !image.isError()) {
|
||||
ImageView imageView = new ImageView(image);
|
||||
getChildren().add(imageView);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(getChildren().isEmpty()) {
|
||||
Glyph glyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.WALLET);
|
||||
glyph.setFontSize(10.0);
|
||||
getChildren().add(glyph);
|
||||
}
|
||||
}
|
||||
|
||||
private void addWalletIcon(String walletId) {
|
||||
Image image = new Image(PROTOCOL + ":" + walletId + "?" + QUERY, WIDTH, HEIGHT, true, false);
|
||||
getChildren().clear();
|
||||
Circle circle = new Circle(getPrefWidth() / 2,getPrefHeight() / 2,getPrefWidth() / 2);
|
||||
circle.setFill(new ImagePattern(image));
|
||||
getChildren().add(circle);
|
||||
}
|
||||
|
||||
public boolean addFailure() {
|
||||
if(getChildren().stream().noneMatch(node -> node.getStyleClass().contains("failure"))) {
|
||||
Glyph failGlyph = new Glyph(FontAwesome5.FONT_NAME, FontAwesome5.Glyph.EXCLAMATION_CIRCLE);
|
||||
failGlyph.setFontSize(10);
|
||||
failGlyph.getStyleClass().add("failure");
|
||||
getChildren().add(failGlyph);
|
||||
StackPane.setAlignment(failGlyph, Pos.TOP_RIGHT);
|
||||
failGlyph.setTranslateX(5);
|
||||
failGlyph.setTranslateY(-4);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void removeFailure() {
|
||||
getChildren().removeIf(node -> node.getStyleClass().contains("failure"));
|
||||
}
|
||||
|
||||
public Wallet getWallet() {
|
||||
return walletProperty.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<Wallet> walletProperty() {
|
||||
return walletProperty;
|
||||
}
|
||||
|
||||
public void setWallet(Wallet wallet) {
|
||||
this.walletProperty.set(wallet);
|
||||
}
|
||||
|
||||
public static class WalletIconStreamHandler extends URLStreamHandler {
|
||||
@Override
|
||||
protected URLConnection openConnection(URL url) throws IOException {
|
||||
return new URLConnection(url) {
|
||||
@Override
|
||||
public void connect() throws IOException {
|
||||
//Nothing required
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() throws IOException {
|
||||
String walletId = url.getPath();
|
||||
String query = url.getQuery();
|
||||
|
||||
Wallet wallet = AppServices.get().getWallet(walletId);
|
||||
if(wallet == null) {
|
||||
throw new IOException("Cannot find wallet for wallet id " + walletId);
|
||||
}
|
||||
|
||||
if(query.startsWith(QUERY)) {
|
||||
if(wallet.getWalletConfig() == null || wallet.getWalletConfig().getIconData() == null) {
|
||||
throw new IOException("No icon data for " + walletId);
|
||||
}
|
||||
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(wallet.getWalletConfig().getIconData());
|
||||
if(query.endsWith("@2x")) {
|
||||
return bais;
|
||||
} else {
|
||||
return ImageUtils.resize(bais, WalletIcon.WIDTH, WalletIcon.HEIGHT);
|
||||
}
|
||||
}
|
||||
|
||||
throw new MalformedURLException("Cannot load url " + url);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package com.sparrowwallet.sparrow.event;
|
||||
|
||||
import com.sparrowwallet.drongo.bip47.PaymentCode;
|
||||
import javafx.scene.image.Image;
|
||||
|
||||
public class PayNymImageLoadedEvent {
|
||||
private final PaymentCode paymentCode;
|
||||
private final Image image;
|
||||
|
||||
public PayNymImageLoadedEvent(PaymentCode paymentCode, Image image) {
|
||||
this.paymentCode = paymentCode;
|
||||
this.image = image;
|
||||
}
|
||||
|
||||
public PaymentCode getPaymentCode() {
|
||||
return paymentCode;
|
||||
}
|
||||
|
||||
public Image getImage() {
|
||||
return image;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package com.sparrowwallet.sparrow.event;
|
||||
|
||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||
|
||||
public class WalletConfigChangedEvent extends WalletChangedEvent {
|
||||
public WalletConfigChangedEvent(Wallet wallet) {
|
||||
super(wallet);
|
||||
}
|
||||
}
|
56
src/main/java/com/sparrowwallet/sparrow/io/ImageUtils.java
Normal file
|
@ -0,0 +1,56 @@
|
|||
package com.sparrowwallet.sparrow.io;
|
||||
|
||||
import javafx.embed.swing.SwingFXUtils;
|
||||
import javafx.scene.image.Image;
|
||||
import net.coobird.thumbnailator.Thumbnails;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.*;
|
||||
|
||||
public class ImageUtils {
|
||||
public static byte[] resize(Image image, int width, int height) {
|
||||
return resize(SwingFXUtils.fromFXImage(image, null), width, height);
|
||||
}
|
||||
|
||||
public static byte[] resize(BufferedImage image, int width, int height) {
|
||||
try {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
resize(image, baos, width, height);
|
||||
return baos.toByteArray();
|
||||
} catch(IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void resize(BufferedImage image, OutputStream outputStream, int width, int height) throws IOException {
|
||||
resize(Thumbnails.of(image), outputStream, width, height);
|
||||
}
|
||||
|
||||
public static byte[] resize(File file, int width, int height) throws IOException {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
resize(file, baos, width, height);
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
public static void resize(File file, OutputStream outputStream, int width, int height) throws IOException {
|
||||
resize(Thumbnails.of(file), outputStream, width, height);
|
||||
}
|
||||
|
||||
public static InputStream resize(InputStream inputStream, int width, int height) {
|
||||
try {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
resize(inputStream, baos, width, height);
|
||||
return new ByteArrayInputStream(baos.toByteArray());
|
||||
} catch(IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void resize(InputStream inputStream, OutputStream outputStream, int width, int height) throws IOException {
|
||||
resize(Thumbnails.of(inputStream), outputStream, width, height);
|
||||
}
|
||||
|
||||
private static void resize(Thumbnails.Builder<?> builder, OutputStream outputStream, int width, int height) throws IOException {
|
||||
builder.size(width, height).outputFormat("png").outputQuality(1).toOutputStream(outputStream);
|
||||
}
|
||||
}
|
|
@ -315,6 +315,11 @@ public class DbPersistence implements Persistence {
|
|||
}
|
||||
}
|
||||
|
||||
if(dirtyPersistables.walletConfig) {
|
||||
WalletConfigDao walletConfigDao = handle.attach(WalletConfigDao.class);
|
||||
walletConfigDao.addOrUpdate(wallet, wallet.getWalletConfig());
|
||||
}
|
||||
|
||||
if(dirtyPersistables.mixConfig) {
|
||||
MixConfigDao mixConfigDao = handle.attach(MixConfigDao.class);
|
||||
mixConfigDao.addOrUpdate(wallet, wallet.getMixConfig());
|
||||
|
@ -745,6 +750,13 @@ public class DbPersistence implements Persistence {
|
|||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void walletConfigChanged(WalletConfigChangedEvent event) {
|
||||
if(persistsFor(event.getWallet()) && event.getWallet().getWalletConfig() != null) {
|
||||
updateExecutor.execute(() -> dirtyPersistablesMap.computeIfAbsent(event.getWallet(), key -> new DirtyPersistables()).walletConfig = true);
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void walletMixConfigChanged(WalletMixConfigChangedEvent event) {
|
||||
if(persistsFor(event.getWallet()) && event.getWallet().getMixConfig() != null) {
|
||||
|
@ -793,6 +805,7 @@ public class DbPersistence implements Persistence {
|
|||
public Integer watchLast = null;
|
||||
public final List<Entry> labelEntries = new ArrayList<>();
|
||||
public final List<BlockTransactionHashIndex> utxoStatuses = new ArrayList<>();
|
||||
public boolean walletConfig;
|
||||
public boolean mixConfig;
|
||||
public final Map<Sha256Hash, UtxoMixData> changedUtxoMixes = new HashMap<>();
|
||||
public final Map<Sha256Hash, UtxoMixData> removedUtxoMixes = new HashMap<>();
|
||||
|
@ -812,6 +825,7 @@ public class DbPersistence implements Persistence {
|
|||
"\nAddress labels:" + labelEntries.stream().filter(entry -> entry instanceof NodeEntry).map(entry -> ((NodeEntry)entry).getNode().toString() + " " + entry.getLabel()).collect(Collectors.toList()) +
|
||||
"\nUTXO labels:" + labelEntries.stream().filter(entry -> entry instanceof HashIndexEntry).map(entry -> ((HashIndexEntry)entry).getHashIndex().toString()).collect(Collectors.toList()) +
|
||||
"\nUTXO statuses:" + utxoStatuses +
|
||||
"\nWallet config:" + walletConfig +
|
||||
"\nMix config:" + mixConfig +
|
||||
"\nUTXO mixes changed:" + changedUtxoMixes +
|
||||
"\nUTXO mixes removed:" + removedUtxoMixes +
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
package com.sparrowwallet.sparrow.io.db;
|
||||
|
||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||
import com.sparrowwallet.drongo.wallet.WalletConfig;
|
||||
import org.jdbi.v3.sqlobject.config.RegisterRowMapper;
|
||||
import org.jdbi.v3.sqlobject.statement.GetGeneratedKeys;
|
||||
import org.jdbi.v3.sqlobject.statement.SqlQuery;
|
||||
import org.jdbi.v3.sqlobject.statement.SqlUpdate;
|
||||
|
||||
public interface WalletConfigDao {
|
||||
@SqlQuery("select id, iconData, userIcon, usePayNym from walletConfig where wallet = ?")
|
||||
@RegisterRowMapper(WalletConfigMapper.class)
|
||||
WalletConfig getForWalletId(Long id);
|
||||
|
||||
@SqlUpdate("insert into walletConfig (iconData, userIcon, usePayNym, wallet) values (?, ?, ?, ?)")
|
||||
@GetGeneratedKeys("id")
|
||||
long insertWalletConfig(byte[] iconData, boolean userIcon, boolean usePayNym, long wallet);
|
||||
|
||||
@SqlUpdate("update walletConfig set iconData = ?, userIcon = ?, usePayNym = ?, wallet = ? where id = ?")
|
||||
void updateWalletConfig(byte[] iconData, boolean userIcon, boolean usePayNym, long wallet, long id);
|
||||
|
||||
default void addWalletConfig(Wallet wallet) {
|
||||
if(wallet.getWalletConfig() != null) {
|
||||
addOrUpdate(wallet, wallet.getWalletConfig());
|
||||
}
|
||||
}
|
||||
|
||||
default void addOrUpdate(Wallet wallet, WalletConfig walletConfig) {
|
||||
if(walletConfig.getId() == null) {
|
||||
long id = insertWalletConfig(walletConfig.getIconData(), walletConfig.isUserIcon(), walletConfig.isUsePayNym(), wallet.getId());
|
||||
walletConfig.setId(id);
|
||||
} else {
|
||||
updateWalletConfig(walletConfig.getIconData(), walletConfig.isUserIcon(), walletConfig.isUsePayNym(), wallet.getId(), walletConfig.getId());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package com.sparrowwallet.sparrow.io.db;
|
||||
|
||||
import com.sparrowwallet.drongo.wallet.WalletConfig;
|
||||
import org.jdbi.v3.core.mapper.RowMapper;
|
||||
import org.jdbi.v3.core.statement.StatementContext;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public class WalletConfigMapper implements RowMapper<WalletConfig> {
|
||||
@Override
|
||||
public WalletConfig map(ResultSet rs, StatementContext ctx) throws SQLException {
|
||||
byte[] iconData = rs.getBytes("iconData");
|
||||
boolean userIcon = rs.getBoolean("userIcon");
|
||||
boolean usePayNym = rs.getBoolean("usePayNym");
|
||||
|
||||
WalletConfig walletConfig = new WalletConfig(iconData, userIcon, usePayNym);
|
||||
walletConfig.setId(rs.getLong("id"));
|
||||
return walletConfig;
|
||||
}
|
||||
}
|
|
@ -1,10 +1,7 @@
|
|||
package com.sparrowwallet.sparrow.io.db;
|
||||
|
||||
import com.sparrowwallet.drongo.protocol.Sha256Hash;
|
||||
import com.sparrowwallet.drongo.wallet.BlockTransaction;
|
||||
import com.sparrowwallet.drongo.wallet.UtxoMixData;
|
||||
import com.sparrowwallet.drongo.wallet.Wallet;
|
||||
import com.sparrowwallet.drongo.wallet.WalletNode;
|
||||
import com.sparrowwallet.drongo.wallet.*;
|
||||
import org.jdbi.v3.sqlobject.CreateSqlObject;
|
||||
import org.jdbi.v3.sqlobject.config.RegisterRowMapper;
|
||||
import org.jdbi.v3.sqlobject.customizer.Bind;
|
||||
|
@ -33,6 +30,9 @@ public interface WalletDao {
|
|||
@CreateSqlObject
|
||||
DetachedLabelDao createDetachedLabelDao();
|
||||
|
||||
@CreateSqlObject
|
||||
WalletConfigDao createWalletConfigDao();
|
||||
|
||||
@CreateSqlObject
|
||||
MixConfigDao createMixConfigDao();
|
||||
|
||||
|
@ -118,6 +118,7 @@ public interface WalletDao {
|
|||
Map<String, String> detachedLabels = createDetachedLabelDao().getAll();
|
||||
wallet.getDetachedLabels().putAll(detachedLabels);
|
||||
|
||||
wallet.setWalletConfig(createWalletConfigDao().getForWalletId(wallet.getId()));
|
||||
wallet.setMixConfig(createMixConfigDao().getForWalletId(wallet.getId()));
|
||||
|
||||
Map<Sha256Hash, UtxoMixData> utxoMixes = createUtxoMixDataDao().getForWalletId(wallet.getId());
|
||||
|
@ -136,6 +137,7 @@ public interface WalletDao {
|
|||
createWalletNodeDao().addWalletNodes(wallet);
|
||||
createBlockTransactionDao().addBlockTransactions(wallet);
|
||||
createDetachedLabelDao().clearAndAddAll(wallet);
|
||||
createWalletConfigDao().addWalletConfig(wallet);
|
||||
createMixConfigDao().addMixConfig(wallet);
|
||||
createUtxoMixDataDao().addUtxoMixData(wallet);
|
||||
} finally {
|
||||
|
|
|
@ -165,7 +165,7 @@ public class PayNymController {
|
|||
followersList.setSelectionModel(new NoSelectionModel<>());
|
||||
followersList.setFocusTraversable(false);
|
||||
|
||||
if(Config.get().isUsePayNym() && AppServices.isConnected() && masterWallet.hasPaymentCode()) {
|
||||
if(isUsePayNym(masterWallet) && AppServices.isConnected() && masterWallet.hasPaymentCode()) {
|
||||
refresh();
|
||||
} else {
|
||||
payNymName.setVisible(false);
|
||||
|
@ -260,9 +260,9 @@ public class PayNymController {
|
|||
}
|
||||
|
||||
public void retrievePayNym(ActionEvent event) {
|
||||
Config.get().setUsePayNym(true);
|
||||
PayNymService payNymService = AppServices.getPayNymService();
|
||||
Wallet masterWallet = getMasterWallet();
|
||||
setUsePayNym(masterWallet, true);
|
||||
payNymService.createPayNym(masterWallet).subscribe(createMap -> {
|
||||
payNymName.setText((String)createMap.get("nymName"));
|
||||
payNymAvatar.setPaymentCode(masterWallet.getPaymentCode());
|
||||
|
@ -630,6 +630,31 @@ public class PayNymController {
|
|||
return wallet.isMasterWallet() ? wallet : wallet.getMasterWallet();
|
||||
}
|
||||
|
||||
protected boolean isUsePayNym(Wallet wallet) {
|
||||
//TODO: Remove config setting
|
||||
boolean usePayNym = Config.get().isUsePayNym();
|
||||
if(usePayNym && wallet != null) {
|
||||
setUsePayNym(wallet, true);
|
||||
}
|
||||
|
||||
return usePayNym;
|
||||
}
|
||||
|
||||
protected void setUsePayNym(Wallet wallet, boolean usePayNym) {
|
||||
//TODO: Remove config setting
|
||||
if(Config.get().isUsePayNym() != usePayNym) {
|
||||
Config.get().setUsePayNym(usePayNym);
|
||||
}
|
||||
|
||||
if(wallet != null) {
|
||||
WalletConfig walletConfig = wallet.getMasterWalletConfig();
|
||||
if(walletConfig.isUsePayNym() != usePayNym) {
|
||||
walletConfig.setUsePayNym(usePayNym);
|
||||
EventManager.get().post(new WalletConfigChangedEvent(wallet.isMasterWallet() ? wallet : wallet.getMasterWallet()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isSelectLinkedOnly() {
|
||||
return selectLinkedOnly;
|
||||
}
|
||||
|
|
|
@ -174,7 +174,7 @@ public class CounterpartyController extends SorobanController {
|
|||
payNymAvatar.visibleProperty().bind(payNym.visibleProperty());
|
||||
payNymButton.managedProperty().bind(payNymButton.visibleProperty());
|
||||
payNymButton.visibleProperty().bind(payNym.visibleProperty().not());
|
||||
if(Config.get().isUsePayNym()) {
|
||||
if(isUsePayNym(wallet)) {
|
||||
retrievePayNym(null);
|
||||
} else {
|
||||
payNym.setVisible(false);
|
||||
|
@ -270,7 +270,7 @@ public class CounterpartyController extends SorobanController {
|
|||
private void updateMixPartner(PaymentCode paymentCodeInitiator, CahootsType cahootsType) {
|
||||
String code = paymentCodeInitiator.toString();
|
||||
mixingPartner.setText(code.substring(0, 12) + "..." + code.substring(code.length() - 5));
|
||||
if(Config.get().isUsePayNym()) {
|
||||
if(isUsePayNym(wallet)) {
|
||||
mixPartnerAvatar.setPaymentCode(paymentCodeInitiator);
|
||||
AppServices.getPayNymService().getPayNym(paymentCodeInitiator.toString()).subscribe(payNym -> {
|
||||
mixingPartner.setText(payNym.nymName());
|
||||
|
@ -351,7 +351,7 @@ public class CounterpartyController extends SorobanController {
|
|||
}
|
||||
|
||||
private void followPaymentCode(PaymentCode paymentCodeInitiator) {
|
||||
if(Config.get().isUsePayNym()) {
|
||||
if(isUsePayNym(wallet)) {
|
||||
PayNymService payNymService = AppServices.getPayNymService();
|
||||
payNymService.getAuthToken(wallet, new HashMap<>()).subscribe(authToken -> {
|
||||
String signature = payNymService.getSignature(wallet, authToken);
|
||||
|
@ -393,7 +393,7 @@ public class CounterpartyController extends SorobanController {
|
|||
}
|
||||
|
||||
public void retrievePayNym(ActionEvent event) {
|
||||
Config.get().setUsePayNym(true);
|
||||
setUsePayNym(wallet, true);
|
||||
|
||||
PayNymService payNymService = AppServices.getPayNymService();
|
||||
payNymService.createPayNym(wallet).subscribe(createMap -> {
|
||||
|
|
|
@ -158,7 +158,7 @@ public class InitiatorController extends SorobanController {
|
|||
if(newValue != null) {
|
||||
if(newValue.startsWith("P") && newValue.contains("...") && newValue.length() == 20 && counterpartyPaymentCode.get() != null) {
|
||||
//Assumed valid payment code
|
||||
} else if(Config.get().isUsePayNym() && PAYNYM_REGEX.matcher(newValue).matches()) {
|
||||
} else if(isUsePayNym(wallet) && PAYNYM_REGEX.matcher(newValue).matches()) {
|
||||
if(!newValue.equals(counterpartyPayNymName.get())) {
|
||||
searchPayNyms(newValue);
|
||||
}
|
||||
|
@ -233,7 +233,7 @@ public class InitiatorController extends SorobanController {
|
|||
payNymFollowers.prefWidthProperty().bind(counterparty.widthProperty());
|
||||
payNymFollowers.valueProperty().addListener((observable, oldValue, payNym) -> {
|
||||
if(payNym == FIND_FOLLOWERS) {
|
||||
Config.get().setUsePayNym(true);
|
||||
setUsePayNym(wallet, true);
|
||||
setPayNymFollowers();
|
||||
} else if(payNym != null) {
|
||||
counterpartyPayNymName.set(payNym.nymName());
|
||||
|
@ -349,7 +349,7 @@ public class InitiatorController extends SorobanController {
|
|||
payNymFollowers.setItems(FXCollections.observableList(followerPayNyms));
|
||||
}, error -> {
|
||||
if(error.getMessage().endsWith("404")) {
|
||||
Config.get().setUsePayNym(false);
|
||||
setUsePayNym(masterWallet, false);
|
||||
AppServices.showErrorDialog("Could not retrieve PayNym", "This wallet does not have an associated PayNym or any followers yet. You can retrieve the PayNym using the Find PayNym button.");
|
||||
} else {
|
||||
log.warn("Could not retrieve followers: ", error);
|
||||
|
|
|
@ -10,7 +10,10 @@ import com.sparrowwallet.drongo.protocol.TransactionOutput;
|
|||
import com.sparrowwallet.drongo.psbt.PSBT;
|
||||
import com.sparrowwallet.drongo.psbt.PSBTParseException;
|
||||
import com.sparrowwallet.drongo.wallet.*;
|
||||
import com.sparrowwallet.sparrow.EventManager;
|
||||
import com.sparrowwallet.sparrow.control.TransactionDiagram;
|
||||
import com.sparrowwallet.sparrow.event.WalletConfigChangedEvent;
|
||||
import com.sparrowwallet.sparrow.io.Config;
|
||||
import com.sparrowwallet.sparrow.net.ElectrumServer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -126,4 +129,29 @@ public class SorobanController {
|
|||
Taskbar.getTaskbar().requestUserAttention(true, false);
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isUsePayNym(Wallet wallet) {
|
||||
//TODO: Remove config setting
|
||||
boolean usePayNym = Config.get().isUsePayNym();
|
||||
if(usePayNym && wallet != null) {
|
||||
setUsePayNym(wallet, true);
|
||||
}
|
||||
|
||||
return usePayNym;
|
||||
}
|
||||
|
||||
protected void setUsePayNym(Wallet wallet, boolean usePayNym) {
|
||||
//TODO: Remove config setting
|
||||
if(Config.get().isUsePayNym() != usePayNym) {
|
||||
Config.get().setUsePayNym(usePayNym);
|
||||
}
|
||||
|
||||
if(wallet != null) {
|
||||
WalletConfig walletConfig = wallet.getMasterWalletConfig();
|
||||
if(walletConfig.isUsePayNym() != usePayNym) {
|
||||
walletConfig.setUsePayNym(usePayNym);
|
||||
EventManager.get().post(new WalletConfigChangedEvent(wallet.isMasterWallet() ? wallet : wallet.getMasterWallet()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import com.sparrowwallet.sparrow.control.*;
|
|||
import com.sparrowwallet.sparrow.event.*;
|
||||
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
|
||||
import com.sparrowwallet.sparrow.io.Config;
|
||||
import com.sparrowwallet.sparrow.io.Storage;
|
||||
import com.sparrowwallet.sparrow.net.ExchangeSource;
|
||||
import com.sparrowwallet.sparrow.paynym.PayNym;
|
||||
import com.sparrowwallet.sparrow.paynym.PayNymAddress;
|
||||
|
@ -39,6 +40,7 @@ import javafx.collections.ListChangeListener;
|
|||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.Initializable;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.*;
|
||||
import org.controlsfx.glyphfont.Glyph;
|
||||
import org.controlsfx.validation.ValidationResult;
|
||||
|
@ -176,7 +178,7 @@ public class PaymentController extends WalletFormController implements Initializ
|
|||
setGraphic(null);
|
||||
} else {
|
||||
setText(wallet.getFullDisplayName() + (wallet == sendController.getWalletForm().getWallet() ? " (Consolidation)" : ""));
|
||||
setGraphic(wallet == payNymWallet ? getPayNymGlyph() : null);
|
||||
setGraphic(getOpenWalletIcon(wallet));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -325,6 +327,20 @@ public class PaymentController extends WalletFormController implements Initializ
|
|||
openWallets.setItems(FXCollections.observableList(openWalletList));
|
||||
}
|
||||
|
||||
private Node getOpenWalletIcon(Wallet wallet) {
|
||||
if(wallet == payNymWallet) {
|
||||
return getPayNymGlyph();
|
||||
}
|
||||
|
||||
Wallet masterWallet = wallet.isMasterWallet() ? wallet : wallet.getMasterWallet();
|
||||
Storage storage = AppServices.get().getOpenWallets().get(masterWallet);
|
||||
if(storage != null) {
|
||||
return new WalletIcon(storage, masterWallet);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void addValidation(ValidationSupport validationSupport) {
|
||||
this.validationSupport = validationSupport;
|
||||
|
||||
|
|
|
@ -7,7 +7,9 @@ import com.sparrowwallet.drongo.wallet.*;
|
|||
import com.sparrowwallet.sparrow.AppServices;
|
||||
import com.sparrowwallet.sparrow.EventManager;
|
||||
import com.sparrowwallet.sparrow.WalletTabData;
|
||||
import com.sparrowwallet.sparrow.control.WalletIcon;
|
||||
import com.sparrowwallet.sparrow.event.*;
|
||||
import com.sparrowwallet.sparrow.io.ImageUtils;
|
||||
import com.sparrowwallet.sparrow.io.StorageException;
|
||||
import com.sparrowwallet.sparrow.net.AllHistoryChangedException;
|
||||
import com.sparrowwallet.sparrow.net.ElectrumServer;
|
||||
|
@ -561,6 +563,13 @@ public class WalletForm {
|
|||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void walletConfigChanged(WalletConfigChangedEvent event) {
|
||||
if(event.getWallet() == wallet) {
|
||||
Platform.runLater(() -> EventManager.get().post(new WalletDataChangedEvent(wallet)));
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void walletMixConfigChanged(WalletMixConfigChangedEvent event) {
|
||||
if(event.getWallet() == wallet) {
|
||||
|
@ -660,4 +669,18 @@ public class WalletForm {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void payNymImageLoaded(PayNymImageLoadedEvent event) {
|
||||
if(wallet.isMasterWallet() && wallet.hasPaymentCode() && event.getPaymentCode().equals(wallet.getPaymentCode())) {
|
||||
WalletConfig walletConfig = wallet.getMasterWalletConfig();
|
||||
if(!walletConfig.isUserIcon()) {
|
||||
byte[] iconData = ImageUtils.resize(event.getImage(), WalletIcon.SAVE_WIDTH, WalletIcon.SAVE_HEIGHT);
|
||||
if(walletConfig.getIconData() == null || !Arrays.equals(walletConfig.getIconData(), iconData)) {
|
||||
walletConfig.setIconData(iconData, false);
|
||||
EventManager.get().post(new WalletConfigChangedEvent(wallet));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,4 +46,5 @@ open module com.sparrowwallet.sparrow {
|
|||
requires co.nstant.in.cbor;
|
||||
requires com.github.librepdf.openpdf;
|
||||
requires com.googlecode.lanterna;
|
||||
requires net.coobird.thumbnailator;
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
create table walletConfig (id identity not null, iconData varbinary(4096), userIcon boolean, usePayNym boolean, wallet bigint not null);
|
BIN
src/main/resources/image/bitbox02-icon.png
Normal file
After Width: | Height: | Size: 550 B |
BIN
src/main/resources/image/bitbox02-icon@2x.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
src/main/resources/image/coldcard-icon.png
Normal file
After Width: | Height: | Size: 287 B |
BIN
src/main/resources/image/coldcard-icon@2x.png
Normal file
After Width: | Height: | Size: 741 B |
BIN
src/main/resources/image/ledger-icon.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
src/main/resources/image/ledger-icon@2x.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
src/main/resources/image/passport-icon.png
Normal file
After Width: | Height: | Size: 558 B |
BIN
src/main/resources/image/passport-icon@2x.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
src/main/resources/image/seedsigner-icon.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
src/main/resources/image/seedsigner-icon@2x.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
src/main/resources/image/trezor-icon.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
src/main/resources/image/trezor-icon@2x.png
Normal file
After Width: | Height: | Size: 1.9 KiB |