diff --git a/src/main/java/com/sparrowwallet/sparrow/terminal/wallet/QRCodeDialog.java b/src/main/java/com/sparrowwallet/sparrow/terminal/wallet/QRCodeDialog.java new file mode 100644 index 00000000..e644dedd --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/terminal/wallet/QRCodeDialog.java @@ -0,0 +1,38 @@ +package com.sparrowwallet.sparrow.terminal.wallet; + +import com.google.zxing.BarcodeFormat; +import com.google.zxing.EncodeHintType; +import com.google.zxing.WriterException; +import com.google.zxing.common.BitMatrix; +import com.google.zxing.qrcode.QRCodeWriter; +import com.googlecode.lanterna.gui2.*; +import com.googlecode.lanterna.gui2.dialogs.DialogWindow; +import com.googlecode.lanterna.input.KeyStroke; + +import java.util.List; +import java.util.Map; + +public class QRCodeDialog extends DialogWindow { + public QRCodeDialog(String data) throws WriterException { + super(data); + + setHints(List.of(Hint.CENTERED)); + + Panel mainPanel = new Panel(new GridLayout(1).setLeftMarginSize(1).setRightMarginSize(1).setTopMarginSize(1)); + + QRCodeWriter qrCodeWriter = new QRCodeWriter(); + BitMatrix qrMatrix = qrCodeWriter.encode(data, BarcodeFormat.QR_CODE, 33, 33, Map.of(EncodeHintType.MARGIN, 0)); + + ImageComponent imageComponent = new ImageComponent(); + imageComponent.setTextImage(new QRTextImage(qrMatrix)); + mainPanel.addComponent(imageComponent); + + setComponent(mainPanel); + } + + @Override + public boolean handleInput(KeyStroke key) { + close(); + return true; + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/terminal/wallet/QRTextImage.java b/src/main/java/com/sparrowwallet/sparrow/terminal/wallet/QRTextImage.java new file mode 100644 index 00000000..9736ca09 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/terminal/wallet/QRTextImage.java @@ -0,0 +1,93 @@ +package com.sparrowwallet.sparrow.terminal.wallet; + +import com.google.zxing.common.BitMatrix; +import com.googlecode.lanterna.*; +import com.googlecode.lanterna.graphics.AbstractTextGraphics; +import com.googlecode.lanterna.graphics.TextGraphics; +import com.googlecode.lanterna.graphics.TextImage; + +import java.util.EnumSet; + +public class QRTextImage implements TextImage { + private final BitMatrix bitMatrix; + private final TerminalSize size; + + private static final TextCharacter REVERSE_CHARACTER = TextCharacter.fromString(" ", TextColor.ANSI.DEFAULT, TextColor.ANSI.DEFAULT, EnumSet.of(SGR.REVERSE))[0]; + + public QRTextImage(BitMatrix bitMatrix) { + this.bitMatrix = bitMatrix; + this.size = new TerminalSize(bitMatrix.getWidth() * 2, bitMatrix.getHeight()); + } + + @Override + public TerminalSize getSize() { + return size; + } + + @Override + public TextCharacter getCharacterAt(TerminalPosition position) { + return getCharacterAt(position.getColumn(), position.getRow()); + } + + @Override + public TextCharacter getCharacterAt(int column, int row) { + boolean filled = bitMatrix.get(column / 2, row); + return filled ? TextCharacter.DEFAULT_CHARACTER : REVERSE_CHARACTER; + } + + @Override + public void setCharacterAt(TerminalPosition position, TextCharacter character) { + throw new UnsupportedOperationException("Cannot set character in QR Code"); + } + + @Override + public void setCharacterAt(int column, int row, TextCharacter character) { + throw new UnsupportedOperationException("Cannot set character in QR Code"); + } + + @Override + public void setAll(TextCharacter character) { + throw new UnsupportedOperationException("Cannot set character in QR Code"); + } + + @Override + public TextGraphics newTextGraphics() { + return new AbstractTextGraphics() { + @Override + public TextGraphics setCharacter(int columnIndex, int rowIndex, TextCharacter textCharacter) { + QRTextImage.this.setCharacterAt(columnIndex, rowIndex, textCharacter); + return this; + } + + @Override + public TextCharacter getCharacter(int column, int row) { + return QRTextImage.this.getCharacterAt(column, row); + } + + @Override + public TerminalSize getSize() { + return size; + } + }; + } + + @Override + public TextImage resize(TerminalSize newSize, TextCharacter filler) { + throw new UnsupportedOperationException("Cannot resize QR Code"); + } + + @Override + public void copyTo(TextImage destination) { + throw new UnsupportedOperationException("Cannot copy QR Code"); + } + + @Override + public void copyTo(TextImage destination, int startRowIndex, int rows, int startColumnIndex, int columns, int destinationRowOffset, int destinationColumnOffset) { + throw new UnsupportedOperationException("Cannot copy QR Code"); + } + + @Override + public void scrollLines(int firstLine, int lastLine, int distance) { + throw new UnsupportedOperationException("Cannot scroll QR Code"); + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/terminal/wallet/ReceiveDialog.java b/src/main/java/com/sparrowwallet/sparrow/terminal/wallet/ReceiveDialog.java index 35aeec65..acbe9745 100644 --- a/src/main/java/com/sparrowwallet/sparrow/terminal/wallet/ReceiveDialog.java +++ b/src/main/java/com/sparrowwallet/sparrow/terminal/wallet/ReceiveDialog.java @@ -1,7 +1,6 @@ package com.sparrowwallet.sparrow.terminal.wallet; import com.google.common.eventbus.Subscribe; -import com.googlecode.lanterna.TerminalSize; import com.googlecode.lanterna.gui2.*; import com.sparrowwallet.drongo.KeyDerivation; import com.sparrowwallet.drongo.KeyPurpose; @@ -14,6 +13,8 @@ import com.sparrowwallet.sparrow.terminal.SparrowTerminal; import com.sparrowwallet.sparrow.wallet.Function; import com.sparrowwallet.sparrow.wallet.NodeEntry; import com.sparrowwallet.sparrow.wallet.WalletForm; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.text.DateFormat; import java.text.SimpleDateFormat; @@ -21,6 +22,7 @@ import java.util.List; import java.util.Set; public class ReceiveDialog extends WalletDialog { + private static final Logger log = LoggerFactory.getLogger(ReceiveDialog.class); private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm"); private final Label address; @@ -50,7 +52,7 @@ public class ReceiveDialog extends WalletDialog { buttonPanel.addComponent(new Button("Back", () -> onBack(Function.RECEIVE))); buttonPanel.addComponent(new Button("Get Fresh Address", this::refreshAddress).setLayoutData(GridLayout.createLayoutData(GridLayout.Alignment.CENTER, GridLayout.Alignment.CENTER, true, false))); - mainPanel.addComponent(new EmptySpace(TerminalSize.ONE)); + mainPanel.addComponent(new Button("Show QR", this::showQR)); buttonPanel.setLayoutData(GridLayout.createLayoutData(GridLayout.Alignment.END, GridLayout.Alignment.CENTER,false,false)).addTo(mainPanel); setComponent(mainPanel); @@ -58,6 +60,20 @@ public class ReceiveDialog extends WalletDialog { refreshAddress(); } + public void showQR() { + if(currentEntry == null) { + return; + } + + try { + QRCodeDialog qrCodeDialog = new QRCodeDialog(currentEntry.getAddress().toString()); + qrCodeDialog.showDialog(SparrowTerminal.get().getGui()); + } catch(Exception e) { + log.error("Error creating QR", e); + AppServices.showErrorDialog("Error creating QR", e.getMessage()); + } + } + public void refreshAddress() { SparrowTerminal.get().getGuiThread().invokeLater(() -> { NodeEntry freshEntry = getWalletForm().getFreshNodeEntry(KeyPurpose.RECEIVE, currentEntry);