diff --git a/build.gradle b/build.gradle index 5761dc7c..dcd3e3e0 100644 --- a/build.gradle +++ b/build.gradle @@ -51,7 +51,7 @@ dependencies { implementation('com.github.arteam:simple-json-rpc-server:1.0') { exclude group: 'org.slf4j' } - implementation('com.sparrowwallet:hummingbird:1.5.2') + implementation('com.sparrowwallet:hummingbird:1.5.3') implementation('com.nativelibs4java:bridj:0.7-20140918-3') { exclude group: 'com.google.android.tools', module: 'dx' } diff --git a/src/main/java/com/sparrowwallet/sparrow/control/QRScanDialog.java b/src/main/java/com/sparrowwallet/sparrow/control/QRScanDialog.java index 0ebfce5d..874ea25a 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/QRScanDialog.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/QRScanDialog.java @@ -28,13 +28,16 @@ import com.sparrowwallet.sparrow.AppServices; import com.sparrowwallet.sparrow.glyphfont.FontAwesome5; import com.sparrowwallet.sparrow.io.Config; import javafx.application.Platform; +import javafx.beans.property.DoubleProperty; import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleDoubleProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; +import javafx.geometry.Insets; import javafx.scene.Node; import javafx.scene.control.*; -import javafx.scene.layout.StackPane; +import javafx.scene.layout.*; import org.controlsfx.glyphfont.Glyph; import org.controlsfx.tools.Borders; import org.slf4j.Logger; @@ -59,13 +62,14 @@ public class QRScanDialog extends Dialog { private final WebcamService webcamService; private List parts; - private boolean isUr; private QRScanDialog.Result result; private static final Pattern PART_PATTERN = Pattern.compile("p(\\d+)of(\\d+) (.+)"); private final ObjectProperty webcamResolutionProperty = new SimpleObjectProperty<>(WebcamResolution.VGA); + private final DoubleProperty percentComplete = new SimpleDoubleProperty(0.0); + public QRScanDialog() { this.decoder = new URDecoder(); this.legacyDecoder = new LegacyURDecoder(); @@ -83,8 +87,23 @@ public class QRScanDialog extends Dialog { StackPane stackPane = new StackPane(); stackPane.getChildren().add(webcamView.getView()); + Node wrappedView = Borders.wrap(stackPane).lineBorder().buildAll(); - dialogPane.setContent(Borders.wrap(stackPane).lineBorder().buildAll()); + ProgressBar progressBar = new ProgressBar(); + progressBar.setMinHeight(20); + progressBar.setPadding(new Insets(0, 10, 0, 10)); + progressBar.setPrefWidth(Integer.MAX_VALUE); + progressBar.progressProperty().bind(percentComplete); + webcamService.openingProperty().addListener((observable, oldValue, newValue) -> { + if(percentComplete.get() <= 0.0) { + Platform.runLater(() -> percentComplete.set(newValue ? 0.0 : -1.0)); + } + }); + + VBox vBox = new VBox(20); + vBox.getChildren().addAll(wrappedView, progressBar); + + dialogPane.setContent(vBox); webcamService.resultProperty().addListener(new QRResultListener()); webcamService.setOnFailed(failedEvent -> { @@ -111,7 +130,7 @@ public class QRScanDialog extends Dialog { final ButtonType hdButtonType = new javafx.scene.control.ButtonType("Use HD Capture", ButtonBar.ButtonData.LEFT); dialogPane.getButtonTypes().addAll(hdButtonType, cancelButtonType); dialogPane.setPrefWidth(646); - dialogPane.setPrefHeight(webcamResolutionProperty.get() == WebcamResolution.HD ? 450 : 550); + dialogPane.setPrefHeight(webcamResolutionProperty.get() == WebcamResolution.HD ? 490 : 590); setResultConverter(dialogButton -> dialogButton != cancelButtonType ? result : null); } @@ -127,11 +146,10 @@ public class QRScanDialog extends Dialog { String qrtext = qrResult.getText(); Matcher partMatcher = PART_PATTERN.matcher(qrtext); - if(isUr || qrtext.toLowerCase().startsWith(UR.UR_PREFIX)) { - isUr = true; - - if(LegacyURDecoder.isLegacyURFragment(qrtext)) { + if(qrtext.toLowerCase().startsWith(UR.UR_PREFIX)) { + if(LegacyURDecoder.isLegacyURFragment(qrtext.toLowerCase())) { legacyDecoder.receivePart(qrtext.toLowerCase()); + Platform.runLater(() -> percentComplete.setValue(legacyDecoder.getPercentComplete())); if(legacyDecoder.isComplete()) { try { @@ -143,6 +161,7 @@ public class QRScanDialog extends Dialog { } } else { decoder.receivePart(qrtext); + Platform.runLater(() -> percentComplete.setValue(decoder.getEstimatedPercentComplete())); if(decoder.getResult() != null) { URDecoder.Result urResult = decoder.getResult(); @@ -164,6 +183,10 @@ public class QRScanDialog extends Dialog { } parts.set(m - 1, payload); + if(n > 0) { + Platform.runLater(() -> percentComplete.setValue((double)parts.stream().filter(Objects::nonNull).count() / n)); + } + if(parts.stream().filter(Objects::nonNull).count() == n) { String complete = String.join("", parts); try { @@ -484,6 +507,7 @@ public class QRScanDialog extends Dialog { private class QRScanDialogPane extends DialogPane { @Override protected Node createButton(ButtonType buttonType) { + Node button = null; if(buttonType.getButtonData() == ButtonBar.ButtonData.LEFT) { ToggleButton hd = new ToggleButton(buttonType.getText()); hd.setSelected(webcamResolutionProperty.get() == WebcamResolution.HD); @@ -497,10 +521,13 @@ public class QRScanDialog extends Dialog { setHdGraphic(hd, newValue); }); - return hd; + button = hd; + } else { + button = super.createButton(buttonType); } - return super.createButton(buttonType); + button.disableProperty().bind(webcamService.openingProperty()); + return button; } private void setHdGraphic(ToggleButton hd, boolean isHd) { diff --git a/src/main/java/com/sparrowwallet/sparrow/control/WebcamService.java b/src/main/java/com/sparrowwallet/sparrow/control/WebcamService.java index ef082cbc..59ef8ed3 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/WebcamService.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/WebcamService.java @@ -6,7 +6,9 @@ import com.github.sarxos.webcam.WebcamResolution; import com.google.zxing.*; import com.google.zxing.client.j2se.BufferedImageLuminanceSource; import com.google.zxing.common.HybridBinarizer; +import javafx.beans.property.BooleanProperty; import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.concurrent.Service; import javafx.concurrent.Task; @@ -20,6 +22,7 @@ import java.util.concurrent.TimeUnit; public class WebcamService extends Service { private WebcamResolution resolution; private final WebcamListener listener; + private BooleanProperty opening = new SimpleBooleanProperty(false); private final ObjectProperty resultProperty = new SimpleObjectProperty<>(null); @@ -41,7 +44,9 @@ public class WebcamService extends Service { cam.addWebcamListener(listener); } + opening.set(true); cam.open(); + opening.set(false); while(!isCancelled()) { if(cam.isImageNew()) { BufferedImage bimg = cam.getImage(); @@ -49,10 +54,12 @@ public class WebcamService extends Service { readQR(bimg); } } - cam.close(); return getValue(); } finally { - cam.close(); + opening.set(false); + if(!cam.close()) { + cam.close(); + } } } }; @@ -89,4 +96,12 @@ public class WebcamService extends Service { public void setResolution(WebcamResolution resolution) { this.resolution = resolution; } + + public boolean isOpening() { + return opening.get(); + } + + public BooleanProperty openingProperty() { + return opening; + } }