|
|
@ -5,20 +5,22 @@ import com.google.zxing.client.j2se.MatrixToImageConfig; |
|
|
|
import com.google.zxing.client.j2se.MatrixToImageWriter; |
|
|
|
import com.google.zxing.common.BitMatrix; |
|
|
|
import com.google.zxing.qrcode.QRCodeWriter; |
|
|
|
import com.sparrowwallet.hummingbird.LegacyUREncoder; |
|
|
|
import com.sparrowwallet.hummingbird.registry.RegistryType; |
|
|
|
import com.sparrowwallet.sparrow.AppController; |
|
|
|
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5; |
|
|
|
import com.sparrowwallet.sparrow.io.ImportException; |
|
|
|
import com.sparrowwallet.hummingbird.UR; |
|
|
|
import com.sparrowwallet.hummingbird.UREncoder; |
|
|
|
import javafx.concurrent.ScheduledService; |
|
|
|
import javafx.concurrent.Task; |
|
|
|
import javafx.scene.control.ButtonBar; |
|
|
|
import javafx.scene.control.ButtonType; |
|
|
|
import javafx.scene.control.Dialog; |
|
|
|
import javafx.scene.control.DialogPane; |
|
|
|
import javafx.scene.Node; |
|
|
|
import javafx.scene.control.*; |
|
|
|
import javafx.scene.image.Image; |
|
|
|
import javafx.scene.image.ImageView; |
|
|
|
import javafx.scene.layout.StackPane; |
|
|
|
import javafx.util.Duration; |
|
|
|
import org.controlsfx.glyphfont.Glyph; |
|
|
|
import org.controlsfx.tools.Borders; |
|
|
|
import org.slf4j.Logger; |
|
|
|
import org.slf4j.LoggerFactory; |
|
|
@ -26,30 +28,41 @@ import org.slf4j.LoggerFactory; |
|
|
|
import java.io.ByteArrayInputStream; |
|
|
|
import java.io.ByteArrayOutputStream; |
|
|
|
|
|
|
|
@SuppressWarnings("deprecation") |
|
|
|
public class QRDisplayDialog extends Dialog<UR> { |
|
|
|
private static final Logger log = LoggerFactory.getLogger(QRDisplayDialog.class); |
|
|
|
|
|
|
|
private static final int MIN_FRAGMENT_LENGTH = 10; |
|
|
|
private static final int MAX_FRAGMENT_LENGTH = 100; |
|
|
|
|
|
|
|
private static final int ANIMATION_PERIOD_MILLIS = 200; |
|
|
|
private static final int ANIMATION_PERIOD_MILLIS = 400; |
|
|
|
|
|
|
|
private final UR ur; |
|
|
|
private final UREncoder encoder; |
|
|
|
|
|
|
|
private final ImageView qrImageView; |
|
|
|
|
|
|
|
private AnimateQRService animateQRService; |
|
|
|
private String currentPart; |
|
|
|
|
|
|
|
public QRDisplayDialog(String type, byte[] data) throws UR.URException { |
|
|
|
this(UR.fromBytes(type, data)); |
|
|
|
private boolean useLegacyEncoding; |
|
|
|
private String[] legacyParts; |
|
|
|
private int legacyPartIndex; |
|
|
|
|
|
|
|
public QRDisplayDialog(String type, byte[] data, boolean addLegacyEncodingOption) throws UR.URException { |
|
|
|
this(UR.fromBytes(type, data), addLegacyEncodingOption); |
|
|
|
} |
|
|
|
|
|
|
|
public QRDisplayDialog(UR ur) { |
|
|
|
this(ur, false); |
|
|
|
} |
|
|
|
|
|
|
|
public QRDisplayDialog(UR ur, boolean addLegacyEncodingOption) { |
|
|
|
this.ur = ur; |
|
|
|
this.encoder = new UREncoder(ur, MAX_FRAGMENT_LENGTH, MIN_FRAGMENT_LENGTH, 0); |
|
|
|
|
|
|
|
final DialogPane dialogPane = getDialogPane(); |
|
|
|
final DialogPane dialogPane = new QRDisplayDialogPane(); |
|
|
|
setDialogPane(dialogPane); |
|
|
|
AppController.setStageIcon(dialogPane.getScene().getWindow()); |
|
|
|
|
|
|
|
StackPane stackPane = new StackPane(); |
|
|
@ -62,7 +75,7 @@ public class QRDisplayDialog extends Dialog<UR> { |
|
|
|
if(encoder.isSinglePart()) { |
|
|
|
qrImageView.setImage(getQrCode(currentPart)); |
|
|
|
} else { |
|
|
|
AnimateQRService animateQRService = new AnimateQRService(); |
|
|
|
animateQRService = new AnimateQRService(); |
|
|
|
animateQRService.setPeriod(Duration.millis(ANIMATION_PERIOD_MILLIS)); |
|
|
|
animateQRService.start(); |
|
|
|
setOnCloseRequest(event -> { |
|
|
@ -71,7 +84,13 @@ public class QRDisplayDialog extends Dialog<UR> { |
|
|
|
} |
|
|
|
|
|
|
|
final ButtonType cancelButtonType = new javafx.scene.control.ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE); |
|
|
|
dialogPane.getButtonTypes().addAll(cancelButtonType); |
|
|
|
dialogPane.getButtonTypes().add(cancelButtonType); |
|
|
|
|
|
|
|
if(addLegacyEncodingOption) { |
|
|
|
final ButtonType legacyEncodingButtonType = new javafx.scene.control.ButtonType("Use Legacy Encoding (Cobo Vault)", ButtonBar.ButtonData.LEFT); |
|
|
|
dialogPane.getButtonTypes().add(legacyEncodingButtonType); |
|
|
|
} |
|
|
|
|
|
|
|
dialogPane.setPrefWidth(500); |
|
|
|
dialogPane.setPrefHeight(550); |
|
|
|
|
|
|
@ -101,8 +120,16 @@ public class QRDisplayDialog extends Dialog<UR> { |
|
|
|
} |
|
|
|
|
|
|
|
private void nextPart() { |
|
|
|
String fragment = encoder.nextPart(); |
|
|
|
currentPart = fragment.toUpperCase(); |
|
|
|
if(!useLegacyEncoding) { |
|
|
|
String fragment = encoder.nextPart(); |
|
|
|
currentPart = fragment.toUpperCase(); |
|
|
|
} else { |
|
|
|
currentPart = legacyParts[legacyPartIndex]; |
|
|
|
legacyPartIndex++; |
|
|
|
if(legacyPartIndex > legacyParts.length - 1) { |
|
|
|
legacyPartIndex = 0; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private Image getQrCode(String fragment) { |
|
|
@ -122,6 +149,44 @@ public class QRDisplayDialog extends Dialog<UR> { |
|
|
|
return null; |
|
|
|
} |
|
|
|
|
|
|
|
private void setUseLegacyEncoding(boolean useLegacyEncoding) { |
|
|
|
if(useLegacyEncoding) { |
|
|
|
try { |
|
|
|
//Force to be bytes type for legacy encoding
|
|
|
|
LegacyUREncoder legacyEncoder = new LegacyUREncoder(new UR(RegistryType.BYTES.toString(), ur.getCborBytes())); |
|
|
|
this.legacyParts = legacyEncoder.encode(); |
|
|
|
this.useLegacyEncoding = true; |
|
|
|
|
|
|
|
if(legacyParts.length == 1) { |
|
|
|
if(animateQRService != null) { |
|
|
|
animateQRService.cancel(); |
|
|
|
} |
|
|
|
|
|
|
|
nextPart(); |
|
|
|
qrImageView.setImage(getQrCode(currentPart)); |
|
|
|
} else if(!animateQRService.isRunning()) { |
|
|
|
animateQRService.reset(); |
|
|
|
animateQRService.start(); |
|
|
|
} |
|
|
|
} catch(UR.InvalidTypeException e) { |
|
|
|
//Can't happen
|
|
|
|
} |
|
|
|
} else { |
|
|
|
this.useLegacyEncoding = false; |
|
|
|
|
|
|
|
if(encoder.isSinglePart()) { |
|
|
|
if(animateQRService != null) { |
|
|
|
animateQRService.cancel(); |
|
|
|
} |
|
|
|
|
|
|
|
qrImageView.setImage(getQrCode(currentPart)); |
|
|
|
} else if(!animateQRService.isRunning()) { |
|
|
|
animateQRService.reset(); |
|
|
|
animateQRService.start(); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private class AnimateQRService extends ScheduledService<Boolean> { |
|
|
|
@Override |
|
|
|
protected Task<Boolean> createTask() { |
|
|
@ -136,4 +201,44 @@ public class QRDisplayDialog extends Dialog<UR> { |
|
|
|
}; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private class QRDisplayDialogPane extends DialogPane { |
|
|
|
@Override |
|
|
|
protected Node createButton(ButtonType buttonType) { |
|
|
|
if(buttonType.getButtonData() == ButtonBar.ButtonData.LEFT) { |
|
|
|
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); |
|
|
|
}); |
|
|
|
|
|
|
|
return legacy; |
|
|
|
} |
|
|
|
|
|
|
|
return super.createButton(buttonType); |
|
|
|
} |
|
|
|
|
|
|
|
private void setLegacyGraphic(ToggleButton legacy, boolean useLegacyEncoding) { |
|
|
|
if(useLegacyEncoding) { |
|
|
|
legacy.setGraphic(getGlyph(FontAwesome5.Glyph.CHECK_CIRCLE, "success")); |
|
|
|
} else { |
|
|
|
legacy.setGraphic(getGlyph(FontAwesome5.Glyph.QUESTION_CIRCLE, null)); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private Glyph getGlyph(FontAwesome5.Glyph glyphName, String styleClass) { |
|
|
|
Glyph glyph = new Glyph(FontAwesome5.FONT_NAME, glyphName); |
|
|
|
glyph.setFontSize(12); |
|
|
|
if(styleClass != null) { |
|
|
|
glyph.getStyleClass().add(styleClass); |
|
|
|
} |
|
|
|
|
|
|
|
return glyph; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|