add and integrate paynym dialog

This commit is contained in:
Craig Raw
2021-11-30 14:43:23 +02:00
parent 4edd84f6e2
commit 44194a074c
17 changed files with 834 additions and 117 deletions
@@ -17,31 +17,52 @@ import org.slf4j.LoggerFactory;
import java.io.InputStream;
import java.net.Proxy;
import java.net.URL;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public class PayNymAvatar extends StackPane {
private static final Logger log = LoggerFactory.getLogger(PayNymAvatar.class);
private final ObjectProperty<PaymentCode> paymentCodeProperty = new SimpleObjectProperty<>(null);
private static final Map<String, Image> paymentCodeCache = Collections.synchronizedMap(new HashMap<>());
private static final Map<String, Object> paymentCodeLoading = Collections.synchronizedMap(new HashMap<>());
public PayNymAvatar() {
super();
paymentCodeProperty.addListener((observable, oldValue, newValue) -> {
if(Config.get().isUsePayNym()) {
PayNymAvatarService payNymAvatarService = new PayNymAvatarService(newValue);
payNymAvatarService.setOnRunning(runningEvent -> {
getChildren().clear();
});
payNymAvatarService.setOnSucceeded(successEvent -> {
Circle circle = new Circle(getWidth() / 2,getHeight() / 2,getWidth() / 2);
circle.setFill(new ImagePattern(payNymAvatarService.getValue()));
getChildren().add(circle);
});
payNymAvatarService.start();
paymentCodeProperty.addListener((observable, oldValue, paymentCode) -> {
if(paymentCode == null) {
getChildren().clear();
} else if(Config.get().isUsePayNym() && (oldValue == null || !oldValue.toString().equals(paymentCode.toString()))) {
String cacheId = getCacheId(paymentCode, getPrefWidth());
if(paymentCodeCache.containsKey(cacheId)) {
setImage(paymentCodeCache.get(cacheId));
} else {
PayNymAvatarService payNymAvatarService = new PayNymAvatarService(paymentCode, getPrefWidth());
payNymAvatarService.setOnRunning(runningEvent -> {
getChildren().clear();
});
payNymAvatarService.setOnSucceeded(successEvent -> {
setImage(payNymAvatarService.getValue());
});
payNymAvatarService.setOnFailed(failedEvent -> {
log.error("Error", failedEvent.getSource().getException());
});
payNymAvatarService.start();
}
}
});
}
private void setImage(Image image) {
getChildren().clear();
Circle circle = new Circle(getPrefWidth() / 2,getPrefHeight() / 2,getPrefWidth() / 2);
circle.setFill(new ImagePattern(image));
getChildren().add(circle);
}
public PaymentCode getPaymentCode() {
return paymentCodeProperty.get();
}
@@ -54,11 +75,17 @@ public class PayNymAvatar extends StackPane {
this.paymentCodeProperty.set(paymentCode);
}
private class PayNymAvatarService extends Service<Image> {
private final PaymentCode paymentCode;
private static String getCacheId(PaymentCode paymentCode, double width) {
return paymentCode.toString();
}
public PayNymAvatarService(PaymentCode paymentCode) {
private static class PayNymAvatarService extends Service<Image> {
private final PaymentCode paymentCode;
private final double width;
public PayNymAvatarService(PaymentCode paymentCode, double width) {
this.paymentCode = paymentCode;
this.width = width;
}
@Override
@@ -67,14 +94,34 @@ public class PayNymAvatar extends StackPane {
@Override
protected Image call() throws Exception {
String paymentCodeStr = paymentCode.toString();
String url = "https://paynym.is/" + paymentCodeStr + "/avatar";
Proxy proxy = AppServices.getProxy();
String cacheId = getCacheId(paymentCode, width);
try(InputStream is = (proxy == null ? new URL(url).openStream() : new URL(url).openConnection(proxy).getInputStream())) {
return new Image(is, getWidth(), getHeight(), true, false);
} catch(Exception e) {
log.debug("Error loading PayNym avatar", e);
throw e;
Object lock = paymentCodeLoading.get(cacheId);
if(lock != null) {
synchronized(lock) {
if(paymentCodeCache.containsKey(cacheId)) {
return paymentCodeCache.get(cacheId);
}
}
} else {
lock = new Object();
paymentCodeLoading.put(cacheId, lock);
}
synchronized(lock) {
String url = "https://paynym.is/" + paymentCodeStr + "/avatar";
Proxy proxy = AppServices.getProxy();
try(InputStream is = (proxy == null ? new URL(url).openStream() : new URL(url).openConnection(proxy).getInputStream())) {
Image image = new Image(is, 150, 150, true, false);
paymentCodeCache.put(cacheId, image);
return image;
} catch(Exception e) {
log.debug("Error loading PayNym avatar", e);
throw e;
} finally {
lock.notifyAll();
}
}
}
};
@@ -0,0 +1,65 @@
package com.sparrowwallet.sparrow.control;
import com.sparrowwallet.sparrow.EventManager;
import com.sparrowwallet.sparrow.event.FollowPayNymEvent;
import com.sparrowwallet.sparrow.soroban.PayNym;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
public class PayNymCell extends ListCell<PayNym> {
private final String walletId;
public PayNymCell(String walletId) {
super();
setAlignment(Pos.CENTER_LEFT);
setContentDisplay(ContentDisplay.LEFT);
getStyleClass().add("paynym-cell");
setPrefHeight(50);
this.walletId = walletId;
}
@Override
protected void updateItem(PayNym payNym, boolean empty) {
super.updateItem(payNym, empty);
if(empty || payNym == null) {
setText(null);
setGraphic(null);
} else {
BorderPane pane = new BorderPane();
pane.setPadding(new Insets(5, 5,5, 5));
PayNymAvatar payNymAvatar = new PayNymAvatar();
payNymAvatar.setPrefWidth(30);
payNymAvatar.setPrefHeight(30);
payNymAvatar.setPaymentCode(payNym.paymentCode());
HBox labelBox = new HBox();
labelBox.setAlignment(Pos.CENTER);
Label label = new Label(payNym.nymName(), payNymAvatar);
label.setGraphicTextGap(10);
labelBox.getChildren().add(label);
pane.setLeft(labelBox);
if(getListView().getUserData() == Boolean.TRUE) {
HBox hBox = new HBox();
hBox.setAlignment(Pos.CENTER);
Button button = new Button("Follow");
hBox.getChildren().add(button);
pane.setRight(hBox);
button.setOnAction(event -> {
EventManager.get().post(new FollowPayNymEvent(walletId, payNym.paymentCode()));
});
}
setText(null);
setGraphic(pane);
}
}
}
@@ -564,7 +564,7 @@ public class TransactionDiagram extends GridPane {
private Pane getTransactionPane() {
VBox txPane = new VBox();
txPane.setPadding(new Insets(0, 8, 0, 8));
txPane.setPadding(new Insets(0, 5, 0, 5));
txPane.setAlignment(Pos.CENTER);
txPane.getChildren().add(createSpacer());
@@ -0,0 +1,21 @@
package com.sparrowwallet.sparrow.event;
import com.samourai.wallet.bip47.rpc.PaymentCode;
public class FollowPayNymEvent {
private final String walletId;
private final PaymentCode paymentCode;
public FollowPayNymEvent(String walletId, PaymentCode paymentCode) {
this.walletId = walletId;
this.paymentCode = paymentCode;
}
public String getWalletId() {
return walletId;
}
public PaymentCode getPaymentCode() {
return paymentCode;
}
}
@@ -56,6 +56,7 @@ public class FontAwesome5 extends GlyphFont {
QUESTION_CIRCLE('\uf059'),
RANDOM('\uf074'),
REPLY_ALL('\uf122'),
ROBOT('\uf544'),
SATELLITE_DISH('\uf7c0'),
SD_CARD('\uf7c2'),
SEARCH('\uf002'),
@@ -55,6 +55,9 @@ public class CounterpartyController extends SorobanController {
@FXML
private CopyableTextField payNym;
@FXML
private Button showPayNym;
@FXML
private PayNymAvatar payNymAvatar;
@@ -157,6 +160,8 @@ public class CounterpartyController extends SorobanController {
}
payNym.managedProperty().bind(payNym.visibleProperty());
showPayNym.managedProperty().bind(showPayNym.visibleProperty());
showPayNym.visibleProperty().bind(payNym.visibleProperty());
payNymAvatar.managedProperty().bind(payNymAvatar.visibleProperty());
payNymAvatar.visibleProperty().bind(payNym.visibleProperty());
payNymButton.managedProperty().bind(payNymButton.visibleProperty());
@@ -169,6 +174,7 @@ public class CounterpartyController extends SorobanController {
paymentCode.setPaymentCode(soroban.getPaymentCode());
paymentCodeQR.prefHeightProperty().bind(paymentCode.heightProperty());
paymentCodeQR.prefWidthProperty().bind(showPayNym.widthProperty());
mixingPartner.managedProperty().bind(mixingPartner.visibleProperty());
meetingFail.managedProperty().bind(meetingFail.visibleProperty());
@@ -388,6 +394,11 @@ public class CounterpartyController extends SorobanController {
});
}
public void showPayNym(ActionEvent event) {
PayNymDialog payNymDialog = new PayNymDialog(walletId, false);
payNymDialog.showAndWait();
}
public void showPayNymQR(ActionEvent event) {
Soroban soroban = AppServices.getSorobanServices().getSoroban(walletId);
QRDisplayDialog qrDisplayDialog = new QRDisplayDialog(soroban.getPaymentCode().toString());
@@ -30,7 +30,10 @@ import io.reactivex.schedulers.Schedulers;
import javafx.application.Platform;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.layout.VBox;
@@ -44,7 +47,6 @@ import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.function.UnaryOperator;
import java.util.regex.Pattern;
import static com.sparrowwallet.sparrow.AppServices.showErrorDialog;
import static com.sparrowwallet.sparrow.soroban.Soroban.TIMEOUT_MS;
@@ -52,8 +54,7 @@ import static com.sparrowwallet.sparrow.soroban.Soroban.TIMEOUT_MS;
public class InitiatorController extends SorobanController {
private static final Logger log = LoggerFactory.getLogger(InitiatorController.class);
private static final Pattern PAYNYM_REGEX = Pattern.compile("\\+[a-z]+[0-9][0-9a-fA-F][0-9a-fA-F]");
private static final PayNym FIND_FOLLOWERS = new PayNym(null, null, "Retrieve PayNyms...", false);
private static final PayNym FIND_FOLLOWERS = new PayNym(null, null, "Retrieve PayNyms...", false, Collections.emptyList(), Collections.emptyList());
private String walletId;
private Wallet wallet;
@@ -74,6 +75,12 @@ public class InitiatorController extends SorobanController {
@FXML
private TextField counterparty;
@FXML
private ProgressIndicator payNymLoading;
@FXML
private Button findPayNym;
@FXML
private PayNymAvatar payNymAvatar;
@@ -101,6 +108,8 @@ public class InitiatorController extends SorobanController {
@FXML
private TransactionDiagram transactionDiagram;
private final StringProperty counterpartyPayNymName = new SimpleStringProperty();
private final ObjectProperty<PaymentCode> counterpartyPaymentCode = new SimpleObjectProperty<>(null);
private final ObjectProperty<Step> stepProperty = new SimpleObjectProperty<>(Step.SETUP);
@@ -147,6 +156,13 @@ public class InitiatorController extends SorobanController {
}
});
payNymLoading.managedProperty().bind(payNymLoading.visibleProperty());
payNymLoading.maxHeightProperty().bind(counterparty.heightProperty());
payNymLoading.setVisible(false);
findPayNym.managedProperty().bind(findPayNym.visibleProperty());
findPayNym.setVisible(Config.get().isUsePayNym());
payNymAvatar.managedProperty().bind(payNymAvatar.visibleProperty());
payNymFollowers.prefWidthProperty().bind(counterparty.widthProperty());
payNymFollowers.valueProperty().addListener((observable, oldValue, payNym) -> {
@@ -154,8 +170,11 @@ public class InitiatorController extends SorobanController {
Config.get().setUsePayNym(true);
setPayNymFollowers();
} else if(payNym != null) {
counterparty.setText(payNym.nymName());
counterpartyPayNymName.set(payNym.nymName());
counterpartyPaymentCode.set(payNym.paymentCode());
payNymAvatar.setPaymentCode(payNym.paymentCode());
counterparty.setText(payNym.nymName());
step1.requestFocus();
}
});
payNymFollowers.setConverter(new StringConverter<>() {
@@ -177,7 +196,9 @@ public class InitiatorController extends SorobanController {
PaymentCode paymentCode = new PaymentCode(input);
if(paymentCode.isValid()) {
counterpartyPaymentCode.set(paymentCode);
payNymAvatar.setPaymentCode(paymentCode);
if(payNymAvatar.getPaymentCode() == null || !input.equals(payNymAvatar.getPaymentCode().toString())) {
payNymAvatar.setPaymentCode(paymentCode);
}
TextInputControl control = (TextInputControl)change.getControl();
change.setText(input.substring(0, 12) + "..." + input.substring(input.length() - 5));
@@ -199,16 +220,23 @@ public class InitiatorController extends SorobanController {
if(newValue.startsWith("P") && newValue.contains("...") && newValue.length() == 20 && counterpartyPaymentCode.get() != null) {
//Assumed valid payment code
} else if(Config.get().isUsePayNym() && PAYNYM_REGEX.matcher(newValue).matches()) {
Soroban soroban = AppServices.getSorobanServices().getSoroban(walletId);
soroban.getPayNym(newValue).subscribe(payNym -> {
counterpartyPaymentCode.set(payNym.paymentCode());
payNymAvatar.setPaymentCode(payNym.paymentCode());
}, error -> {
//ignore, probably doesn't exist but will try again on meeting request
});
if(!newValue.equals(counterpartyPayNymName.get())) {
Soroban soroban = AppServices.getSorobanServices().getSoroban(walletId);
payNymLoading.setVisible(true);
soroban.getPayNym(newValue).subscribe(payNym -> {
payNymLoading.setVisible(false);
counterpartyPayNymName.set(payNym.nymName());
counterpartyPaymentCode.set(payNym.paymentCode());
payNymAvatar.setPaymentCode(payNym.paymentCode());
}, error -> {
payNymLoading.setVisible(false);
//ignore, probably doesn't exist but will try again on meeting request
});
}
} else {
counterpartyPayNymName.set(null);
counterpartyPaymentCode.set(null);
payNymAvatar.getChildren().clear();
payNymAvatar.setPaymentCode(null);
}
}
});
@@ -231,7 +259,8 @@ public class InitiatorController extends SorobanController {
private void setPayNymFollowers() {
Soroban soroban = AppServices.getSorobanServices().getSoroban(walletId);
if(soroban.getPaymentCode() != null) {
soroban.getFollowers().subscribe(followerPayNyms -> {
soroban.getFollowing().subscribe(followerPayNyms -> {
findPayNym.setVisible(true);
payNymFollowers.setItems(FXCollections.observableList(followerPayNyms));
}, error -> {
if(error.getMessage().endsWith("404")) {
@@ -352,7 +381,7 @@ public class InitiatorController extends SorobanController {
Soroban soroban = AppServices.getSorobanServices().getSoroban(walletId);
Payment payment = walletTransaction.getPayments().get(0);
Map<BlockTransactionHashIndex, WalletNode> firstSetUtxos = walletTransaction.getSelectedUtxoSets().get(0);
Map<BlockTransactionHashIndex, WalletNode> firstSetUtxos = walletTransaction.isCoinControlUsed() ? walletTransaction.getSelectedUtxoSets().get(0) : wallet.getWalletUtxos();
for(Map.Entry<BlockTransactionHashIndex, WalletNode> entry : firstSetUtxos.entrySet()) {
initiatorCahootsWallet.addUtxo(wallet, entry.getValue(), wallet.getTransactions().get(entry.getKey().getHash()), (int)entry.getKey().getIndex());
}
@@ -461,6 +490,18 @@ public class InitiatorController extends SorobanController {
transactionAccepted.set(Boolean.FALSE);
}
public void findPayNym(ActionEvent event) {
PayNymDialog payNymDialog = new PayNymDialog(walletId, true);
Optional<PayNym> optPayNym = payNymDialog.showAndWait();
optPayNym.ifPresent(payNym -> {
counterpartyPayNymName.set(payNym.nymName());
counterpartyPaymentCode.set(payNym.paymentCode());
payNymAvatar.setPaymentCode(payNym.paymentCode());
counterparty.setText(payNym.nymName());
step1.requestFocus();
});
}
public ObjectProperty<PaymentCode> counterpartyPaymentCodeProperty() {
return counterpartyPaymentCode;
}
@@ -2,4 +2,6 @@ package com.sparrowwallet.sparrow.soroban;
import com.samourai.wallet.bip47.rpc.PaymentCode;
public record PayNym(PaymentCode paymentCode, String nymId, String nymName, boolean segwit) {}
import java.util.List;
public record PayNym(PaymentCode paymentCode, String nymId, String nymName, boolean segwit, List<PayNym> following, List<PayNym> followers) {}
@@ -0,0 +1,273 @@
package com.sparrowwallet.sparrow.soroban;
import com.google.common.eventbus.Subscribe;
import com.samourai.wallet.bip47.rpc.PaymentCode;
import com.sparrowwallet.sparrow.AppServices;
import com.sparrowwallet.sparrow.control.*;
import com.sparrowwallet.sparrow.event.FollowPayNymEvent;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Optional;
import java.util.function.UnaryOperator;
public class PayNymController extends SorobanController {
private static final Logger log = LoggerFactory.getLogger(PayNymController.class);
private String walletId;
private PayNym walletPayNym;
@FXML
private CopyableTextField payNymName;
@FXML
private PaymentCodeTextField paymentCode;
@FXML
private CopyableTextField searchPayNyms;
@FXML
private ProgressIndicator findPayNym;
@FXML
private PayNymAvatar payNymAvatar;
@FXML
private ListView<PayNym> followingList;
@FXML
private ListView<PayNym> followersList;
private final ObjectProperty<PayNym> payNymProperty = new SimpleObjectProperty<>(null);
private final StringProperty findNymProperty = new SimpleStringProperty();
public void initializeView(String walletId) {
this.walletId = walletId;
findNymProperty.addListener((observable, oldValue, nymIdentifier) -> {
if(nymIdentifier != null) {
searchFollowing(nymIdentifier);
}
});
UnaryOperator<TextFormatter.Change> paymentCodeFilter = change -> {
String input = change.getControlNewText();
if(input.startsWith("P") && !input.contains("...")) {
try {
PaymentCode paymentCode = new PaymentCode(input);
if(paymentCode.isValid()) {
findNymProperty.set(input);
TextInputControl control = (TextInputControl)change.getControl();
change.setText(input.substring(0, 12) + "..." + input.substring(input.length() - 5));
change.setRange(0, control.getLength());
change.setAnchor(change.getText().length());
change.setCaretPosition(change.getText().length());
}
} catch(Exception e) {
//ignore
}
} else if(PAYNYM_REGEX.matcher(input).matches()) {
findNymProperty.set(input);
} else {
findNymProperty.set(null);
resetFollowing();
}
return change;
};
searchPayNyms.setTextFormatter(new TextFormatter<>(paymentCodeFilter));
findPayNym.managedProperty().bind(findPayNym.visibleProperty());
findPayNym.maxHeightProperty().bind(searchPayNyms.heightProperty());
findPayNym.setVisible(false);
followingList.setCellFactory(param -> {
return new PayNymCell(walletId);
});
followingList.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, payNym) -> {
payNymProperty.set(payNym);
});
followersList.setCellFactory(param -> {
return new PayNymCell(walletId);
});
followersList.setSelectionModel(new NoSelectionModel<>());
followersList.setFocusTraversable(false);
refresh();
}
private void refresh() {
Soroban soroban = AppServices.getSorobanServices().getSoroban(walletId);
if(soroban.getPaymentCode() == null) {
throw new IllegalStateException("Payment code has not been set");
}
soroban.getPayNym(soroban.getPaymentCode().toString()).subscribe(payNym -> {
walletPayNym = payNym;
payNymName.setText(payNym.nymName());
paymentCode.setPaymentCode(payNym.paymentCode());
payNymAvatar.setPaymentCode(payNym.paymentCode());
followingList.setUserData(null);
followingList.setItems(FXCollections.observableList(payNym.following()));
followersList.setItems(FXCollections.observableList(payNym.followers()));
});
}
private void resetFollowing() {
if(followingList.getUserData() != null) {
followingList.setUserData(null);
followingList.setItems(FXCollections.observableList(walletPayNym.following()));
}
}
private void searchFollowing(String nymIdentifier) {
Optional<PayNym> optExisting = walletPayNym.following().stream().filter(payNym -> payNym.nymName().equals(nymIdentifier) || payNym.paymentCode().toString().equals(nymIdentifier)).findFirst();
if(optExisting.isPresent()) {
followingList.setUserData(Boolean.FALSE);
List<PayNym> existingPayNym = new ArrayList<>();
existingPayNym.add(optExisting.get());
followingList.setItems(FXCollections.observableList(existingPayNym));
} else {
followingList.setUserData(Boolean.TRUE);
followingList.setItems(FXCollections.observableList(new ArrayList<>()));
findPayNym.setVisible(true);
Soroban soroban = AppServices.getSorobanServices().getSoroban(walletId);
soroban.getPayNym(nymIdentifier).subscribe(searchedPayNym -> {
findPayNym.setVisible(false);
List<PayNym> searchList = new ArrayList<>();
searchList.add(searchedPayNym);
followingList.setUserData(Boolean.TRUE);
followingList.setItems(FXCollections.observableList(searchList));
}, error -> {
findPayNym.setVisible(false);
});
}
}
public void showQR(ActionEvent event) {
Soroban soroban = AppServices.getSorobanServices().getSoroban(walletId);
QRDisplayDialog qrDisplayDialog = new QRDisplayDialog(soroban.getPaymentCode().toString());
qrDisplayDialog.showAndWait();
}
public void scanQR(ActionEvent event) {
QRScanDialog qrScanDialog = new QRScanDialog();
Optional<QRScanDialog.Result> optResult = qrScanDialog.showAndWait();
if(optResult.isPresent()) {
QRScanDialog.Result result = optResult.get();
if(result.payload != null) {
searchPayNyms.setText(result.payload);
} else {
AppServices.showErrorDialog("Invalid QR Code", "Cannot parse QR code into a payment code");
}
}
}
public PayNym getPayNym() {
return payNymProperty.get();
}
public ObjectProperty<PayNym> payNymProperty() {
return payNymProperty;
}
@Subscribe
public void followPayNym(FollowPayNymEvent event) {
if(event.getWalletId().equals(walletId)) {
Soroban soroban = AppServices.getSorobanServices().getSoroban(walletId);
soroban.getAuthToken(new HashMap<>()).subscribe(authToken -> {
String signature = soroban.getSignature(authToken);
soroban.followPaymentCode(event.getPaymentCode(), authToken, signature).subscribe(followMap -> {
refresh();
}, error -> {
log.error("Could not follow payment code", error);
AppServices.showErrorDialog("Could not follow payment code", error.getMessage());
});
});
}
}
public static class NoSelectionModel<T> extends MultipleSelectionModel<T> {
@Override
public ObservableList<Integer> getSelectedIndices() {
return FXCollections.emptyObservableList();
}
@Override
public ObservableList<T> getSelectedItems() {
return FXCollections.emptyObservableList();
}
@Override
public void selectIndices(int index, int... indices) {
}
@Override
public void selectAll() {
}
@Override
public void selectFirst() {
}
@Override
public void selectLast() {
}
@Override
public void clearAndSelect(int index) {
}
@Override
public void select(int index) {
}
@Override
public void select(T obj) {
}
@Override
public void clearSelection(int index) {
}
@Override
public void clearSelection() {
}
@Override
public boolean isSelected(int index) {
return false;
}
@Override
public boolean isEmpty() {
return true;
}
@Override
public void selectPrevious() {
}
@Override
public void selectNext() {
}
}
}
@@ -0,0 +1,55 @@
package com.sparrowwallet.sparrow.soroban;
import com.sparrowwallet.sparrow.AppServices;
import com.sparrowwallet.sparrow.EventManager;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.*;
import java.io.IOException;
public class PayNymDialog extends Dialog<PayNym> {
public PayNymDialog(String walletId, boolean selectPayNym) {
final DialogPane dialogPane = getDialogPane();
AppServices.setStageIcon(dialogPane.getScene().getWindow());
AppServices.onEscapePressed(dialogPane.getScene(), this::close);
try {
FXMLLoader payNymLoader = new FXMLLoader(AppServices.class.getResource("soroban/paynym.fxml"));
dialogPane.setContent(payNymLoader.load());
PayNymController payNymController = payNymLoader.getController();
payNymController.initializeView(walletId);
EventManager.get().register(payNymController);
dialogPane.setPrefWidth(730);
dialogPane.setPrefHeight(600);
AppServices.moveToActiveWindowScreen(this);
dialogPane.getStylesheets().add(AppServices.class.getResource("app.css").toExternalForm());
dialogPane.getStylesheets().add(AppServices.class.getResource("soroban/paynym.css").toExternalForm());
final ButtonType selectButtonType = new javafx.scene.control.ButtonType("Select PayNym", ButtonBar.ButtonData.APPLY);
final ButtonType cancelButtonType = new javafx.scene.control.ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE);
final ButtonType doneButtonType = new javafx.scene.control.ButtonType("Done", ButtonBar.ButtonData.OK_DONE);
if(selectPayNym) {
dialogPane.getButtonTypes().addAll(selectButtonType, cancelButtonType);
Button selectButton = (Button)dialogPane.lookupButton(selectButtonType);
selectButton.setDisable(true);
selectButton.setDefaultButton(true);
payNymController.payNymProperty().addListener((observable, oldValue, payNym) -> {
selectButton.setDisable(payNym == null);
});
} else {
dialogPane.getButtonTypes().add(doneButtonType);
}
setOnCloseRequest(event -> {
EventManager.get().unregister(payNymController);
});
setResultConverter(dialogButton -> dialogButton == selectButtonType ? payNymController.getPayNym() : null);
} catch(IOException e) {
throw new RuntimeException(e);
}
}
}
@@ -9,6 +9,7 @@ import io.reactivex.rxjavafx.schedulers.JavaFxScheduler;
import io.reactivex.schedulers.Schedulers;
import java8.util.Optional;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -124,16 +125,18 @@ public class PayNymService {
return fetchPayNym(nymIdentifier).map(nymMap -> {
List<Map<String, Object>> codes = (List<Map<String, Object>>)nymMap.get("codes");
PaymentCode code = new PaymentCode((String)codes.stream().filter(codeMap -> codeMap.get("segwit") == Boolean.FALSE).map(codeMap -> codeMap.get("code")).findFirst().orElse(codes.get(0).get("code")));
return new PayNym(code, (String)nymMap.get("nymID"), (String)nymMap.get("nymName"), (Boolean)nymMap.get("segwit"));
});
}
public Observable<List<PayNym>> getFollowers(String nymIdentifier) {
return fetchPayNym(nymIdentifier).flatMap(nymMap -> {
List<Map<String, Object>> followers = (List<Map<String, Object>>)nymMap.get("following");
return Observable.fromArray(followers.stream().map(followerMap -> {
return new PayNym(new PaymentCode((String)followerMap.get("code")), (String)followerMap.get("nymId"), (String)followerMap.get("nymName"), (Boolean)followerMap.get("segwit"));
}).collect(Collectors.toList()));
List<Map<String, Object>> followingMaps = (List<Map<String, Object>>)nymMap.get("following");
List<PayNym> following = followingMaps.stream().map(followingMap -> {
return new PayNym(new PaymentCode((String)followingMap.get("code")), (String)followingMap.get("nymId"), (String)followingMap.get("nymName"), (Boolean)followingMap.get("segwit"), Collections.emptyList(), Collections.emptyList());
}).collect(Collectors.toList());
List<Map<String, Object>> followersMaps = (List<Map<String, Object>>)nymMap.get("followers");
List<PayNym> followers = followersMaps.stream().map(followerMap -> {
return new PayNym(new PaymentCode((String)followerMap.get("code")), (String)followerMap.get("nymId"), (String)followerMap.get("nymName"), (Boolean)followerMap.get("segwit"), Collections.emptyList(), Collections.emptyList());
}).collect(Collectors.toList());
return new PayNym(code, (String)nymMap.get("nymID"), (String)nymMap.get("nymName"), (Boolean)nymMap.get("segwit"), following, followers);
});
}
}
@@ -171,8 +171,8 @@ public class Soroban {
return payNymService.getPayNym(nymIdentifier);
}
public Observable<List<PayNym>> getFollowers() {
return payNymService.getFollowers(paymentCode.toString());
public Observable<List<PayNym>> getFollowing() {
return payNymService.getPayNym(paymentCode.toString()).map(PayNym::following);
}
public Observable<String> getAuthToken(Map<String, Object> map) {
@@ -16,10 +16,12 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
public class SorobanController {
private static final Logger log = LoggerFactory.getLogger(SorobanController.class);
protected static final Pattern PAYNYM_REGEX = Pattern.compile("\\+[a-z]+[0-9][0-9a-fA-F][0-9a-fA-F]");
protected Transaction getTransaction(Cahoots cahoots) throws PSBTParseException {
if(cahoots.getPSBT() != null) {