fix webcam opening/closing issues, display progress bar for qr animation scanning progress

This commit is contained in:
Craig Raw 2021-03-24 12:54:14 +02:00
parent b74741bccb
commit 11a201b3f5
3 changed files with 55 additions and 13 deletions

View file

@ -51,7 +51,7 @@ dependencies {
implementation('com.github.arteam:simple-json-rpc-server:1.0') { implementation('com.github.arteam:simple-json-rpc-server:1.0') {
exclude group: 'org.slf4j' 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') { implementation('com.nativelibs4java:bridj:0.7-20140918-3') {
exclude group: 'com.google.android.tools', module: 'dx' exclude group: 'com.google.android.tools', module: 'dx'
} }

View file

@ -28,13 +28,16 @@ import com.sparrowwallet.sparrow.AppServices;
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5; import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
import com.sparrowwallet.sparrow.io.Config; import com.sparrowwallet.sparrow.io.Config;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.ObjectProperty; import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ChangeListener; import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue; import javafx.beans.value.ObservableValue;
import javafx.geometry.Insets;
import javafx.scene.Node; import javafx.scene.Node;
import javafx.scene.control.*; import javafx.scene.control.*;
import javafx.scene.layout.StackPane; import javafx.scene.layout.*;
import org.controlsfx.glyphfont.Glyph; import org.controlsfx.glyphfont.Glyph;
import org.controlsfx.tools.Borders; import org.controlsfx.tools.Borders;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -59,13 +62,14 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
private final WebcamService webcamService; private final WebcamService webcamService;
private List<String> parts; private List<String> parts;
private boolean isUr;
private QRScanDialog.Result result; private QRScanDialog.Result result;
private static final Pattern PART_PATTERN = Pattern.compile("p(\\d+)of(\\d+) (.+)"); private static final Pattern PART_PATTERN = Pattern.compile("p(\\d+)of(\\d+) (.+)");
private final ObjectProperty<WebcamResolution> webcamResolutionProperty = new SimpleObjectProperty<>(WebcamResolution.VGA); private final ObjectProperty<WebcamResolution> webcamResolutionProperty = new SimpleObjectProperty<>(WebcamResolution.VGA);
private final DoubleProperty percentComplete = new SimpleDoubleProperty(0.0);
public QRScanDialog() { public QRScanDialog() {
this.decoder = new URDecoder(); this.decoder = new URDecoder();
this.legacyDecoder = new LegacyURDecoder(); this.legacyDecoder = new LegacyURDecoder();
@ -83,8 +87,23 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
StackPane stackPane = new StackPane(); StackPane stackPane = new StackPane();
stackPane.getChildren().add(webcamView.getView()); 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.resultProperty().addListener(new QRResultListener());
webcamService.setOnFailed(failedEvent -> { webcamService.setOnFailed(failedEvent -> {
@ -111,7 +130,7 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
final ButtonType hdButtonType = new javafx.scene.control.ButtonType("Use HD Capture", ButtonBar.ButtonData.LEFT); final ButtonType hdButtonType = new javafx.scene.control.ButtonType("Use HD Capture", ButtonBar.ButtonData.LEFT);
dialogPane.getButtonTypes().addAll(hdButtonType, cancelButtonType); dialogPane.getButtonTypes().addAll(hdButtonType, cancelButtonType);
dialogPane.setPrefWidth(646); 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); setResultConverter(dialogButton -> dialogButton != cancelButtonType ? result : null);
} }
@ -127,11 +146,10 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
String qrtext = qrResult.getText(); String qrtext = qrResult.getText();
Matcher partMatcher = PART_PATTERN.matcher(qrtext); Matcher partMatcher = PART_PATTERN.matcher(qrtext);
if(isUr || qrtext.toLowerCase().startsWith(UR.UR_PREFIX)) { if(qrtext.toLowerCase().startsWith(UR.UR_PREFIX)) {
isUr = true; if(LegacyURDecoder.isLegacyURFragment(qrtext.toLowerCase())) {
if(LegacyURDecoder.isLegacyURFragment(qrtext)) {
legacyDecoder.receivePart(qrtext.toLowerCase()); legacyDecoder.receivePart(qrtext.toLowerCase());
Platform.runLater(() -> percentComplete.setValue(legacyDecoder.getPercentComplete()));
if(legacyDecoder.isComplete()) { if(legacyDecoder.isComplete()) {
try { try {
@ -143,6 +161,7 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
} }
} else { } else {
decoder.receivePart(qrtext); decoder.receivePart(qrtext);
Platform.runLater(() -> percentComplete.setValue(decoder.getEstimatedPercentComplete()));
if(decoder.getResult() != null) { if(decoder.getResult() != null) {
URDecoder.Result urResult = decoder.getResult(); URDecoder.Result urResult = decoder.getResult();
@ -164,6 +183,10 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
} }
parts.set(m - 1, payload); 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) { if(parts.stream().filter(Objects::nonNull).count() == n) {
String complete = String.join("", parts); String complete = String.join("", parts);
try { try {
@ -484,6 +507,7 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
private class QRScanDialogPane extends DialogPane { private class QRScanDialogPane extends DialogPane {
@Override @Override
protected Node createButton(ButtonType buttonType) { protected Node createButton(ButtonType buttonType) {
Node button = null;
if(buttonType.getButtonData() == ButtonBar.ButtonData.LEFT) { if(buttonType.getButtonData() == ButtonBar.ButtonData.LEFT) {
ToggleButton hd = new ToggleButton(buttonType.getText()); ToggleButton hd = new ToggleButton(buttonType.getText());
hd.setSelected(webcamResolutionProperty.get() == WebcamResolution.HD); hd.setSelected(webcamResolutionProperty.get() == WebcamResolution.HD);
@ -497,10 +521,13 @@ public class QRScanDialog extends Dialog<QRScanDialog.Result> {
setHdGraphic(hd, newValue); 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) { private void setHdGraphic(ToggleButton hd, boolean isHd) {

View file

@ -6,7 +6,9 @@ import com.github.sarxos.webcam.WebcamResolution;
import com.google.zxing.*; import com.google.zxing.*;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource; import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.common.HybridBinarizer; import com.google.zxing.common.HybridBinarizer;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty; import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleObjectProperty;
import javafx.concurrent.Service; import javafx.concurrent.Service;
import javafx.concurrent.Task; import javafx.concurrent.Task;
@ -20,6 +22,7 @@ import java.util.concurrent.TimeUnit;
public class WebcamService extends Service<Image> { public class WebcamService extends Service<Image> {
private WebcamResolution resolution; private WebcamResolution resolution;
private final WebcamListener listener; private final WebcamListener listener;
private BooleanProperty opening = new SimpleBooleanProperty(false);
private final ObjectProperty<Result> resultProperty = new SimpleObjectProperty<>(null); private final ObjectProperty<Result> resultProperty = new SimpleObjectProperty<>(null);
@ -41,7 +44,9 @@ public class WebcamService extends Service<Image> {
cam.addWebcamListener(listener); cam.addWebcamListener(listener);
} }
opening.set(true);
cam.open(); cam.open();
opening.set(false);
while(!isCancelled()) { while(!isCancelled()) {
if(cam.isImageNew()) { if(cam.isImageNew()) {
BufferedImage bimg = cam.getImage(); BufferedImage bimg = cam.getImage();
@ -49,12 +54,14 @@ public class WebcamService extends Service<Image> {
readQR(bimg); readQR(bimg);
} }
} }
cam.close();
return getValue(); return getValue();
} finally { } finally {
opening.set(false);
if(!cam.close()) {
cam.close(); cam.close();
} }
} }
}
}; };
} }
@ -89,4 +96,12 @@ public class WebcamService extends Service<Image> {
public void setResolution(WebcamResolution resolution) { public void setResolution(WebcamResolution resolution) {
this.resolution = resolution; this.resolution = resolution;
} }
public boolean isOpening() {
return opening.get();
}
public BooleanProperty openingProperty() {
return opening;
}
} }