diff --git a/src/main/java/com/sparrowwallet/sparrow/control/QRDensity.java b/src/main/java/com/sparrowwallet/sparrow/control/QRDensity.java new file mode 100644 index 00000000..f74a5d09 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/control/QRDensity.java @@ -0,0 +1,22 @@ +package com.sparrowwallet.sparrow.control; + +public enum QRDensity { + NORMAL("Normal", 250), + LOW("Low", 80); + + private final String name; + private final int maxFragmentLength; + + QRDensity(String name, int maxFragmentLength) { + this.name = name; + this.maxFragmentLength = maxFragmentLength; + } + + public String getName() { + return name; + } + + public int getMaxFragmentLength() { + return maxFragmentLength; + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/control/QRDisplayDialog.java b/src/main/java/com/sparrowwallet/sparrow/control/QRDisplayDialog.java index 5107cb37..19339379 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/QRDisplayDialog.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/QRDisplayDialog.java @@ -9,6 +9,7 @@ import com.sparrowwallet.hummingbird.LegacyUREncoder; import com.sparrowwallet.hummingbird.registry.RegistryType; import com.sparrowwallet.sparrow.AppServices; import com.sparrowwallet.sparrow.glyphfont.FontAwesome5; +import com.sparrowwallet.sparrow.io.Config; import com.sparrowwallet.sparrow.io.ImportException; import com.sparrowwallet.hummingbird.UR; import com.sparrowwallet.hummingbird.UREncoder; @@ -28,13 +29,13 @@ import org.slf4j.LoggerFactory; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.util.Locale; +import java.util.Optional; @SuppressWarnings("deprecation") public class QRDisplayDialog extends Dialog { private static final Logger log = LoggerFactory.getLogger(QRDisplayDialog.class); private static final int MIN_FRAGMENT_LENGTH = 10; - private static final int MAX_FRAGMENT_LENGTH = 250; private static final int ANIMATION_PERIOD_MILLIS = 200; @@ -42,17 +43,20 @@ public class QRDisplayDialog extends Dialog { private static final int QR_HEIGHT = 480; private final UR ur; - private final UREncoder encoder; + private UREncoder encoder; private final ImageView qrImageView; private AnimateQRService animateQRService; private String currentPart; + private boolean addLegacyEncodingOption; private boolean useLegacyEncoding; private String[] legacyParts; private int legacyPartIndex; + private static boolean initialDensityChange; + public QRDisplayDialog(String type, byte[] data, boolean addLegacyEncodingOption) throws UR.URException { this(UR.fromBytes(type, data), addLegacyEncodingOption); } @@ -63,7 +67,8 @@ public class QRDisplayDialog extends Dialog { public QRDisplayDialog(UR ur, boolean addLegacyEncodingOption) { this.ur = ur; - this.encoder = new UREncoder(ur, MAX_FRAGMENT_LENGTH, MIN_FRAGMENT_LENGTH, 0); + this.addLegacyEncodingOption = addLegacyEncodingOption; + this.encoder = new UREncoder(ur, Config.get().getQrDensity().getMaxFragmentLength(), MIN_FRAGMENT_LENGTH, 0); final DialogPane dialogPane = new QRDisplayDialogPane(); setDialogPane(dialogPane); @@ -88,6 +93,9 @@ public class QRDisplayDialog extends Dialog { if(addLegacyEncodingOption) { final ButtonType legacyEncodingButtonType = new javafx.scene.control.ButtonType("Use Legacy Encoding (Cobo Vault)", ButtonBar.ButtonData.LEFT); dialogPane.getButtonTypes().add(legacyEncodingButtonType); + } else { + final ButtonType densityButtonType = new javafx.scene.control.ButtonType("Change Density", ButtonBar.ButtonData.LEFT); + dialogPane.getButtonTypes().add(densityButtonType); } dialogPane.setPrefWidth(40 + QR_WIDTH + 40); @@ -201,6 +209,20 @@ public class QRDisplayDialog extends Dialog { } } + private void changeQRDensity() { + if(animateQRService != null) { + animateQRService.cancel(); + } + + this.encoder = new UREncoder(ur, Config.get().getQrDensity().getMaxFragmentLength(), MIN_FRAGMENT_LENGTH, 0); + nextPart(); + if(encoder.isSinglePart()) { + qrImageView.setImage(getQrCode(currentPart)); + } else { + createAnimateQRService(); + } + } + private class AnimateQRService extends ScheduledService { @Override protected Task createTask() { @@ -220,18 +242,44 @@ public class QRDisplayDialog extends Dialog { @Override protected Node createButton(ButtonType buttonType) { if(buttonType.getButtonData() == ButtonBar.ButtonData.LEFT) { - ToggleButton legacy = new ToggleButton(buttonType.getText()); - legacy.setGraphicTextGap(5); - setLegacyGraphic(legacy, false); + if(addLegacyEncodingOption) { + ToggleButton legacy = new ToggleButton(buttonType.getText()); + legacy.setGraphicTextGap(5); + setLegacyGraphic(legacy, false); - final ButtonBar.ButtonData buttonData = buttonType.getButtonData(); - ButtonBar.setButtonData(legacy, buttonData); - legacy.selectedProperty().addListener((observable, oldValue, newValue) -> { - setUseLegacyEncoding(newValue); - setLegacyGraphic(legacy, newValue); - }); + final ButtonBar.ButtonData buttonData = buttonType.getButtonData(); + ButtonBar.setButtonData(legacy, buttonData); + legacy.selectedProperty().addListener((observable, oldValue, newValue) -> { + setUseLegacyEncoding(newValue); + setLegacyGraphic(legacy, newValue); + }); - return legacy; + return legacy; + } else { + Button density = new Button(buttonType.getText()); + density.setPrefWidth(160); + density.setGraphicTextGap(5); + updateDensityButton(density); + + final ButtonBar.ButtonData buttonData = buttonType.getButtonData(); + ButtonBar.setButtonData(density, buttonData); + density.setOnAction(event -> { + if(!initialDensityChange && !encoder.isSinglePart()) { + Optional optButtonType = AppServices.showWarningDialog("Discard progress?", "Changing the QR code density means any progress on the receiving device must be discarded. Proceed?", ButtonType.NO, ButtonType.YES); + if(optButtonType.isPresent() && optButtonType.get() == ButtonType.YES) { + initialDensityChange = true; + } else { + return; + } + } + + Config.get().setQrDensity(Config.get().getQrDensity() == QRDensity.NORMAL ? QRDensity.LOW : QRDensity.NORMAL); + updateDensityButton(density); + changeQRDensity(); + }); + + return density; + } } return super.createButton(buttonType); @@ -244,6 +292,15 @@ public class QRDisplayDialog extends Dialog { legacy.setGraphic(getGlyph(FontAwesome5.Glyph.BAN)); } } + + private void updateDensityButton(Button density) { + density.setText(Config.get().getQrDensity() == QRDensity.NORMAL ? "Decrease Density" : "Increase Density"); + if(Config.get().getQrDensity() == QRDensity.NORMAL) { + density.setGraphic(getGlyph(FontAwesome5.Glyph.MAGNIFYING_GLASS_PLUS)); + } else { + density.setGraphic(getGlyph(FontAwesome5.Glyph.MAGNIFYING_GLASS_MINUS)); + } + } } protected static Glyph getGlyph(FontAwesome5.Glyph glyphName) { diff --git a/src/main/java/com/sparrowwallet/sparrow/glyphfont/FontAwesome5.java b/src/main/java/com/sparrowwallet/sparrow/glyphfont/FontAwesome5.java index 434f3f31..e66bd20c 100644 --- a/src/main/java/com/sparrowwallet/sparrow/glyphfont/FontAwesome5.java +++ b/src/main/java/com/sparrowwallet/sparrow/glyphfont/FontAwesome5.java @@ -49,6 +49,8 @@ public class FontAwesome5 extends GlyphFont { LINK('\uf0c1'), LOCK('\uf023'), LOCK_OPEN('\uf3c1'), + MAGNIFYING_GLASS_PLUS('\uf00e'), + MAGNIFYING_GLASS_MINUS('\uf010'), MINUS_CIRCLE('\uf056'), PEN_FANCY('\uf5ac'), PLUS('\uf067'), diff --git a/src/main/java/com/sparrowwallet/sparrow/io/Config.java b/src/main/java/com/sparrowwallet/sparrow/io/Config.java index b3a450dd..d2254dbc 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/Config.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/Config.java @@ -5,6 +5,7 @@ import com.sparrowwallet.drongo.BitcoinUnit; import com.sparrowwallet.sparrow.UnitFormat; import com.sparrowwallet.sparrow.Mode; import com.sparrowwallet.sparrow.Theme; +import com.sparrowwallet.sparrow.control.QRDensity; import com.sparrowwallet.sparrow.net.*; import com.sparrowwallet.sparrow.wallet.FeeRatesSelection; import com.sparrowwallet.sparrow.wallet.OptimizationStrategy; @@ -53,6 +54,7 @@ public class Config { private long dustAttackThreshold = DUST_ATTACK_THRESHOLD_SATS; private File hwi; private int enumerateHwPeriod = ENUMERATE_HW_PERIOD_SECS; + private QRDensity qrDensity; private Boolean hdCapture; private String webcamDevice; private ServerType serverType; @@ -354,6 +356,15 @@ public class Config { return enumerateHwPeriod; } + public QRDensity getQrDensity() { + return qrDensity == null ? QRDensity.NORMAL : qrDensity; + } + + public void setQrDensity(QRDensity qrDensity) { + this.qrDensity = qrDensity; + flush(); + } + public Boolean getHdCapture() { return hdCapture; } diff --git a/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java b/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java index c5257bb5..af2cc81a 100644 --- a/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java +++ b/src/main/java/com/sparrowwallet/sparrow/transaction/HeadersController.java @@ -841,7 +841,7 @@ public class HeadersController extends TransactionFormController implements Init toggleButton.setSelected(false); //TODO: Remove once Cobo Vault has upgraded to UR2.0 - boolean addLegacyEncodingOption = headersForm.getSigningWallet().getKeystores().stream().anyMatch(keystore -> keystore.getWalletModel().equals(WalletModel.COBO_VAULT) || keystore.getWalletModel().equals(WalletModel.SPARROW)); + boolean addLegacyEncodingOption = headersForm.getSigningWallet().getKeystores().stream().anyMatch(keystore -> keystore.getWalletModel().equals(WalletModel.COBO_VAULT)); CryptoPSBT cryptoPSBT = new CryptoPSBT(headersForm.getPsbt().serialize()); QRDisplayDialog qrDisplayDialog = new QRDisplayDialog(cryptoPSBT.toUR(), addLegacyEncodingOption);