mirror of
https://github.com/bisq-network/bisq.git
synced 2025-02-23 15:00:30 +01:00
Improve chat functionality of mediation/arbitration
Show badge with number of unread chat messages on each ticket Use icons for chat, info and process ticket functions Select a row by clicking on it, no clunky select button needed. Show chat messages in a movable popup window. More space for ticket list. Implement requested feature additions: Indicate if the trade period is over Indicate more clearly the sender of each chat message Support badge count Indicate the XMR tx proof (when applicable) Allow trader to close own mediation ticket if the trade is paid out Fixes: null check for cases when extraData null/not applicable when upgrading closed disputes, clear chat unread count
This commit is contained in:
parent
0feb1079b8
commit
cc56470b2f
25 changed files with 697 additions and 194 deletions
|
@ -123,7 +123,7 @@ public class DisputeMsgEvents {
|
|||
// If last message is not a result message we re-open as we might have received a new message from the
|
||||
// trader/mediator/arbitrator who has reopened the case
|
||||
if (dispute.isClosed() && !chatMessages.isEmpty() && !chatMessages.get(chatMessages.size() - 1).isResultMessage(dispute)) {
|
||||
dispute.setIsClosed(false);
|
||||
dispute.reOpen();
|
||||
if (dispute.getSupportType() == SupportType.MEDIATION) {
|
||||
mediationManager.requestPersistence();
|
||||
} else if (dispute.getSupportType() == SupportType.REFUND) {
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
package bisq.core.support.dispute;
|
||||
|
||||
import bisq.core.locale.Res;
|
||||
import bisq.core.proto.CoreProtoResolver;
|
||||
import bisq.core.support.SupportType;
|
||||
import bisq.core.support.messages.ChatMessage;
|
||||
|
@ -26,15 +27,20 @@ import bisq.common.crypto.PubKeyRing;
|
|||
import bisq.common.proto.ProtoUtil;
|
||||
import bisq.common.proto.network.NetworkPayload;
|
||||
import bisq.common.proto.persistable.PersistablePayload;
|
||||
import bisq.common.util.CollectionUtils;
|
||||
import bisq.common.util.ExtraDataMapValidator;
|
||||
import bisq.common.util.Utilities;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyBooleanProperty;
|
||||
import javafx.beans.property.ReadOnlyIntegerProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
|
||||
import javafx.collections.FXCollections;
|
||||
|
@ -42,7 +48,9 @@ import javafx.collections.ObservableList;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
@ -58,6 +66,23 @@ import javax.annotation.Nullable;
|
|||
@EqualsAndHashCode
|
||||
@Getter
|
||||
public final class Dispute implements NetworkPayload, PersistablePayload {
|
||||
|
||||
public enum State {
|
||||
NEEDS_UPGRADE,
|
||||
NEW,
|
||||
OPEN,
|
||||
REOPENED,
|
||||
CLOSED;
|
||||
|
||||
public static Dispute.State fromProto(protobuf.Dispute.State state) {
|
||||
return ProtoUtil.enumFromProto(Dispute.State.class, state.name());
|
||||
}
|
||||
|
||||
public static protobuf.Dispute.State toProtoMessage(Dispute.State state) {
|
||||
return protobuf.Dispute.State.valueOf(state.name());
|
||||
}
|
||||
}
|
||||
|
||||
private final String tradeId;
|
||||
private final String id;
|
||||
private final int traderId;
|
||||
|
@ -66,6 +91,7 @@ public final class Dispute implements NetworkPayload, PersistablePayload {
|
|||
// PubKeyRing of trader who opened the dispute
|
||||
private final PubKeyRing traderPubKeyRing;
|
||||
private final long tradeDate;
|
||||
private final long tradePeriodEnd;
|
||||
private final Contract contract;
|
||||
@Nullable
|
||||
private final byte[] contractHash;
|
||||
|
@ -85,7 +111,6 @@ public final class Dispute implements NetworkPayload, PersistablePayload {
|
|||
private final PubKeyRing agentPubKeyRing; // dispute agent
|
||||
private final boolean isSupportTicket;
|
||||
private final ObservableList<ChatMessage> chatMessages = FXCollections.observableArrayList();
|
||||
private final BooleanProperty isClosedProperty = new SimpleBooleanProperty();
|
||||
// disputeResultProperty.get is Nullable!
|
||||
private final ObjectProperty<DisputeResult> disputeResultProperty = new SimpleObjectProperty<>();
|
||||
private final long openingDate;
|
||||
|
@ -107,10 +132,25 @@ public final class Dispute implements NetworkPayload, PersistablePayload {
|
|||
@Setter
|
||||
@Nullable
|
||||
private String donationAddressOfDelayedPayoutTx;
|
||||
// Added at v1.6.0
|
||||
private Dispute.State disputeState = State.NEW;
|
||||
|
||||
// Should be only used in emergency case if we need to add data but do not want to break backward compatibility
|
||||
// at the P2P network storage checks. The hash of the object will be used to verify if the data is valid. Any new
|
||||
// field in a class would break that hash and therefore break the storage mechanism.
|
||||
@Nullable
|
||||
@Setter
|
||||
private Map<String, String> extraDataMap;
|
||||
|
||||
// We do not persist uid, it is only used by dispute agents to guarantee an uid.
|
||||
@Setter
|
||||
@Nullable
|
||||
private transient String uid;
|
||||
@Setter
|
||||
private transient long payoutTxConfirms = -1;
|
||||
|
||||
private transient final BooleanProperty isClosedProperty = new SimpleBooleanProperty();
|
||||
private transient final IntegerProperty alertCountProperty = new SimpleIntegerProperty();
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -124,6 +164,7 @@ public final class Dispute implements NetworkPayload, PersistablePayload {
|
|||
boolean disputeOpenerIsMaker,
|
||||
PubKeyRing traderPubKeyRing,
|
||||
long tradeDate,
|
||||
long tradePeriodEnd,
|
||||
Contract contract,
|
||||
@Nullable byte[] contractHash,
|
||||
@Nullable byte[] depositTxSerialized,
|
||||
|
@ -143,6 +184,7 @@ public final class Dispute implements NetworkPayload, PersistablePayload {
|
|||
this.disputeOpenerIsMaker = disputeOpenerIsMaker;
|
||||
this.traderPubKeyRing = traderPubKeyRing;
|
||||
this.tradeDate = tradeDate;
|
||||
this.tradePeriodEnd = tradePeriodEnd;
|
||||
this.contract = contract;
|
||||
this.contractHash = contractHash;
|
||||
this.depositTxSerialized = depositTxSerialized;
|
||||
|
@ -158,6 +200,7 @@ public final class Dispute implements NetworkPayload, PersistablePayload {
|
|||
|
||||
id = tradeId + "_" + traderId;
|
||||
uid = UUID.randomUUID().toString();
|
||||
refreshAlertLevel(true);
|
||||
}
|
||||
|
||||
|
||||
|
@ -176,6 +219,7 @@ public final class Dispute implements NetworkPayload, PersistablePayload {
|
|||
.setDisputeOpenerIsMaker(disputeOpenerIsMaker)
|
||||
.setTraderPubKeyRing(traderPubKeyRing.toProtoMessage())
|
||||
.setTradeDate(tradeDate)
|
||||
.setTradePeriodEnd(tradePeriodEnd)
|
||||
.setContract(contract.toProtoMessage())
|
||||
.setContractAsJson(contractAsJson)
|
||||
.setAgentPubKeyRing(agentPubKeyRing.toProtoMessage())
|
||||
|
@ -183,8 +227,9 @@ public final class Dispute implements NetworkPayload, PersistablePayload {
|
|||
.addAllChatMessage(clonedChatMessages.stream()
|
||||
.map(msg -> msg.toProtoNetworkEnvelope().getChatMessage())
|
||||
.collect(Collectors.toList()))
|
||||
.setIsClosed(isClosedProperty.get())
|
||||
.setIsClosed(this.isClosed())
|
||||
.setOpeningDate(openingDate)
|
||||
.setState(Dispute.State.toProtoMessage(disputeState))
|
||||
.setId(id);
|
||||
|
||||
Optional.ofNullable(contractHash).ifPresent(e -> builder.setContractHash(ByteString.copyFrom(e)));
|
||||
|
@ -200,6 +245,7 @@ public final class Dispute implements NetworkPayload, PersistablePayload {
|
|||
Optional.ofNullable(mediatorsDisputeResult).ifPresent(result -> builder.setMediatorsDisputeResult(mediatorsDisputeResult));
|
||||
Optional.ofNullable(delayedPayoutTxId).ifPresent(result -> builder.setDelayedPayoutTxId(delayedPayoutTxId));
|
||||
Optional.ofNullable(donationAddressOfDelayedPayoutTx).ifPresent(result -> builder.setDonationAddressOfDelayedPayoutTx(donationAddressOfDelayedPayoutTx));
|
||||
Optional.ofNullable(getExtraDataMap()).ifPresent(builder::putAllExtraData);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
|
@ -211,6 +257,7 @@ public final class Dispute implements NetworkPayload, PersistablePayload {
|
|||
proto.getDisputeOpenerIsMaker(),
|
||||
PubKeyRing.fromProto(proto.getTraderPubKeyRing()),
|
||||
proto.getTradeDate(),
|
||||
proto.getTradePeriodEnd(),
|
||||
Contract.fromProto(proto.getContract(), coreProtoResolver),
|
||||
ProtoUtil.byteArrayOrNullFromProto(proto.getContractHash()),
|
||||
ProtoUtil.byteArrayOrNullFromProto(proto.getDepositTxSerialized()),
|
||||
|
@ -224,11 +271,13 @@ public final class Dispute implements NetworkPayload, PersistablePayload {
|
|||
proto.getIsSupportTicket(),
|
||||
SupportType.fromProto(proto.getSupportType()));
|
||||
|
||||
dispute.setExtraDataMap(CollectionUtils.isEmpty(proto.getExtraDataMap()) ?
|
||||
null : ExtraDataMapValidator.getValidatedExtraDataMap(proto.getExtraDataMap()));
|
||||
|
||||
dispute.chatMessages.addAll(proto.getChatMessageList().stream()
|
||||
.map(ChatMessage::fromPayloadProto)
|
||||
.collect(Collectors.toList()));
|
||||
|
||||
dispute.isClosedProperty.set(proto.getIsClosed());
|
||||
if (proto.hasDisputeResult())
|
||||
dispute.disputeResultProperty.set(DisputeResult.fromProto(proto.getDisputeResult()));
|
||||
dispute.disputePayoutTxId = ProtoUtil.stringOrNullFromProto(proto.getDisputePayoutTxId());
|
||||
|
@ -248,6 +297,20 @@ public final class Dispute implements NetworkPayload, PersistablePayload {
|
|||
dispute.setDonationAddressOfDelayedPayoutTx(donationAddressOfDelayedPayoutTx);
|
||||
}
|
||||
|
||||
if (Dispute.State.fromProto(proto.getState()) == State.NEEDS_UPGRADE) {
|
||||
// old disputes did not have a state field, so choose an appropriate state:
|
||||
dispute.setState(proto.getIsClosed() ? State.CLOSED : State.OPEN);
|
||||
if (dispute.getDisputeState() == State.CLOSED) {
|
||||
// mark chat messages as read for pre-existing CLOSED disputes
|
||||
// otherwise at upgrade, all old disputes would have 1 unread chat message
|
||||
// because currently when a dispute is closed, the last chat message is not marked read
|
||||
dispute.getChatMessages().forEach(m -> m.setWasDisplayed(true));
|
||||
}
|
||||
} else {
|
||||
dispute.setState(Dispute.State.fromProto(proto.getState()));
|
||||
}
|
||||
|
||||
dispute.refreshAlertLevel(true);
|
||||
return dispute;
|
||||
}
|
||||
|
||||
|
@ -269,14 +332,38 @@ public final class Dispute implements NetworkPayload, PersistablePayload {
|
|||
// Setters
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void setIsClosed(boolean isClosed) {
|
||||
this.isClosedProperty.set(isClosed);
|
||||
public void setIsClosed() {
|
||||
setState(State.CLOSED);
|
||||
}
|
||||
|
||||
public void reOpen() {
|
||||
setState(State.REOPENED);
|
||||
}
|
||||
|
||||
public void clearNewFlag() {
|
||||
if (this.disputeState == State.NEW) {
|
||||
this.disputeState = State.OPEN;
|
||||
}
|
||||
}
|
||||
|
||||
public void setState(Dispute.State disputeState) {
|
||||
this.disputeState = disputeState;
|
||||
this.isClosedProperty.set(disputeState == State.CLOSED);
|
||||
}
|
||||
|
||||
public void setDisputeResult(DisputeResult disputeResult) {
|
||||
disputeResultProperty.set(disputeResult);
|
||||
}
|
||||
|
||||
public void setExtraData(String key, String value) {
|
||||
if (key == null || value == null) {
|
||||
return;
|
||||
}
|
||||
if (extraDataMap == null) {
|
||||
extraDataMap = new HashMap<>();
|
||||
}
|
||||
extraDataMap.put(key, value);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Getters
|
||||
|
@ -289,7 +376,9 @@ public final class Dispute implements NetworkPayload, PersistablePayload {
|
|||
public ReadOnlyBooleanProperty isClosedProperty() {
|
||||
return isClosedProperty;
|
||||
}
|
||||
|
||||
public ReadOnlyIntegerProperty getAlertCountProperty() {
|
||||
return alertCountProperty;
|
||||
}
|
||||
public ReadOnlyObjectProperty<DisputeResult> disputeResultProperty() {
|
||||
return disputeResultProperty;
|
||||
}
|
||||
|
@ -298,14 +387,60 @@ public final class Dispute implements NetworkPayload, PersistablePayload {
|
|||
return new Date(tradeDate);
|
||||
}
|
||||
|
||||
public Date getTradePeriodEnd() {
|
||||
return new Date(tradePeriodEnd);
|
||||
}
|
||||
|
||||
public Date getOpeningDate() {
|
||||
return new Date(openingDate);
|
||||
}
|
||||
|
||||
public boolean isClosed() {
|
||||
return isClosedProperty.get();
|
||||
public boolean isNew() {
|
||||
return this.disputeState == State.NEW;
|
||||
}
|
||||
|
||||
public boolean isClosed() {
|
||||
return this.disputeState == State.CLOSED;
|
||||
}
|
||||
|
||||
public void refreshAlertLevel(boolean senderFlag) {
|
||||
// if the dispute is "new" that is 1 alert that has to be propagated upstream
|
||||
// unread message count also has to be propagated upstream
|
||||
alertCountProperty.setValue((isNew() ? 1 : 0) + unreadMessageCount(senderFlag));
|
||||
}
|
||||
|
||||
public long unreadMessageCount(boolean senderFlag) {
|
||||
return chatMessages.stream()
|
||||
.filter(m -> m.isSenderIsTrader() == senderFlag)
|
||||
.filter(m -> !m.isSystemMessage())
|
||||
.filter(m -> !m.isWasDisplayed())
|
||||
.count();
|
||||
}
|
||||
|
||||
public void setDisputeSeen(boolean senderFlag) {
|
||||
if (this.disputeState == State.NEW)
|
||||
setState(State.OPEN);
|
||||
refreshAlertLevel(senderFlag);
|
||||
}
|
||||
|
||||
public void setChatMessagesSeen(boolean senderFlag) {
|
||||
getChatMessages().forEach(m -> m.setWasDisplayed(true));
|
||||
refreshAlertLevel(senderFlag);
|
||||
}
|
||||
|
||||
public String getRoleString() {
|
||||
if (disputeOpenerIsMaker) {
|
||||
if (disputeOpenerIsBuyer)
|
||||
return Res.get("support.buyerOfferer");
|
||||
else
|
||||
return Res.get("support.sellerOfferer");
|
||||
} else {
|
||||
if (disputeOpenerIsBuyer)
|
||||
return Res.get("support.buyerTaker");
|
||||
else
|
||||
return Res.get("support.sellerTaker");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
|
@ -313,11 +448,13 @@ public final class Dispute implements NetworkPayload, PersistablePayload {
|
|||
"\n tradeId='" + tradeId + '\'' +
|
||||
",\n id='" + id + '\'' +
|
||||
",\n uid='" + uid + '\'' +
|
||||
",\n state=" + disputeState +
|
||||
",\n traderId=" + traderId +
|
||||
",\n disputeOpenerIsBuyer=" + disputeOpenerIsBuyer +
|
||||
",\n disputeOpenerIsMaker=" + disputeOpenerIsMaker +
|
||||
",\n traderPubKeyRing=" + traderPubKeyRing +
|
||||
",\n tradeDate=" + tradeDate +
|
||||
",\n tradePeriodEnd=" + tradePeriodEnd +
|
||||
",\n contract=" + contract +
|
||||
",\n contractHash=" + Utilities.bytesAsHexString(contractHash) +
|
||||
",\n depositTxSerialized=" + Utilities.bytesAsHexString(depositTxSerialized) +
|
||||
|
|
|
@ -26,17 +26,14 @@ import bisq.common.persistence.PersistenceManager;
|
|||
import bisq.common.proto.persistable.PersistedDataHost;
|
||||
|
||||
import org.fxmisc.easybind.EasyBind;
|
||||
import org.fxmisc.easybind.Subscription;
|
||||
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
|
||||
import javafx.collections.ObservableList;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
@ -52,7 +49,6 @@ public abstract class DisputeListService<T extends DisputeList<Dispute>> impleme
|
|||
protected final PersistenceManager<T> persistenceManager;
|
||||
@Getter
|
||||
private final T disputeList;
|
||||
private final Map<String, Subscription> disputeIsClosedSubscriptionsMap = new HashMap<>();
|
||||
@Getter
|
||||
private final IntegerProperty numOpenDisputes = new SimpleIntegerProperty();
|
||||
@Getter
|
||||
|
@ -153,26 +149,21 @@ public abstract class DisputeListService<T extends DisputeList<Dispute>> impleme
|
|||
@Nullable List<? extends Dispute> removedList) {
|
||||
if (removedList != null) {
|
||||
removedList.forEach(dispute -> {
|
||||
String id = dispute.getId();
|
||||
if (disputeIsClosedSubscriptionsMap.containsKey(id)) {
|
||||
disputeIsClosedSubscriptionsMap.get(id).unsubscribe();
|
||||
disputeIsClosedSubscriptionsMap.remove(id);
|
||||
}
|
||||
disputedTradeIds.remove(dispute.getTradeId());
|
||||
});
|
||||
}
|
||||
addedList.forEach(dispute -> {
|
||||
String id = dispute.getId();
|
||||
Subscription disputeStateSubscription = EasyBind.subscribe(dispute.isClosedProperty(),
|
||||
isClosed -> {
|
||||
// for each dispute added, keep track of its "AlertCountProperty"
|
||||
EasyBind.subscribe(dispute.getAlertCountProperty(),
|
||||
isAlerting -> {
|
||||
// We get the event before the list gets updated, so we execute on next frame
|
||||
UserThread.execute(() -> {
|
||||
int openDisputes = (int) disputeList.getList().stream()
|
||||
.filter(e -> !e.isClosed()).count();
|
||||
numOpenDisputes.set(openDisputes);
|
||||
int numAlerts = (int) disputeList.getList().stream()
|
||||
.mapToLong(x -> x.getAlertCountProperty().getValue())
|
||||
.sum();
|
||||
numOpenDisputes.set(numAlerts);
|
||||
});
|
||||
});
|
||||
disputeIsClosedSubscriptionsMap.put(id, disputeStateSubscription);
|
||||
disputedTradeIds.add(dispute.getTradeId());
|
||||
});
|
||||
}
|
||||
|
|
|
@ -314,6 +314,8 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
|
|||
Dispute dispute = openNewDisputeMessage.getDispute();
|
||||
// Disputes from clients < 1.2.0 always have support type ARBITRATION in dispute as the field didn't exist before
|
||||
dispute.setSupportType(openNewDisputeMessage.getSupportType());
|
||||
// disputes from clients < 1.6.0 have state not set as the field didn't exist before
|
||||
dispute.setState(Dispute.State.NEW); // this can be removed a few months after 1.6.0 release
|
||||
|
||||
Contract contract = dispute.getContract();
|
||||
addPriceInfoMessage(dispute, 0);
|
||||
|
@ -577,6 +579,7 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
|
|||
!disputeFromOpener.isDisputeOpenerIsMaker(),
|
||||
pubKeyRing,
|
||||
disputeFromOpener.getTradeDate().getTime(),
|
||||
disputeFromOpener.getTradePeriodEnd().getTime(),
|
||||
contractFromOpener,
|
||||
disputeFromOpener.getContractHash(),
|
||||
disputeFromOpener.getDepositTxSerialized(),
|
||||
|
@ -589,6 +592,7 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
|
|||
disputeFromOpener.getAgentPubKeyRing(),
|
||||
disputeFromOpener.isSupportTicket(),
|
||||
disputeFromOpener.getSupportType());
|
||||
dispute.setExtraDataMap(disputeFromOpener.getExtraDataMap());
|
||||
dispute.setDelayedPayoutTxId(disputeFromOpener.getDelayedPayoutTxId());
|
||||
dispute.setDonationAddressOfDelayedPayoutTx(disputeFromOpener.getDonationAddressOfDelayedPayoutTx());
|
||||
|
||||
|
@ -829,6 +833,14 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
|
|||
.findAny();
|
||||
}
|
||||
|
||||
public Optional<Trade> findTrade(Dispute dispute) {
|
||||
Optional<Trade> retVal = tradeManager.getTradeById(dispute.getTradeId());
|
||||
if (!retVal.isPresent()) {
|
||||
retVal = closedTradableManager.getClosedTrades().stream().filter(e -> e.getId().equals(dispute.getTradeId())).findFirst();
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private void addMediationResultMessage(Dispute dispute) {
|
||||
// In case of refundAgent we add a message with the mediatorsDisputeSummary. Only visible for refundAgent.
|
||||
if (dispute.getMediatorsDisputeResult() != null) {
|
||||
|
@ -846,6 +858,21 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
|
|||
}
|
||||
}
|
||||
|
||||
public void addMediationReOpenedMessage(Dispute dispute, boolean senderIsTrader) {
|
||||
String message = "Dispute ticket has been re-opened.";
|
||||
ChatMessage chatMessage = new ChatMessage(
|
||||
getSupportType(),
|
||||
dispute.getTradeId(),
|
||||
dispute.getTraderId(),
|
||||
senderIsTrader,
|
||||
message,
|
||||
p2PService.getAddress());
|
||||
chatMessage.setSystemMessage(false);
|
||||
dispute.addAndPersistChatMessage(chatMessage);
|
||||
this.sendChatMessage(chatMessage);
|
||||
requestPersistence();
|
||||
}
|
||||
|
||||
// If price was going down between take offer time and open dispute time the buyer has an incentive to
|
||||
// not send the payment but to try to make a new trade with the better price. We risks to lose part of the
|
||||
// security deposit (in mediation we will always get back 0.003 BTC to keep some incentive to accept mediated
|
||||
|
|
|
@ -217,7 +217,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
|||
} else {
|
||||
log.warn("We got a dispute mail msg what we have already stored. TradeId = " + chatMessage.getTradeId());
|
||||
}
|
||||
dispute.setIsClosed(true);
|
||||
dispute.setIsClosed();
|
||||
|
||||
if (dispute.disputeResultProperty().get() != null) {
|
||||
log.warn("We already got a dispute result. That should only happen if a dispute needs to be closed " +
|
||||
|
|
|
@ -193,7 +193,7 @@ public final class MediationManager extends DisputeManager<MediationDisputeList>
|
|||
} else {
|
||||
log.warn("We got a dispute mail msg what we have already stored. TradeId = " + chatMessage.getTradeId());
|
||||
}
|
||||
dispute.setIsClosed(true);
|
||||
dispute.setIsClosed();
|
||||
|
||||
dispute.setDisputeResult(disputeResult);
|
||||
|
||||
|
|
|
@ -190,7 +190,7 @@ public final class RefundManager extends DisputeManager<RefundDisputeList> {
|
|||
} else {
|
||||
log.warn("We got a dispute mail msg what we have already stored. TradeId = " + chatMessage.getTradeId());
|
||||
}
|
||||
dispute.setIsClosed(true);
|
||||
dispute.setIsClosed();
|
||||
|
||||
if (dispute.disputeResultProperty().get() != null) {
|
||||
log.warn("We got already a dispute result. That should only happen if a dispute needs to be closed " +
|
||||
|
|
|
@ -1127,8 +1127,10 @@ support.sellerAddress=BTC seller address
|
|||
support.role=Role
|
||||
support.agent=Support agent
|
||||
support.state=State
|
||||
support.chat=Chat
|
||||
support.closed=Closed
|
||||
support.open=Open
|
||||
support.process=Process
|
||||
support.buyerOfferer=BTC buyer/Maker
|
||||
support.sellerOfferer=BTC seller/Maker
|
||||
support.buyerTaker=BTC buyer/Taker
|
||||
|
@ -2534,6 +2536,9 @@ disputeSummaryWindow.payoutAmount.buyer=Buyer's payout amount
|
|||
disputeSummaryWindow.payoutAmount.seller=Seller's payout amount
|
||||
disputeSummaryWindow.payoutAmount.invert=Use loser as publisher
|
||||
disputeSummaryWindow.reason=Reason of dispute
|
||||
disputeSummaryWindow.tradePeriodEnd=Trade period end
|
||||
disputeSummaryWindow.extraInfo=Extra information
|
||||
disputeSummaryWindow.delayedPayoutStatus=Delayed Payout Status
|
||||
|
||||
# dynamic values are not recognized by IntelliJ
|
||||
# suppress inspection "UnusedProperty"
|
||||
|
|
|
@ -184,6 +184,7 @@ public class AccountAgeWitnessServiceTest {
|
|||
true,
|
||||
buyerPubKeyRing,
|
||||
now - 1,
|
||||
now - 1,
|
||||
contract,
|
||||
null,
|
||||
null,
|
||||
|
@ -196,7 +197,7 @@ public class AccountAgeWitnessServiceTest {
|
|||
null,
|
||||
true,
|
||||
SupportType.ARBITRATION));
|
||||
disputes.get(0).getIsClosedProperty().set(true);
|
||||
disputes.get(0).setIsClosed();
|
||||
disputes.get(0).getDisputeResultProperty().set(new DisputeResult(
|
||||
"trade1",
|
||||
1,
|
||||
|
|
|
@ -84,16 +84,16 @@ import javafx.geometry.Insets;
|
|||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static bisq.desktop.util.FormBuilder.add2ButtonsWithBox;
|
||||
import static bisq.desktop.util.FormBuilder.addConfirmationLabelLabel;
|
||||
import static bisq.desktop.util.FormBuilder.addTitledGroupBg;
|
||||
import static bisq.desktop.util.FormBuilder.addTopLabelWithVBox;
|
||||
import static bisq.desktop.util.FormBuilder.*;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
@Slf4j
|
||||
|
@ -104,6 +104,7 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
|
|||
private final TradeWalletService tradeWalletService;
|
||||
private final BtcWalletService btcWalletService;
|
||||
private final TxFeeEstimationService txFeeEstimationService;
|
||||
// PR5160 will add private final MempoolService mempoolService;
|
||||
private final DaoFacade daoFacade;
|
||||
private Dispute dispute;
|
||||
private Optional<Runnable> finalizeDisputeHandlerOptional = Optional.empty();
|
||||
|
@ -120,6 +121,7 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
|
|||
// Dispute object of other trade peer. The dispute field is the one from which we opened the close dispute window.
|
||||
private Optional<Dispute> peersDisputeOptional;
|
||||
private String role;
|
||||
private Label delayedPayoutTxStatus;
|
||||
private TextArea summaryNotesTextArea;
|
||||
|
||||
private ChangeListener<Boolean> customRadioButtonSelectedListener;
|
||||
|
@ -141,6 +143,7 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
|
|||
TradeWalletService tradeWalletService,
|
||||
BtcWalletService btcWalletService,
|
||||
TxFeeEstimationService txFeeEstimationService,
|
||||
// PR5160 will add MempoolService mempoolService,
|
||||
DaoFacade daoFacade) {
|
||||
|
||||
this.formatter = formatter;
|
||||
|
@ -149,6 +152,7 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
|
|||
this.tradeWalletService = tradeWalletService;
|
||||
this.btcWalletService = btcWalletService;
|
||||
this.txFeeEstimationService = txFeeEstimationService;
|
||||
// PR5160 will add this.mempoolService = mempoolService;
|
||||
this.daoFacade = daoFacade;
|
||||
|
||||
type = Type.Confirmation;
|
||||
|
@ -161,6 +165,7 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
|
|||
width = 1150;
|
||||
createGridPane();
|
||||
addContent();
|
||||
checkDelayedPayoutTransaction();
|
||||
display();
|
||||
|
||||
if (DevEnv.isDevMode()) {
|
||||
|
@ -170,12 +175,6 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
|
|||
}
|
||||
}
|
||||
|
||||
public DisputeSummaryWindow onFinalizeDispute(Runnable finalizeDisputeHandler) {
|
||||
this.finalizeDisputeHandlerOptional = Optional.of(finalizeDisputeHandler);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Protected
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -288,17 +287,7 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
|
|||
addConfirmationLabelLabel(gridPane, rowIndex, Res.get("shared.tradeId"), dispute.getShortTradeId(),
|
||||
Layout.TWICE_FIRST_ROW_DISTANCE);
|
||||
addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("disputeSummaryWindow.openDate"), DisplayUtils.formatDateTime(dispute.getOpeningDate()));
|
||||
if (dispute.isDisputeOpenerIsMaker()) {
|
||||
if (dispute.isDisputeOpenerIsBuyer())
|
||||
role = Res.get("support.buyerOfferer");
|
||||
else
|
||||
role = Res.get("support.sellerOfferer");
|
||||
} else {
|
||||
if (dispute.isDisputeOpenerIsBuyer())
|
||||
role = Res.get("support.buyerTaker");
|
||||
else
|
||||
role = Res.get("support.sellerTaker");
|
||||
}
|
||||
role = dispute.getRoleString();
|
||||
addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("disputeSummaryWindow.role"), role);
|
||||
addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("shared.tradeAmount"),
|
||||
formatter.formatCoinWithCode(contract.getTradeAmount()));
|
||||
|
@ -314,6 +303,26 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
|
|||
" " +
|
||||
formatter.formatCoinWithCode(contract.getOfferPayload().getSellerSecurityDeposit());
|
||||
addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("shared.securityDeposit"), securityDeposit);
|
||||
|
||||
boolean isMediationDispute = getDisputeManager(dispute) instanceof MediationManager;
|
||||
if (isMediationDispute) {
|
||||
if (dispute.getTradePeriodEnd().getTime() > 0) {
|
||||
String status = DisplayUtils.formatDateTime(dispute.getTradePeriodEnd());
|
||||
Label tpe = addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("disputeSummaryWindow.tradePeriodEnd"), status).second;
|
||||
if (dispute.getTradePeriodEnd().toInstant().isAfter(Instant.now())) {
|
||||
tpe.getStyleClass().add("version-new"); // highlight field when the trade period is still active
|
||||
}
|
||||
}
|
||||
if (dispute.getExtraDataMap() != null && dispute.getExtraDataMap().size() > 0) {
|
||||
String extraDataSummary = "";
|
||||
for (Map.Entry<String, String> entry : dispute.getExtraDataMap().entrySet()) {
|
||||
extraDataSummary += "[" + entry.getKey() + ":" + entry.getValue() + "] ";
|
||||
}
|
||||
addConfirmationLabelLabelWithCopyIcon(gridPane, ++rowIndex, Res.get("disputeSummaryWindow.extraInfo"), extraDataSummary);
|
||||
}
|
||||
} else { // it is arbitration, show the delayed payout status
|
||||
delayedPayoutTxStatus = addConfirmationLabelLabel(gridPane, ++rowIndex, Res.get("disputeSummaryWindow.delayedPayoutStatus"), "Checking...").second;
|
||||
}
|
||||
}
|
||||
|
||||
private void addTradeAmountPayoutControls() {
|
||||
|
@ -639,6 +648,10 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
|
|||
Button cancelButton = tuple.second;
|
||||
|
||||
closeTicketButton.setOnAction(e -> {
|
||||
if (dispute.getPayoutTxConfirms() == 0) {
|
||||
log.warn("dispute payout tx is not confirmed");
|
||||
return;
|
||||
}
|
||||
if (dispute.getDepositTxSerialized() == null) {
|
||||
log.warn("dispute.getDepositTxSerialized is null");
|
||||
return;
|
||||
|
@ -812,7 +825,7 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
|
|||
disputeResult.setLoserPublisher(isLoserPublisherCheckBox.isSelected());
|
||||
disputeResult.setCloseDate(new Date());
|
||||
dispute.setDisputeResult(disputeResult);
|
||||
dispute.setIsClosed(true);
|
||||
dispute.setIsClosed();
|
||||
DisputeResult.Reason reason = disputeResult.getReason();
|
||||
|
||||
summaryNotesTextArea.textProperty().unbindBidirectional(disputeResult.summaryNotesProperty());
|
||||
|
@ -970,4 +983,31 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
|
|||
customRadioButton.setSelected(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkDelayedPayoutTransaction() {
|
||||
if (dispute.getDelayedPayoutTxId() == null)
|
||||
return;
|
||||
if (dispute.getPayoutTxConfirms() < 1) {
|
||||
log.warn("TODO: // PR5160 will add Mempool check of DelayedPayoutTxId");
|
||||
/*
|
||||
mempoolService.checkTxIsConfirmed(dispute.getDelayedPayoutTxId(), (status -> {
|
||||
log.warn("Mempool check confirmation status of DelayedPayoutTxId returned: [{}]", status);
|
||||
dispute.setPayoutTxConfirms(status);
|
||||
displayPayoutStatus(status);
|
||||
}));
|
||||
*/
|
||||
}
|
||||
displayPayoutStatus(dispute.getPayoutTxConfirms());
|
||||
}
|
||||
|
||||
private void displayPayoutStatus(long nConfirmStatus) {
|
||||
if (delayedPayoutTxStatus != null) {
|
||||
String status = Res.get("confidence.unknown");
|
||||
if (nConfirmStatus == 0)
|
||||
status = Res.get("confidence.seen", 1);
|
||||
else if (nConfirmStatus > 0)
|
||||
status = Res.get("confidence.confirmed", nConfirmStatus);
|
||||
delayedPayoutTxStatus.setText(status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -537,6 +537,7 @@ public class PendingTradesDataModel extends ActivatableDataModel {
|
|||
isMaker,
|
||||
pubKeyRing,
|
||||
trade.getDate().getTime(),
|
||||
trade.getMaxTradePeriodDate().getTime(),
|
||||
trade.getContract(),
|
||||
trade.getContractHash(),
|
||||
depositTxSerialized,
|
||||
|
@ -549,6 +550,8 @@ public class PendingTradesDataModel extends ActivatableDataModel {
|
|||
mediatorPubKeyRing,
|
||||
isSupportTicket,
|
||||
SupportType.MEDIATION);
|
||||
dispute.setExtraData("counterCurrencyTxId", trade.getCounterCurrencyTxId());
|
||||
dispute.setExtraData("counterCurrencyExtraData", trade.getCounterCurrencyExtraData());
|
||||
|
||||
dispute.setDonationAddressOfDelayedPayoutTx(donationAddressString.get());
|
||||
if (delayedPayoutTx != null) {
|
||||
|
@ -598,6 +601,7 @@ public class PendingTradesDataModel extends ActivatableDataModel {
|
|||
isMaker,
|
||||
pubKeyRing,
|
||||
trade.getDate().getTime(),
|
||||
trade.getMaxTradePeriodDate().getTime(),
|
||||
trade.getContract(),
|
||||
trade.getContractHash(),
|
||||
depositTxSerialized,
|
||||
|
@ -610,6 +614,8 @@ public class PendingTradesDataModel extends ActivatableDataModel {
|
|||
refundAgentPubKeyRing,
|
||||
isSupportTicket,
|
||||
SupportType.REFUND);
|
||||
dispute.setExtraData("counterCurrencyTxId", trade.getCounterCurrencyTxId());
|
||||
dispute.setExtraData("counterCurrencyExtraData", trade.getCounterCurrencyExtraData());
|
||||
|
||||
String tradeId = dispute.getTradeId();
|
||||
mediationManager.findDispute(tradeId)
|
||||
|
|
|
@ -403,7 +403,7 @@ public class PendingTradesView extends ActivatableViewAndModel<VBox, PendingTrad
|
|||
model.dataModel.getTradeManager().requestPersistence();
|
||||
tradeIdOfOpenChat = trade.getId();
|
||||
|
||||
ChatView chatView = new ChatView(traderChatManager, formatter);
|
||||
ChatView chatView = new ChatView(traderChatManager, formatter, Res.get("offerbook.trader"));
|
||||
chatView.setAllowAttachments(false);
|
||||
chatView.setDisplayHeader(false);
|
||||
chatView.initialize();
|
||||
|
|
|
@ -137,10 +137,12 @@ public class ChatView extends AnchorPane {
|
|||
private EventHandler<KeyEvent> keyEventEventHandler;
|
||||
private SupportManager supportManager;
|
||||
private Optional<SupportSession> optionalSupportSession = Optional.empty();
|
||||
private String cptyName;
|
||||
|
||||
public ChatView(SupportManager supportManager, CoinFormatter formatter) {
|
||||
public ChatView(SupportManager supportManager, CoinFormatter formatter, String cptyName) {
|
||||
this.supportManager = supportManager;
|
||||
this.formatter = formatter;
|
||||
this.cptyName = cptyName;
|
||||
allowAttachments = true;
|
||||
displayHeader = true;
|
||||
}
|
||||
|
@ -414,7 +416,11 @@ public class ChatView extends AnchorPane {
|
|||
AnchorPane.setLeftAnchor(statusHBox, padding);
|
||||
}
|
||||
AnchorPane.setBottomAnchor(statusHBox, 7d);
|
||||
headerLabel.setText(DisplayUtils.formatDateTime(new Date(message.getDate())));
|
||||
String metaData = DisplayUtils.formatDateTime(new Date(message.getDate()));
|
||||
if (!message.isSystemMessage())
|
||||
metaData = (isMyMsg ? "Sent " : "Received ") + metaData
|
||||
+ (isMyMsg ? "" : " from " + cptyName);
|
||||
headerLabel.setText(metaData);
|
||||
messageLabel.setText(message.getMessage());
|
||||
attachmentsBox.getChildren().clear();
|
||||
if (allowAttachments &&
|
||||
|
|
|
@ -0,0 +1,155 @@
|
|||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.desktop.main.support.dispute;
|
||||
|
||||
import bisq.desktop.components.AutoTooltipButton;
|
||||
import bisq.desktop.main.MainView;
|
||||
import bisq.desktop.main.shared.ChatView;
|
||||
import bisq.desktop.util.CssTheme;
|
||||
|
||||
import bisq.core.locale.Res;
|
||||
import bisq.core.support.dispute.Dispute;
|
||||
import bisq.core.support.dispute.DisputeList;
|
||||
import bisq.core.support.dispute.DisputeManager;
|
||||
import bisq.core.support.dispute.DisputeSession;
|
||||
import bisq.core.user.Preferences;
|
||||
import bisq.core.util.coin.CoinFormatter;
|
||||
|
||||
import bisq.common.UserThread;
|
||||
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.stage.StageStyle;
|
||||
import javafx.stage.Window;
|
||||
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.layout.AnchorPane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
|
||||
import javafx.beans.value.ChangeListener;
|
||||
|
||||
public class DisputeChatPopup {
|
||||
public interface ChatCallback {
|
||||
void onCloseDisputeFromChatWindow(Dispute dispute);
|
||||
}
|
||||
|
||||
private Stage chatPopupStage;
|
||||
protected final DisputeManager<? extends DisputeList<Dispute>> disputeManager;
|
||||
protected final CoinFormatter formatter;
|
||||
protected final Preferences preferences;
|
||||
private ChatCallback chatCallback;
|
||||
private double chatPopupStageXPosition = -1;
|
||||
private double chatPopupStageYPosition = -1;
|
||||
private ChangeListener<Number> xPositionListener;
|
||||
private ChangeListener<Number> yPositionListener;
|
||||
|
||||
DisputeChatPopup(DisputeManager<? extends DisputeList<Dispute>> disputeManager,
|
||||
CoinFormatter formatter,
|
||||
Preferences preferences,
|
||||
ChatCallback chatCallback) {
|
||||
this.disputeManager = disputeManager;
|
||||
this.formatter = formatter;
|
||||
this.preferences = preferences;
|
||||
this.chatCallback = chatCallback;
|
||||
}
|
||||
|
||||
public boolean isChatShown() {
|
||||
return chatPopupStage != null;
|
||||
}
|
||||
|
||||
public void closeChat() {
|
||||
if (chatPopupStage != null)
|
||||
chatPopupStage.close();
|
||||
}
|
||||
|
||||
public void openChat(Dispute selectedDispute, DisputeSession concreteDisputeSession, String cptyName) {
|
||||
closeChat();
|
||||
selectedDispute.getChatMessages().forEach(m -> m.setWasDisplayed(true));
|
||||
disputeManager.requestPersistence();
|
||||
|
||||
ChatView chatView = new ChatView(disputeManager, formatter, cptyName);
|
||||
chatView.setAllowAttachments(true);
|
||||
chatView.setDisplayHeader(false);
|
||||
chatView.initialize();
|
||||
|
||||
AnchorPane pane = new AnchorPane(chatView);
|
||||
pane.setPrefSize(760, 500);
|
||||
AnchorPane.setLeftAnchor(chatView, 10d);
|
||||
AnchorPane.setRightAnchor(chatView, 10d);
|
||||
AnchorPane.setTopAnchor(chatView, -20d);
|
||||
AnchorPane.setBottomAnchor(chatView, 10d);
|
||||
|
||||
Button closeDisputeButton = null;
|
||||
if (!selectedDispute.isClosed() && !disputeManager.isTrader(selectedDispute)) {
|
||||
closeDisputeButton = new AutoTooltipButton(Res.get("support.closeTicket"));
|
||||
closeDisputeButton.setOnAction(e -> chatCallback.onCloseDisputeFromChatWindow(selectedDispute));
|
||||
}
|
||||
chatView.display(concreteDisputeSession, closeDisputeButton, pane.widthProperty());
|
||||
chatView.activate();
|
||||
chatView.scrollToBottom();
|
||||
chatPopupStage = new Stage();
|
||||
chatPopupStage.setTitle(Res.get("tradeChat.chatWindowTitle", selectedDispute.getShortTradeId())
|
||||
+ " " + selectedDispute.getRoleString());
|
||||
StackPane owner = MainView.getRootContainer();
|
||||
Scene rootScene = owner.getScene();
|
||||
chatPopupStage.initOwner(rootScene.getWindow());
|
||||
chatPopupStage.initModality(Modality.NONE);
|
||||
chatPopupStage.initStyle(StageStyle.DECORATED);
|
||||
chatPopupStage.setOnHiding(event -> {
|
||||
chatView.deactivate();
|
||||
// at close we set all as displayed. While open we ignore updates of the numNewMsg in the list icon.
|
||||
selectedDispute.getChatMessages().forEach(m -> m.setWasDisplayed(true));
|
||||
disputeManager.requestPersistence();
|
||||
chatPopupStage = null;
|
||||
});
|
||||
|
||||
Scene scene = new Scene(pane);
|
||||
CssTheme.loadSceneStyles(scene, preferences.getCssTheme(), false);
|
||||
scene.addEventHandler(KeyEvent.KEY_RELEASED, ev -> {
|
||||
if (ev.getCode() == KeyCode.ESCAPE) {
|
||||
ev.consume();
|
||||
chatPopupStage.hide();
|
||||
}
|
||||
});
|
||||
chatPopupStage.setScene(scene);
|
||||
chatPopupStage.setOpacity(0);
|
||||
chatPopupStage.show();
|
||||
|
||||
xPositionListener = (observable, oldValue, newValue) -> chatPopupStageXPosition = (double) newValue;
|
||||
chatPopupStage.xProperty().addListener(xPositionListener);
|
||||
yPositionListener = (observable, oldValue, newValue) -> chatPopupStageYPosition = (double) newValue;
|
||||
chatPopupStage.yProperty().addListener(yPositionListener);
|
||||
|
||||
if (chatPopupStageXPosition == -1) {
|
||||
Window rootSceneWindow = rootScene.getWindow();
|
||||
double titleBarHeight = rootSceneWindow.getHeight() - rootScene.getHeight();
|
||||
chatPopupStage.setX(Math.round(rootSceneWindow.getX() + (owner.getWidth() - chatPopupStage.getWidth() / 4 * 3)));
|
||||
chatPopupStage.setY(Math.round(rootSceneWindow.getY() + titleBarHeight + (owner.getHeight() - chatPopupStage.getHeight() / 4 * 3)));
|
||||
} else {
|
||||
chatPopupStage.setX(chatPopupStageXPosition);
|
||||
chatPopupStage.setY(chatPopupStageYPosition);
|
||||
}
|
||||
|
||||
// Delay display to next render frame to avoid that the popup is first quickly displayed in default position
|
||||
// and after a short moment in the correct position
|
||||
UserThread.execute(() -> chatPopupStage.setOpacity(1));
|
||||
}
|
||||
}
|
|
@ -29,8 +29,8 @@ import bisq.desktop.main.overlays.windows.DisputeSummaryWindow;
|
|||
import bisq.desktop.main.overlays.windows.SendPrivateNotificationWindow;
|
||||
import bisq.desktop.main.overlays.windows.TradeDetailsWindow;
|
||||
import bisq.desktop.main.overlays.windows.VerifyDisputeResultSignatureWindow;
|
||||
import bisq.desktop.main.shared.ChatView;
|
||||
import bisq.desktop.util.DisplayUtils;
|
||||
import bisq.desktop.util.FormBuilder;
|
||||
import bisq.desktop.util.GUIUtil;
|
||||
|
||||
import bisq.core.account.witness.AccountAgeWitnessService;
|
||||
|
@ -45,17 +45,20 @@ import bisq.core.support.dispute.DisputeManager;
|
|||
import bisq.core.support.dispute.DisputeResult;
|
||||
import bisq.core.support.dispute.DisputeSession;
|
||||
import bisq.core.support.dispute.agent.DisputeAgentLookupMap;
|
||||
import bisq.core.support.dispute.mediation.MediationManager;
|
||||
import bisq.core.support.dispute.mediation.mediator.MediatorManager;
|
||||
import bisq.core.support.dispute.refund.refundagent.RefundAgentManager;
|
||||
import bisq.core.support.messages.ChatMessage;
|
||||
import bisq.core.trade.Contract;
|
||||
import bisq.core.trade.Trade;
|
||||
import bisq.core.trade.TradeManager;
|
||||
import bisq.core.user.Preferences;
|
||||
import bisq.core.util.FormattingUtils;
|
||||
import bisq.core.util.coin.CoinFormatter;
|
||||
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
|
||||
import bisq.common.UserThread;
|
||||
import bisq.common.crypto.KeyRing;
|
||||
import bisq.common.crypto.PubKeyRing;
|
||||
import bisq.common.util.Utilities;
|
||||
|
@ -64,6 +67,8 @@ import org.bitcoinj.core.Coin;
|
|||
|
||||
import de.jensd.fx.glyphs.materialdesignicons.MaterialDesignIcon;
|
||||
|
||||
import com.jfoenix.controls.JFXBadge;
|
||||
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TableCell;
|
||||
|
@ -77,6 +82,7 @@ import javafx.scene.layout.VBox;
|
|||
import javafx.scene.text.Text;
|
||||
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
|
||||
import org.fxmisc.easybind.EasyBind;
|
||||
import org.fxmisc.easybind.Subscription;
|
||||
|
@ -110,6 +116,7 @@ import lombok.Getter;
|
|||
import javax.annotation.Nullable;
|
||||
|
||||
import static bisq.desktop.util.FormBuilder.getIconForLabel;
|
||||
import static bisq.desktop.util.FormBuilder.getRegularIconButton;
|
||||
|
||||
public abstract class DisputeView extends ActivatableView<VBox, Void> {
|
||||
public enum FilterResult {
|
||||
|
@ -143,6 +150,7 @@ public abstract class DisputeView extends ActivatableView<VBox, Void> {
|
|||
protected final KeyRing keyRing;
|
||||
private final TradeManager tradeManager;
|
||||
protected final CoinFormatter formatter;
|
||||
protected final Preferences preferences;
|
||||
protected final DisputeSummaryWindow disputeSummaryWindow;
|
||||
private final PrivateNotificationManager privateNotificationManager;
|
||||
private final ContractWindow contractWindow;
|
||||
|
@ -160,19 +168,21 @@ public abstract class DisputeView extends ActivatableView<VBox, Void> {
|
|||
@Getter
|
||||
protected Dispute selectedDispute;
|
||||
|
||||
protected ChatView chatView;
|
||||
|
||||
private ChangeListener<Boolean> selectedDisputeClosedPropertyListener;
|
||||
private Subscription selectedDisputeSubscription;
|
||||
protected FilteredList<Dispute> filteredList;
|
||||
protected InputTextField filterTextField;
|
||||
private ChangeListener<String> filterTextFieldListener;
|
||||
protected AutoTooltipButton sigCheckButton, reOpenButton, sendPrivateNotificationButton, reportButton, fullReportButton;
|
||||
protected AutoTooltipButton sigCheckButton, reOpenButton, closeButton, sendPrivateNotificationButton, reportButton, fullReportButton;
|
||||
private final Map<String, ListChangeListener<ChatMessage>> disputeChatMessagesListeners = new HashMap<>();
|
||||
@Nullable
|
||||
private ListChangeListener<Dispute> disputesListener; // Only set in mediation cases
|
||||
protected Label alertIconLabel;
|
||||
protected TableColumn<Dispute, Dispute> stateColumn;
|
||||
private Map<String, ListChangeListener<ChatMessage>> listenerByDispute = new HashMap<>();
|
||||
private Map<String, Button> chatButtonByDispute = new HashMap<>();
|
||||
private Map<String, JFXBadge> chatBadgeByDispute = new HashMap<>();
|
||||
private Map<String, JFXBadge> newBadgeByDispute = new HashMap<>();
|
||||
protected DisputeChatPopup chatPopup;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -183,6 +193,7 @@ public abstract class DisputeView extends ActivatableView<VBox, Void> {
|
|||
KeyRing keyRing,
|
||||
TradeManager tradeManager,
|
||||
CoinFormatter formatter,
|
||||
Preferences preferences,
|
||||
DisputeSummaryWindow disputeSummaryWindow,
|
||||
PrivateNotificationManager privateNotificationManager,
|
||||
ContractWindow contractWindow,
|
||||
|
@ -196,6 +207,7 @@ public abstract class DisputeView extends ActivatableView<VBox, Void> {
|
|||
this.keyRing = keyRing;
|
||||
this.tradeManager = tradeManager;
|
||||
this.formatter = formatter;
|
||||
this.preferences = preferences;
|
||||
this.disputeSummaryWindow = disputeSummaryWindow;
|
||||
this.privateNotificationManager = privateNotificationManager;
|
||||
this.contractWindow = contractWindow;
|
||||
|
@ -205,6 +217,8 @@ public abstract class DisputeView extends ActivatableView<VBox, Void> {
|
|||
this.refundAgentManager = refundAgentManager;
|
||||
this.daoFacade = daoFacade;
|
||||
this.useDevPrivilegeKeys = useDevPrivilegeKeys;
|
||||
DisputeChatPopup.ChatCallback chatCallback = this::handleOnProcessDispute;
|
||||
chatPopup = new DisputeChatPopup(disputeManager, formatter, preferences, chatCallback);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -238,6 +252,15 @@ public abstract class DisputeView extends ActivatableView<VBox, Void> {
|
|||
reOpenDisputeFromButton();
|
||||
});
|
||||
|
||||
closeButton = new AutoTooltipButton(Res.get("support.closeTicket"));
|
||||
closeButton.setDisable(true);
|
||||
closeButton.setVisible(false);
|
||||
closeButton.setManaged(false);
|
||||
HBox.setHgrow(closeButton, Priority.NEVER);
|
||||
closeButton.setOnAction(e -> {
|
||||
closeDisputeFromButton();
|
||||
});
|
||||
|
||||
sendPrivateNotificationButton = new AutoTooltipButton(Res.get("support.sendNotificationButton.label"));
|
||||
sendPrivateNotificationButton.setDisable(true);
|
||||
sendPrivateNotificationButton.setVisible(false);
|
||||
|
@ -279,6 +302,7 @@ public abstract class DisputeView extends ActivatableView<VBox, Void> {
|
|||
alertIconLabel,
|
||||
spacer,
|
||||
reOpenButton,
|
||||
closeButton,
|
||||
sendPrivateNotificationButton,
|
||||
reportButton,
|
||||
fullReportButton,
|
||||
|
@ -292,11 +316,6 @@ public abstract class DisputeView extends ActivatableView<VBox, Void> {
|
|||
root.getChildren().addAll(filterBox, tableView);
|
||||
|
||||
setupTable();
|
||||
|
||||
selectedDisputeClosedPropertyListener = (observable, oldValue, newValue) -> chatView.setInputBoxVisible(!newValue);
|
||||
|
||||
chatView = new ChatView(disputeManager, formatter);
|
||||
chatView.initialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -311,7 +330,6 @@ public abstract class DisputeView extends ActivatableView<VBox, Void> {
|
|||
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
|
||||
tableView.setItems(sortedList);
|
||||
|
||||
// sortedList.setComparator((o1, o2) -> o2.getOpeningDate().compareTo(o1.getOpeningDate()));
|
||||
selectedDisputeSubscription = EasyBind.subscribe(tableView.getSelectionModel().selectedItemProperty(), this::onSelectDispute);
|
||||
|
||||
Dispute selectedItem = tableView.getSelectionModel().getSelectedItem();
|
||||
|
@ -320,11 +338,6 @@ public abstract class DisputeView extends ActivatableView<VBox, Void> {
|
|||
else if (sortedList.size() > 0)
|
||||
tableView.getSelectionModel().select(0);
|
||||
|
||||
if (chatView != null) {
|
||||
chatView.activate();
|
||||
chatView.scrollToBottom();
|
||||
}
|
||||
|
||||
GUIUtil.requestFocus(filterTextField);
|
||||
}
|
||||
|
||||
|
@ -333,10 +346,6 @@ public abstract class DisputeView extends ActivatableView<VBox, Void> {
|
|||
filterTextField.textProperty().removeListener(filterTextFieldListener);
|
||||
sortedList.comparatorProperty().unbind();
|
||||
selectedDisputeSubscription.unsubscribe();
|
||||
removeListenersOnSelectDispute();
|
||||
|
||||
if (chatView != null)
|
||||
chatView.deactivate();
|
||||
}
|
||||
|
||||
|
||||
|
@ -385,6 +394,8 @@ public abstract class DisputeView extends ActivatableView<VBox, Void> {
|
|||
|
||||
protected abstract DisputeSession getConcreteDisputeChatSession(Dispute dispute);
|
||||
|
||||
protected abstract boolean senderFlag(); // implemented in the agent / client views
|
||||
|
||||
protected void applyFilteredListPredicate(String filterString) {
|
||||
AtomicReference<FilterResult> filterResult = new AtomicReference<>(FilterResult.NO_FILTER);
|
||||
filteredList.setPredicate(dispute -> {
|
||||
|
@ -468,18 +479,35 @@ public abstract class DisputeView extends ActivatableView<VBox, Void> {
|
|||
return FilterResult.NO_MATCH;
|
||||
}
|
||||
|
||||
|
||||
// a derived version in the ClientView for users pops up an "Are you sure" box first.
|
||||
// this version includes the sending of an automatic message to the user, see addMediationReOpenedMessage
|
||||
protected void reOpenDisputeFromButton() {
|
||||
reOpenDispute();
|
||||
disputeManager.addMediationReOpenedMessage(selectedDispute, false);
|
||||
}
|
||||
|
||||
protected abstract void handleOnSelectDispute(Dispute dispute);
|
||||
// only applicable to traders
|
||||
// only allow them to close the dispute if the trade is paid out
|
||||
// the reason for having this is that sometimes traders end up with closed disputes that are not "closed" @pazza
|
||||
protected void closeDisputeFromButton() {
|
||||
Optional<Trade> tradeOptional = disputeManager.findTrade(selectedDispute);
|
||||
if (tradeOptional.isPresent() && tradeOptional.get().getPayoutTxId() != null && tradeOptional.get().getPayoutTxId().length() > 0) {
|
||||
selectedDispute.setIsClosed();
|
||||
disputeManager.requestPersistence();
|
||||
onSelectDispute(selectedDispute);
|
||||
}
|
||||
}
|
||||
|
||||
protected void handleOnProcessDispute(Dispute dispute) {
|
||||
// overridden by clients that use it (dispute agents)
|
||||
}
|
||||
|
||||
protected void reOpenDispute() {
|
||||
if (selectedDispute != null) {
|
||||
selectedDispute.setIsClosed(false);
|
||||
handleOnSelectDispute(selectedDispute);
|
||||
if (selectedDispute != null && selectedDispute.isClosed()) {
|
||||
selectedDispute.reOpen();
|
||||
handleOnProcessDispute(selectedDispute);
|
||||
disputeManager.requestPersistence();
|
||||
onSelectDispute(selectedDispute);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -488,46 +516,21 @@ public abstract class DisputeView extends ActivatableView<VBox, Void> {
|
|||
// UI actions
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private void onOpenContract(Dispute dispute) {
|
||||
protected void onOpenContract(Dispute dispute) {
|
||||
dispute.setDisputeSeen(senderFlag());
|
||||
contractWindow.show(dispute);
|
||||
}
|
||||
|
||||
private void removeListenersOnSelectDispute() {
|
||||
if (selectedDispute != null) {
|
||||
if (selectedDisputeClosedPropertyListener != null)
|
||||
selectedDispute.isClosedProperty().removeListener(selectedDisputeClosedPropertyListener);
|
||||
}
|
||||
}
|
||||
|
||||
private void addListenersOnSelectDispute() {
|
||||
if (selectedDispute != null)
|
||||
selectedDispute.isClosedProperty().addListener(selectedDisputeClosedPropertyListener);
|
||||
}
|
||||
|
||||
private void onSelectDispute(Dispute dispute) {
|
||||
removeListenersOnSelectDispute();
|
||||
if (dispute == null) {
|
||||
if (root.getChildren().size() > 2) {
|
||||
root.getChildren().remove(2);
|
||||
}
|
||||
|
||||
selectedDispute = null;
|
||||
} else if (selectedDispute != dispute) {
|
||||
selectedDispute = dispute;
|
||||
if (chatView != null) {
|
||||
handleOnSelectDispute(dispute);
|
||||
}
|
||||
|
||||
if (root.getChildren().size() > 2) {
|
||||
root.getChildren().remove(2);
|
||||
}
|
||||
root.getChildren().add(2, chatView);
|
||||
}
|
||||
|
||||
reOpenButton.setDisable(selectedDispute == null || !selectedDispute.isClosed());
|
||||
closeButton.setDisable(selectedDispute == null || selectedDispute.isClosed());
|
||||
sendPrivateNotificationButton.setDisable(selectedDispute == null);
|
||||
|
||||
addListenersOnSelectDispute();
|
||||
}
|
||||
|
||||
|
||||
|
@ -887,10 +890,7 @@ public abstract class DisputeView extends ActivatableView<VBox, Void> {
|
|||
tableView.setPlaceholder(placeholder);
|
||||
tableView.getSelectionModel().clearSelection();
|
||||
|
||||
tableView.getColumns().add(getSelectColumn());
|
||||
|
||||
TableColumn<Dispute, Dispute> contractColumn = getContractColumn();
|
||||
tableView.getColumns().add(contractColumn);
|
||||
tableView.getColumns().add(getContractColumn());
|
||||
|
||||
TableColumn<Dispute, Dispute> dateColumn = getDateColumn();
|
||||
tableView.getColumns().add(dateColumn);
|
||||
|
@ -904,18 +904,19 @@ public abstract class DisputeView extends ActivatableView<VBox, Void> {
|
|||
TableColumn<Dispute, Dispute> sellerOnionAddressColumn = getSellerOnionAddressColumn();
|
||||
tableView.getColumns().add(sellerOnionAddressColumn);
|
||||
|
||||
|
||||
TableColumn<Dispute, Dispute> marketColumn = getMarketColumn();
|
||||
tableView.getColumns().add(marketColumn);
|
||||
|
||||
TableColumn<Dispute, Dispute> roleColumn = getRoleColumn();
|
||||
tableView.getColumns().add(roleColumn);
|
||||
tableView.getColumns().add(getRoleColumn());
|
||||
|
||||
maybeAddAgentColumn();
|
||||
|
||||
stateColumn = getStateColumn();
|
||||
tableView.getColumns().add(stateColumn);
|
||||
|
||||
maybeAddProcessColumn();
|
||||
tableView.getColumns().add(getChatColumn());
|
||||
|
||||
tradeIdColumn.setComparator(Comparator.comparing(Dispute::getTradeId));
|
||||
dateColumn.setComparator(Comparator.comparing(Dispute::getOpeningDate));
|
||||
buyerOnionAddressColumn.setComparator(Comparator.comparing(this::getBuyerOnionAddressColumnLabel));
|
||||
|
@ -926,6 +927,10 @@ public abstract class DisputeView extends ActivatableView<VBox, Void> {
|
|||
tableView.getSortOrder().add(dateColumn);
|
||||
}
|
||||
|
||||
protected void maybeAddProcessColumn() {
|
||||
// Only relevant client views will impl it
|
||||
}
|
||||
|
||||
protected void maybeAddAgentColumn() {
|
||||
// Only relevant client views will impl it
|
||||
}
|
||||
|
@ -935,41 +940,42 @@ public abstract class DisputeView extends ActivatableView<VBox, Void> {
|
|||
return null;
|
||||
}
|
||||
|
||||
private TableColumn<Dispute, Dispute> getSelectColumn() {
|
||||
TableColumn<Dispute, Dispute> column = new AutoTooltipTableColumn<>(Res.get("shared.select"));
|
||||
column.setMinWidth(80);
|
||||
column.setMaxWidth(80);
|
||||
column.setSortable(false);
|
||||
column.getStyleClass().add("first-column");
|
||||
|
||||
column.setCellValueFactory((addressListItem) ->
|
||||
new ReadOnlyObjectWrapper<>(addressListItem.getValue()));
|
||||
private TableColumn<Dispute, Dispute> getContractColumn() {
|
||||
TableColumn<Dispute, Dispute> column = new AutoTooltipTableColumn<>(Res.get("shared.details")) {
|
||||
{
|
||||
setMaxWidth(150);
|
||||
setMinWidth(80);
|
||||
getStyleClass().addAll("first-column", "avatar-column");
|
||||
setSortable(false);
|
||||
}
|
||||
};
|
||||
column.setCellValueFactory((dispute) -> new ReadOnlyObjectWrapper<>(dispute.getValue()));
|
||||
column.setCellFactory(
|
||||
new Callback<>() {
|
||||
|
||||
@Override
|
||||
public TableCell<Dispute, Dispute> call(TableColumn<Dispute,
|
||||
Dispute> column) {
|
||||
public TableCell<Dispute, Dispute> call(TableColumn<Dispute, Dispute> column) {
|
||||
return new TableCell<>() {
|
||||
|
||||
Button button;
|
||||
|
||||
@Override
|
||||
public void updateItem(final Dispute item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
|
||||
if (item != null && !empty) {
|
||||
if (button == null) {
|
||||
button = new AutoTooltipButton(Res.get("shared.select"));
|
||||
setGraphic(button);
|
||||
}
|
||||
button.setOnAction(e -> tableView.getSelectionModel().select(item));
|
||||
Button button = getRegularIconButton(MaterialDesignIcon.INFORMATION_OUTLINE);
|
||||
JFXBadge badge = new JFXBadge(new Label(""), Pos.BASELINE_RIGHT);
|
||||
badge.setPosition(Pos.TOP_RIGHT);
|
||||
badge.setVisible(item.isNew());
|
||||
badge.setText("New");
|
||||
badge.getStyleClass().add("auto-conf");
|
||||
newBadgeByDispute.put(item.getId(), badge);
|
||||
HBox hBox = new HBox(button, badge);
|
||||
setGraphic(hBox);
|
||||
button.setOnAction(e -> {
|
||||
tableView.getSelectionModel().select(this.getIndex());
|
||||
onOpenContract(item);
|
||||
item.clearNewFlag();
|
||||
badge.setVisible(item.isNew());
|
||||
});
|
||||
} else {
|
||||
setGraphic(null);
|
||||
if (button != null) {
|
||||
button.setOnAction(null);
|
||||
button = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -978,38 +984,97 @@ public abstract class DisputeView extends ActivatableView<VBox, Void> {
|
|||
return column;
|
||||
}
|
||||
|
||||
private TableColumn<Dispute, Dispute> getContractColumn() {
|
||||
TableColumn<Dispute, Dispute> column = new AutoTooltipTableColumn<>(Res.get("shared.details")) {
|
||||
protected TableColumn<Dispute, Dispute> getProcessColumn() {
|
||||
TableColumn<Dispute, Dispute> column = new AutoTooltipTableColumn<>(Res.get("support.process")) {
|
||||
{
|
||||
setMinWidth(80);
|
||||
setMaxWidth(50);
|
||||
setMinWidth(50);
|
||||
getStyleClass().addAll("avatar-column");
|
||||
setSortable(false);
|
||||
}
|
||||
};
|
||||
column.setCellValueFactory((dispute) -> new ReadOnlyObjectWrapper<>(dispute.getValue()));
|
||||
column.setCellFactory(
|
||||
new Callback<>() {
|
||||
|
||||
@Override
|
||||
public TableCell<Dispute, Dispute> call(TableColumn<Dispute, Dispute> column) {
|
||||
return new TableCell<>() {
|
||||
Button button;
|
||||
|
||||
@Override
|
||||
public void updateItem(final Dispute item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
|
||||
if (item != null && !empty) {
|
||||
if (button == null) {
|
||||
button = new AutoTooltipButton(Res.get("shared.details"));
|
||||
setGraphic(button);
|
||||
}
|
||||
button.setOnAction(e -> onOpenContract(item));
|
||||
Button button = getRegularIconButton(MaterialDesignIcon.SETTINGS);
|
||||
button.setOnAction(e -> {
|
||||
tableView.getSelectionModel().select(this.getIndex());
|
||||
handleOnProcessDispute(item);
|
||||
item.clearNewFlag();
|
||||
newBadgeByDispute.get(item.getId()).setVisible(item.isNew());
|
||||
});
|
||||
HBox hBox = new HBox(button);
|
||||
hBox.setAlignment(Pos.CENTER);
|
||||
setGraphic(hBox);
|
||||
} else {
|
||||
setGraphic(null);
|
||||
if (button != null) {
|
||||
button.setOnAction(null);
|
||||
button = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
return column;
|
||||
}
|
||||
|
||||
private TableColumn<Dispute, Dispute> getChatColumn() {
|
||||
TableColumn<Dispute, Dispute> column = new AutoTooltipTableColumn<>(Res.get("support.chat")) {
|
||||
{
|
||||
setMaxWidth(40);
|
||||
setMinWidth(40);
|
||||
getStyleClass().addAll("avatar-column");
|
||||
setSortable(false);
|
||||
}
|
||||
};
|
||||
column.setCellValueFactory((dispute) -> new ReadOnlyObjectWrapper<>(dispute.getValue()));
|
||||
column.setCellFactory(
|
||||
new Callback<>() {
|
||||
@Override
|
||||
public TableCell<Dispute, Dispute> call(TableColumn<Dispute, Dispute> column) {
|
||||
return new TableCell<>() {
|
||||
@Override
|
||||
public void updateItem(final Dispute item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (item != null && !empty) {
|
||||
String id = item.getId();
|
||||
Button button;
|
||||
if (!chatButtonByDispute.containsKey(id)) {
|
||||
button = FormBuilder.getIconButton(MaterialDesignIcon.COMMENT_MULTIPLE_OUTLINE);
|
||||
chatButtonByDispute.put(id, button);
|
||||
button.setTooltip(new Tooltip(Res.get("tradeChat.openChat")));
|
||||
} else {
|
||||
button = chatButtonByDispute.get(id);
|
||||
}
|
||||
JFXBadge chatBadge;
|
||||
if (!chatBadgeByDispute.containsKey(id)) {
|
||||
chatBadge = new JFXBadge(button);
|
||||
chatBadgeByDispute.put(id, chatBadge);
|
||||
chatBadge.setPosition(Pos.TOP_RIGHT);
|
||||
} else {
|
||||
chatBadge = chatBadgeByDispute.get(id);
|
||||
}
|
||||
button.setOnAction(e -> {
|
||||
tableView.getSelectionModel().select(this.getIndex());
|
||||
chatPopup.openChat(item, getConcreteDisputeChatSession(item), getCounterpartyName());
|
||||
item.clearNewFlag();
|
||||
newBadgeByDispute.get(id).setVisible(item.isNew());
|
||||
updateChatMessageCount(item, chatBadge);
|
||||
});
|
||||
if (!listenerByDispute.containsKey(id)) {
|
||||
ListChangeListener<ChatMessage> listener = c -> updateChatMessageCount(item, chatBadge);
|
||||
listenerByDispute.put(id, listener);
|
||||
item.getChatMessages().addListener(listener);
|
||||
}
|
||||
updateChatMessageCount(item, chatBadge);
|
||||
setGraphic(chatBadge);
|
||||
} else {
|
||||
setGraphic(null);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -1214,10 +1279,7 @@ public abstract class DisputeView extends ActivatableView<VBox, Void> {
|
|||
public void updateItem(final Dispute item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (item != null && !empty) {
|
||||
if (item.isDisputeOpenerIsMaker())
|
||||
setText(item.isDisputeOpenerIsBuyer() ? Res.get("support.buyerOfferer") : Res.get("support.sellerOfferer"));
|
||||
else
|
||||
setText(item.isDisputeOpenerIsBuyer() ? Res.get("support.buyerTaker") : Res.get("support.sellerTaker"));
|
||||
setText(item.getRoleString());
|
||||
} else {
|
||||
setText("");
|
||||
}
|
||||
|
@ -1312,6 +1374,36 @@ public abstract class DisputeView extends ActivatableView<VBox, Void> {
|
|||
});
|
||||
return column;
|
||||
}
|
||||
|
||||
private void updateChatMessageCount(Dispute dispute, JFXBadge chatBadge) {
|
||||
if (chatBadge == null)
|
||||
return;
|
||||
// when the chat popup is active, we do not display new message count indicator for that item
|
||||
if (chatPopup.isChatShown() && selectedDispute != null && dispute.getId().equals(selectedDispute.getId())) {
|
||||
chatBadge.setText("");
|
||||
chatBadge.setEnabled(false);
|
||||
chatBadge.refreshBadge();
|
||||
// have to UserThread.execute or the new message will be sent to peer as "read"
|
||||
UserThread.execute(() -> dispute.setChatMessagesSeen(senderFlag()));
|
||||
return;
|
||||
}
|
||||
|
||||
if (dispute.unreadMessageCount(senderFlag()) > 0) {
|
||||
chatBadge.setText(String.valueOf(dispute.unreadMessageCount(senderFlag())));
|
||||
chatBadge.setEnabled(true);
|
||||
} else {
|
||||
chatBadge.setText("");
|
||||
chatBadge.setEnabled(false);
|
||||
}
|
||||
chatBadge.refreshBadge();
|
||||
dispute.refreshAlertLevel(senderFlag());
|
||||
}
|
||||
|
||||
private String getCounterpartyName() {
|
||||
if (senderFlag()) {
|
||||
return Res.get("offerbook.trader");
|
||||
} else {
|
||||
return (disputeManager instanceof MediationManager) ? Res.get("shared.mediator") : Res.get("shared.refundAgent");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
|
||||
package bisq.desktop.main.support.dispute.agent;
|
||||
|
||||
import bisq.desktop.components.AutoTooltipButton;
|
||||
import bisq.desktop.components.AutoTooltipTableColumn;
|
||||
import bisq.desktop.main.overlays.popups.Popup;
|
||||
import bisq.desktop.main.overlays.windows.ContractWindow;
|
||||
|
@ -32,13 +31,13 @@ import bisq.core.locale.Res;
|
|||
import bisq.core.support.dispute.Dispute;
|
||||
import bisq.core.support.dispute.DisputeList;
|
||||
import bisq.core.support.dispute.DisputeManager;
|
||||
import bisq.core.support.dispute.DisputeSession;
|
||||
import bisq.core.support.dispute.agent.MultipleHolderNameDetection;
|
||||
import bisq.core.support.dispute.mediation.mediator.MediatorManager;
|
||||
import bisq.core.support.dispute.refund.refundagent.RefundAgentManager;
|
||||
import bisq.core.trade.TradeDataValidation;
|
||||
import bisq.core.trade.TradeManager;
|
||||
import bisq.core.user.DontShowAgainLookup;
|
||||
import bisq.core.user.Preferences;
|
||||
import bisq.core.util.coin.CoinFormatter;
|
||||
|
||||
import bisq.common.crypto.KeyRing;
|
||||
|
@ -46,7 +45,6 @@ import bisq.common.util.Utilities;
|
|||
|
||||
import de.jensd.fx.glyphs.materialdesignicons.MaterialDesignIcon;
|
||||
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TableCell;
|
||||
import javafx.scene.control.TableColumn;
|
||||
|
@ -76,6 +74,7 @@ public abstract class DisputeAgentView extends DisputeView implements MultipleHo
|
|||
KeyRing keyRing,
|
||||
TradeManager tradeManager,
|
||||
CoinFormatter formatter,
|
||||
Preferences preferences,
|
||||
DisputeSummaryWindow disputeSummaryWindow,
|
||||
PrivateNotificationManager privateNotificationManager,
|
||||
ContractWindow contractWindow,
|
||||
|
@ -89,6 +88,7 @@ public abstract class DisputeAgentView extends DisputeView implements MultipleHo
|
|||
keyRing,
|
||||
tradeManager,
|
||||
formatter,
|
||||
preferences,
|
||||
disputeSummaryWindow,
|
||||
privateNotificationManager,
|
||||
contractWindow,
|
||||
|
@ -216,14 +216,8 @@ public abstract class DisputeAgentView extends DisputeView implements MultipleHo
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void handleOnSelectDispute(Dispute dispute) {
|
||||
Button closeDisputeButton = null;
|
||||
if (!dispute.isClosed() && !disputeManager.isTrader(dispute)) {
|
||||
closeDisputeButton = new AutoTooltipButton(Res.get("support.closeTicket"));
|
||||
closeDisputeButton.setOnAction(e -> onCloseDispute(getSelectedDispute()));
|
||||
}
|
||||
DisputeSession chatSession = getConcreteDisputeChatSession(dispute);
|
||||
chatView.display(chatSession, closeDisputeButton, root.widthProperty());
|
||||
protected void handleOnProcessDispute(Dispute dispute) {
|
||||
onCloseDispute(dispute);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -352,6 +346,16 @@ public abstract class DisputeAgentView extends DisputeView implements MultipleHo
|
|||
"to them so they can ban those traders.\n\n" +
|
||||
Utilities.toTruncatedString(report, 700, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void maybeAddProcessColumn() {
|
||||
tableView.getColumns().add(getProcessColumn());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean senderFlag() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@ import bisq.core.support.dispute.arbitration.ArbitrationSession;
|
|||
import bisq.core.support.dispute.mediation.mediator.MediatorManager;
|
||||
import bisq.core.support.dispute.refund.refundagent.RefundAgentManager;
|
||||
import bisq.core.trade.TradeManager;
|
||||
import bisq.core.user.Preferences;
|
||||
import bisq.core.util.FormattingUtils;
|
||||
import bisq.core.util.coin.CoinFormatter;
|
||||
|
||||
|
@ -53,6 +54,7 @@ public class ArbitratorView extends DisputeAgentView {
|
|||
KeyRing keyRing,
|
||||
TradeManager tradeManager,
|
||||
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter formatter,
|
||||
Preferences preferences,
|
||||
DisputeSummaryWindow disputeSummaryWindow,
|
||||
PrivateNotificationManager privateNotificationManager,
|
||||
ContractWindow contractWindow,
|
||||
|
@ -66,6 +68,7 @@ public class ArbitratorView extends DisputeAgentView {
|
|||
keyRing,
|
||||
tradeManager,
|
||||
formatter,
|
||||
preferences,
|
||||
disputeSummaryWindow,
|
||||
privateNotificationManager,
|
||||
contractWindow,
|
||||
|
@ -94,7 +97,8 @@ public class ArbitratorView extends DisputeAgentView {
|
|||
// This code path is not tested and it is not assumed that it is still be used as old arbitrators would use
|
||||
// their old Bisq version if still cases are pending.
|
||||
if (protocolVersion == 1) {
|
||||
disputeSummaryWindow.onFinalizeDispute(() -> chatView.removeInputBox()).show(dispute);
|
||||
chatPopup.closeChat();
|
||||
disputeSummaryWindow.show(dispute);
|
||||
} else {
|
||||
new Popup().warning(Res.get("support.wrongVersion", protocolVersion)).show();
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@ import bisq.core.support.dispute.mediation.MediationSession;
|
|||
import bisq.core.support.dispute.mediation.mediator.MediatorManager;
|
||||
import bisq.core.support.dispute.refund.refundagent.RefundAgentManager;
|
||||
import bisq.core.trade.TradeManager;
|
||||
import bisq.core.user.Preferences;
|
||||
import bisq.core.util.FormattingUtils;
|
||||
import bisq.core.util.coin.CoinFormatter;
|
||||
|
||||
|
@ -51,6 +52,7 @@ public class MediatorView extends DisputeAgentView {
|
|||
KeyRing keyRing,
|
||||
TradeManager tradeManager,
|
||||
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter formatter,
|
||||
Preferences preferences,
|
||||
DisputeSummaryWindow disputeSummaryWindow,
|
||||
PrivateNotificationManager privateNotificationManager,
|
||||
ContractWindow contractWindow,
|
||||
|
@ -64,6 +66,7 @@ public class MediatorView extends DisputeAgentView {
|
|||
keyRing,
|
||||
tradeManager,
|
||||
formatter,
|
||||
preferences,
|
||||
disputeSummaryWindow,
|
||||
privateNotificationManager,
|
||||
contractWindow,
|
||||
|
@ -112,6 +115,7 @@ public class MediatorView extends DisputeAgentView {
|
|||
|
||||
@Override
|
||||
protected void onCloseDispute(Dispute dispute) {
|
||||
disputeSummaryWindow.onFinalizeDispute(() -> chatView.removeInputBox()).show(dispute);
|
||||
chatPopup.closeChat();
|
||||
disputeSummaryWindow.show(dispute);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ import bisq.core.support.dispute.refund.RefundManager;
|
|||
import bisq.core.support.dispute.refund.RefundSession;
|
||||
import bisq.core.support.dispute.refund.refundagent.RefundAgentManager;
|
||||
import bisq.core.trade.TradeManager;
|
||||
import bisq.core.user.Preferences;
|
||||
import bisq.core.util.FormattingUtils;
|
||||
import bisq.core.util.coin.CoinFormatter;
|
||||
|
||||
|
@ -53,6 +54,7 @@ public class RefundAgentView extends DisputeAgentView {
|
|||
KeyRing keyRing,
|
||||
TradeManager tradeManager,
|
||||
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter formatter,
|
||||
Preferences preferences,
|
||||
DisputeSummaryWindow disputeSummaryWindow,
|
||||
PrivateNotificationManager privateNotificationManager,
|
||||
ContractWindow contractWindow,
|
||||
|
@ -66,6 +68,7 @@ public class RefundAgentView extends DisputeAgentView {
|
|||
keyRing,
|
||||
tradeManager,
|
||||
formatter,
|
||||
preferences,
|
||||
disputeSummaryWindow,
|
||||
privateNotificationManager,
|
||||
contractWindow,
|
||||
|
@ -92,7 +95,8 @@ public class RefundAgentView extends DisputeAgentView {
|
|||
long protocolVersion = dispute.getContract().getOfferPayload().getProtocolVersion();
|
||||
// Refund agent was introduced with protocolVersion version 2. We do not support old trade protocol cases.
|
||||
if (protocolVersion >= 2) {
|
||||
disputeSummaryWindow.onFinalizeDispute(() -> chatView.removeInputBox()).show(dispute);
|
||||
chatPopup.closeChat();
|
||||
disputeSummaryWindow.show(dispute);
|
||||
} else {
|
||||
new Popup().warning(Res.get("support.wrongVersion", protocolVersion)).show();
|
||||
}
|
||||
|
|
|
@ -28,10 +28,10 @@ import bisq.core.dao.DaoFacade;
|
|||
import bisq.core.support.dispute.Dispute;
|
||||
import bisq.core.support.dispute.DisputeList;
|
||||
import bisq.core.support.dispute.DisputeManager;
|
||||
import bisq.core.support.dispute.DisputeSession;
|
||||
import bisq.core.support.dispute.mediation.mediator.MediatorManager;
|
||||
import bisq.core.support.dispute.refund.refundagent.RefundAgentManager;
|
||||
import bisq.core.trade.TradeManager;
|
||||
import bisq.core.user.Preferences;
|
||||
import bisq.core.util.coin.CoinFormatter;
|
||||
|
||||
import bisq.common.crypto.KeyRing;
|
||||
|
@ -41,6 +41,7 @@ public abstract class DisputeClientView extends DisputeView {
|
|||
KeyRing keyRing,
|
||||
TradeManager tradeManager,
|
||||
CoinFormatter formatter,
|
||||
Preferences preferences,
|
||||
DisputeSummaryWindow disputeSummaryWindow,
|
||||
PrivateNotificationManager privateNotificationManager,
|
||||
ContractWindow contractWindow,
|
||||
|
@ -50,17 +51,11 @@ public abstract class DisputeClientView extends DisputeView {
|
|||
RefundAgentManager refundAgentManager,
|
||||
DaoFacade daoFacade,
|
||||
boolean useDevPrivilegeKeys) {
|
||||
super(DisputeManager, keyRing, tradeManager, formatter, disputeSummaryWindow, privateNotificationManager,
|
||||
super(DisputeManager, keyRing, tradeManager, formatter, preferences, disputeSummaryWindow, privateNotificationManager,
|
||||
contractWindow, tradeDetailsWindow, accountAgeWitnessService,
|
||||
mediatorManager, refundAgentManager, daoFacade, useDevPrivilegeKeys);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleOnSelectDispute(Dispute dispute) {
|
||||
DisputeSession chatSession = getConcreteDisputeChatSession(dispute);
|
||||
chatView.display(chatSession, root.widthProperty());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DisputeView.FilterResult getFilterResult(Dispute dispute, String filterString) {
|
||||
// As we are in the client view we hide disputes where we are the agent
|
||||
|
@ -70,4 +65,9 @@ public abstract class DisputeClientView extends DisputeView {
|
|||
|
||||
return super.getFilterResult(dispute, filterString);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean senderFlag() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@ import bisq.core.support.dispute.arbitration.ArbitrationSession;
|
|||
import bisq.core.support.dispute.mediation.mediator.MediatorManager;
|
||||
import bisq.core.support.dispute.refund.refundagent.RefundAgentManager;
|
||||
import bisq.core.trade.TradeManager;
|
||||
import bisq.core.user.Preferences;
|
||||
import bisq.core.util.FormattingUtils;
|
||||
import bisq.core.util.coin.CoinFormatter;
|
||||
|
||||
|
@ -50,6 +51,7 @@ public class ArbitrationClientView extends DisputeClientView {
|
|||
KeyRing keyRing,
|
||||
TradeManager tradeManager,
|
||||
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter formatter,
|
||||
Preferences preferences,
|
||||
DisputeSummaryWindow disputeSummaryWindow,
|
||||
PrivateNotificationManager privateNotificationManager,
|
||||
ContractWindow contractWindow,
|
||||
|
@ -59,7 +61,7 @@ public class ArbitrationClientView extends DisputeClientView {
|
|||
RefundAgentManager refundAgentManager,
|
||||
DaoFacade daoFacade,
|
||||
@Named(Config.USE_DEV_PRIVILEGE_KEYS) boolean useDevPrivilegeKeys) {
|
||||
super(arbitrationManager, keyRing, tradeManager, formatter, disputeSummaryWindow,
|
||||
super(arbitrationManager, keyRing, tradeManager, formatter, preferences, disputeSummaryWindow,
|
||||
privateNotificationManager, contractWindow, tradeDetailsWindow, accountAgeWitnessService,
|
||||
mediatorManager, refundAgentManager, daoFacade, useDevPrivilegeKeys);
|
||||
}
|
||||
|
|
|
@ -37,6 +37,7 @@ import bisq.core.support.dispute.mediation.mediator.MediatorManager;
|
|||
import bisq.core.support.dispute.refund.refundagent.RefundAgentManager;
|
||||
import bisq.core.trade.Contract;
|
||||
import bisq.core.trade.TradeManager;
|
||||
import bisq.core.user.Preferences;
|
||||
import bisq.core.util.FormattingUtils;
|
||||
import bisq.core.util.coin.CoinFormatter;
|
||||
|
||||
|
@ -48,8 +49,6 @@ import bisq.common.crypto.KeyRing;
|
|||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
import javafx.scene.control.TableColumn;
|
||||
|
||||
@FxmlView
|
||||
public class MediationClientView extends DisputeClientView {
|
||||
@Inject
|
||||
|
@ -57,6 +56,7 @@ public class MediationClientView extends DisputeClientView {
|
|||
KeyRing keyRing,
|
||||
TradeManager tradeManager,
|
||||
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter formatter,
|
||||
Preferences preferences,
|
||||
DisputeSummaryWindow disputeSummaryWindow,
|
||||
PrivateNotificationManager privateNotificationManager,
|
||||
ContractWindow contractWindow,
|
||||
|
@ -66,7 +66,7 @@ public class MediationClientView extends DisputeClientView {
|
|||
RefundAgentManager refundAgentManager,
|
||||
DaoFacade daoFacade,
|
||||
@Named(Config.USE_DEV_PRIVILEGE_KEYS) boolean useDevPrivilegeKeys) {
|
||||
super(mediationManager, keyRing, tradeManager, formatter, disputeSummaryWindow,
|
||||
super(mediationManager, keyRing, tradeManager, formatter, preferences, disputeSummaryWindow,
|
||||
privateNotificationManager, contractWindow, tradeDetailsWindow, accountAgeWitnessService,
|
||||
mediatorManager, refundAgentManager, daoFacade, useDevPrivilegeKeys);
|
||||
}
|
||||
|
@ -76,6 +76,8 @@ public class MediationClientView extends DisputeClientView {
|
|||
super.initialize();
|
||||
reOpenButton.setVisible(true);
|
||||
reOpenButton.setManaged(true);
|
||||
closeButton.setVisible(true);
|
||||
closeButton.setManaged(true);
|
||||
setupReOpenDisputeListener();
|
||||
}
|
||||
|
||||
|
@ -105,7 +107,7 @@ public class MediationClientView extends DisputeClientView {
|
|||
protected void reOpenDisputeFromButton() {
|
||||
new Popup().attention(Res.get("support.reOpenByTrader.prompt"))
|
||||
.actionButtonText(Res.get("shared.yes"))
|
||||
.onAction(this::reOpenDispute)
|
||||
.onAction(() -> reOpenDispute())
|
||||
.show();
|
||||
}
|
||||
|
||||
|
@ -116,7 +118,6 @@ public class MediationClientView extends DisputeClientView {
|
|||
|
||||
@Override
|
||||
protected void maybeAddAgentColumn() {
|
||||
TableColumn<Dispute, Dispute> agentColumn = getAgentColumn();
|
||||
tableView.getColumns().add(agentColumn);
|
||||
tableView.getColumns().add(getAgentColumn());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ import bisq.core.support.dispute.refund.RefundSession;
|
|||
import bisq.core.support.dispute.refund.refundagent.RefundAgentManager;
|
||||
import bisq.core.trade.Contract;
|
||||
import bisq.core.trade.TradeManager;
|
||||
import bisq.core.user.Preferences;
|
||||
import bisq.core.util.FormattingUtils;
|
||||
import bisq.core.util.coin.CoinFormatter;
|
||||
|
||||
|
@ -46,8 +47,6 @@ import bisq.common.crypto.KeyRing;
|
|||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
import javafx.scene.control.TableColumn;
|
||||
|
||||
@FxmlView
|
||||
public class RefundClientView extends DisputeClientView {
|
||||
@Inject
|
||||
|
@ -55,6 +54,7 @@ public class RefundClientView extends DisputeClientView {
|
|||
KeyRing keyRing,
|
||||
TradeManager tradeManager,
|
||||
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter formatter,
|
||||
Preferences preferences,
|
||||
DisputeSummaryWindow disputeSummaryWindow,
|
||||
PrivateNotificationManager privateNotificationManager,
|
||||
ContractWindow contractWindow,
|
||||
|
@ -64,7 +64,7 @@ public class RefundClientView extends DisputeClientView {
|
|||
RefundAgentManager refundAgentManager,
|
||||
DaoFacade daoFacade,
|
||||
@Named(Config.USE_DEV_PRIVILEGE_KEYS) boolean useDevPrivilegeKeys) {
|
||||
super(refundManager, keyRing, tradeManager, formatter, disputeSummaryWindow,
|
||||
super(refundManager, keyRing, tradeManager, formatter, preferences, disputeSummaryWindow,
|
||||
privateNotificationManager, contractWindow, tradeDetailsWindow, accountAgeWitnessService,
|
||||
mediatorManager, refundAgentManager, daoFacade, useDevPrivilegeKeys);
|
||||
}
|
||||
|
@ -86,7 +86,6 @@ public class RefundClientView extends DisputeClientView {
|
|||
|
||||
@Override
|
||||
protected void maybeAddAgentColumn() {
|
||||
TableColumn<Dispute, Dispute> agentColumn = getAgentColumn();
|
||||
tableView.getColumns().add(agentColumn);
|
||||
tableView.getColumns().add(getAgentColumn());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -392,6 +392,21 @@ public class FormBuilder {
|
|||
return new Tuple2<>(label1, label2);
|
||||
}
|
||||
|
||||
public static Tuple2<Label, TextFieldWithCopyIcon> addConfirmationLabelLabelWithCopyIcon(GridPane gridPane,
|
||||
int rowIndex,
|
||||
String title1,
|
||||
String title2) {
|
||||
Label label1 = addLabel(gridPane, rowIndex, title1);
|
||||
label1.getStyleClass().add("confirmation-label");
|
||||
TextFieldWithCopyIcon label2 = new TextFieldWithCopyIcon("confirmation-value");
|
||||
label2.setText(title2);
|
||||
GridPane.setRowIndex(label2, rowIndex);
|
||||
gridPane.getChildren().add(label2);
|
||||
GridPane.setColumnIndex(label2, 1);
|
||||
GridPane.setHalignment(label1, HPos.LEFT);
|
||||
return new Tuple2<>(label1, label2);
|
||||
}
|
||||
|
||||
public static Tuple2<Label, TextArea> addConfirmationLabelTextArea(GridPane gridPane,
|
||||
int rowIndex,
|
||||
String title1,
|
||||
|
|
|
@ -804,6 +804,13 @@ message SignedWitness {
|
|||
|
||||
|
||||
message Dispute {
|
||||
enum State {
|
||||
NEEDS_UPGRADE = 0;
|
||||
NEW = 1;
|
||||
OPEN = 2;
|
||||
REOPENED = 3;
|
||||
CLOSED = 4;
|
||||
}
|
||||
string trade_id = 1;
|
||||
string id = 2;
|
||||
int32 trader_id = 3;
|
||||
|
@ -831,6 +838,9 @@ message Dispute {
|
|||
string mediators_dispute_result = 25;
|
||||
string delayed_payout_tx_id = 26;
|
||||
string donation_address_of_delayed_payout_tx = 27;
|
||||
State state = 28;
|
||||
int64 trade_period_end = 29;
|
||||
map<string, string> extra_data = 30;
|
||||
}
|
||||
|
||||
message Attachment {
|
||||
|
|
Loading…
Add table
Reference in a new issue