From 4bef95172c5c34f3f1541319ae73f6229862a563 Mon Sep 17 00:00:00 2001
From: napoly <napolytan@protonmail.com>
Date: Fri, 17 Mar 2023 18:43:33 +0100
Subject: [PATCH] Update Signed Offers view in legacy UI

---
 .github/workflows/build.yml                   |   8 +-
 .../resources/i18n/displayStrings.properties  |   1 +
 .../signedoffer/SignedOfferListItem.java      |  48 ++
 .../signedoffer}/SignedOfferView.fxml         |   6 +-
 .../offer/signedoffer/SignedOfferView.java    | 358 ++++++++++++++
 .../signedoffer/SignedOffersDataModel.java    |  70 +++
 .../signedoffer/SignedOffersViewModel.java    |  39 ++
 .../desktop/main/support/SupportView.java     |   2 +-
 .../dispute/agent/SignedOfferView.java        | 438 ------------------
 9 files changed, 525 insertions(+), 445 deletions(-)
 create mode 100644 desktop/src/main/java/haveno/desktop/main/offer/signedoffer/SignedOfferListItem.java
 rename desktop/src/main/java/haveno/desktop/main/{support/dispute/agent => offer/signedoffer}/SignedOfferView.fxml (87%)
 create mode 100644 desktop/src/main/java/haveno/desktop/main/offer/signedoffer/SignedOfferView.java
 create mode 100644 desktop/src/main/java/haveno/desktop/main/offer/signedoffer/SignedOffersDataModel.java
 create mode 100644 desktop/src/main/java/haveno/desktop/main/offer/signedoffer/SignedOffersViewModel.java
 delete mode 100644 desktop/src/main/java/haveno/desktop/main/support/dispute/agent/SignedOfferView.java

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 68d4af83da..bf12e778f8 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -14,11 +14,11 @@ jobs:
       fail-fast: false
     runs-on: ${{ matrix.os }}
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v3
         with:
           lfs: true
       - name: Set up JDK 11
-        uses: actions/setup-java@v2
+        uses: actions/setup-java@v3
         with:
           java-version: '11'
           distribution: 'adopt'
@@ -26,11 +26,11 @@ jobs:
       - name: Build with Gradle
         run: ./gradlew build --stacktrace --scan
       - name: cache nodes dependencies
-        uses: actions/upload-artifact@v2
+        uses: actions/upload-artifact@v3
         with:
           name: cached-localnet
           path: .localnet
-      - uses: actions/upload-artifact@v2
+      - uses: actions/upload-artifact@v3
         if: failure()
         with:
           name: gradlew-report
diff --git a/core/src/main/resources/i18n/displayStrings.properties b/core/src/main/resources/i18n/displayStrings.properties
index 7f3830dd91..e970949a5f 100644
--- a/core/src/main/resources/i18n/displayStrings.properties
+++ b/core/src/main/resources/i18n/displayStrings.properties
@@ -90,6 +90,7 @@ shared.usage=Usage
 shared.state=Status
 shared.tradeId=Trade ID
 shared.offerId=Offer ID
+shared.traderId=Trader ID
 shared.bankName=Bank name
 shared.acceptedBanks=Accepted banks
 shared.amountMinMax=Amount (min - max)
diff --git a/desktop/src/main/java/haveno/desktop/main/offer/signedoffer/SignedOfferListItem.java b/desktop/src/main/java/haveno/desktop/main/offer/signedoffer/SignedOfferListItem.java
new file mode 100644
index 0000000000..7a1e267794
--- /dev/null
+++ b/desktop/src/main/java/haveno/desktop/main/offer/signedoffer/SignedOfferListItem.java
@@ -0,0 +1,48 @@
+/*
+ * This file is part of Haveno.
+ *
+ * Haveno 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.
+ *
+ * Haveno 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 Haveno. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package haveno.desktop.main.offer.signedoffer;
+
+import org.apache.commons.lang3.StringUtils;
+
+import lombok.Getter;
+
+import haveno.core.offer.SignedOffer;
+import haveno.desktop.util.filtering.FilterableListItem;
+
+class SignedOfferListItem implements FilterableListItem {
+    @Getter
+    private final SignedOffer signedOffer;
+
+    SignedOfferListItem(SignedOffer signedOffer) {
+        this.signedOffer = signedOffer;
+    }
+
+    @Override
+    public boolean match(String filterString) {
+        if (filterString.isEmpty()) {
+            return true;
+        }
+        if (StringUtils.containsIgnoreCase(String.valueOf(signedOffer.getTraderId()), filterString)) {
+            return true;
+        }
+        if (StringUtils.containsIgnoreCase(String.valueOf(signedOffer.getOfferId()), filterString)) {
+            return true;
+        }
+        return StringUtils.containsIgnoreCase(String.valueOf(signedOffer.getReserveTxKeyImages()), filterString);
+    }
+}
diff --git a/desktop/src/main/java/haveno/desktop/main/support/dispute/agent/SignedOfferView.fxml b/desktop/src/main/java/haveno/desktop/main/offer/signedoffer/SignedOfferView.fxml
similarity index 87%
rename from desktop/src/main/java/haveno/desktop/main/support/dispute/agent/SignedOfferView.fxml
rename to desktop/src/main/java/haveno/desktop/main/offer/signedoffer/SignedOfferView.fxml
index 38f839c51e..a12a97990b 100644
--- a/desktop/src/main/java/haveno/desktop/main/support/dispute/agent/SignedOfferView.fxml
+++ b/desktop/src/main/java/haveno/desktop/main/offer/signedoffer/SignedOfferView.fxml
@@ -17,17 +17,19 @@
   ~ along with Haveno. If not, see <http://www.gnu.org/licenses/>.
   -->
 
-<?import javafx.geometry.Insets?>
 <?import javafx.scene.control.Label?>
 <?import javafx.scene.control.TableView?>
 <?import javafx.scene.layout.HBox?>
 <?import javafx.scene.layout.Region?>
 <?import javafx.scene.layout.VBox?>
-<VBox fx:id="root" fx:controller="haveno.desktop.main.support.dispute.agent.SignedOfferView"
+<?import javafx.geometry.Insets?>
+<?import haveno.desktop.components.list.FilterBox?>
+<VBox fx:id="root" fx:controller="haveno.desktop.main.offer.signedoffer.SignedOfferView"
       spacing="10" xmlns:fx="http://javafx.com/fxml">
     <padding>
         <Insets bottom="15.0" left="15.0" right="15.0" top="15.0"/>
     </padding>
+    <FilterBox fx:id="filterBox" />
     <TableView fx:id="tableView" VBox.vgrow="ALWAYS" />
     <HBox spacing="10">
         <Label fx:id="numItems"/>
diff --git a/desktop/src/main/java/haveno/desktop/main/offer/signedoffer/SignedOfferView.java b/desktop/src/main/java/haveno/desktop/main/offer/signedoffer/SignedOfferView.java
new file mode 100644
index 0000000000..dfbde8df65
--- /dev/null
+++ b/desktop/src/main/java/haveno/desktop/main/offer/signedoffer/SignedOfferView.java
@@ -0,0 +1,358 @@
+/*
+ * This file is part of Haveno.
+ *
+ * Haveno 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.
+ *
+ * Haveno 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 Haveno. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package haveno.desktop.main.offer.signedoffer;
+
+import javax.inject.Inject;
+
+import javafx.fxml.FXML;
+
+import javafx.scene.control.ContextMenu;
+import javafx.scene.control.Label;
+import javafx.scene.control.MenuItem;
+import javafx.scene.control.TableCell;
+import javafx.scene.control.TableColumn;
+import javafx.scene.control.TableRow;
+import javafx.scene.control.TableView;
+import javafx.scene.control.Tooltip;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.Priority;
+import javafx.scene.layout.Region;
+import javafx.scene.layout.VBox;
+
+import javafx.geometry.Insets;
+
+import javafx.beans.property.ReadOnlyObjectWrapper;
+
+import javafx.collections.transformation.FilteredList;
+import javafx.collections.transformation.SortedList;
+
+import javafx.util.Callback;
+import javafx.util.Duration;
+
+import java.util.Comparator;
+import java.util.Date;
+
+
+
+import haveno.common.util.Utilities;
+import haveno.core.locale.Res;
+import haveno.core.offer.SignedOffer;
+import haveno.core.trade.HavenoUtils;
+import haveno.core.xmr.wallet.XmrWalletService;
+import haveno.desktop.common.view.ActivatableViewAndModel;
+import haveno.desktop.common.view.FxmlView;
+import haveno.desktop.components.AutoTooltipLabel;
+import haveno.desktop.components.AutoTooltipTableColumn;
+import haveno.desktop.components.HyperlinkWithIcon;
+import haveno.desktop.components.InputTextField;
+import haveno.desktop.components.list.FilterBox;
+import haveno.desktop.main.offer.OfferViewUtil;
+import haveno.desktop.main.overlays.popups.Popup;
+import haveno.desktop.util.DisplayUtils;
+import haveno.desktop.util.GUIUtil;
+
+@FxmlView
+public class SignedOfferView extends ActivatableViewAndModel<VBox, SignedOffersViewModel> {
+
+    @FXML
+    FilterBox filterBox;
+    @FXML
+    protected TableView<SignedOfferListItem> tableView;
+    @FXML
+    TableColumn<SignedOfferListItem, SignedOfferListItem> dateColumn;
+    @FXML
+    TableColumn<SignedOfferListItem, SignedOfferListItem> traderIdColumn;
+    @FXML
+    TableColumn<SignedOfferListItem, SignedOfferListItem> offerIdColumn;
+    @FXML
+    TableColumn<SignedOfferListItem, SignedOfferListItem> reserveTxKeyImages;
+    @FXML
+    TableColumn<SignedOfferListItem, SignedOfferListItem> makerPenaltyFeeColumn;
+    @FXML
+    InputTextField filterTextField;
+    @FXML
+    Label numItems;
+    @FXML
+    Region footerSpacer;
+
+    private SignedOfferListItem selectedSignedOffer;
+
+    private final XmrWalletService xmrWalletService;
+
+    private ContextMenu contextMenu;
+
+    @Inject
+    public SignedOfferView(SignedOffersViewModel model, XmrWalletService xmrWalletService) {
+        super(model);
+        this.xmrWalletService = xmrWalletService;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////////////////////
+    // Life cycle
+    ///////////////////////////////////////////////////////////////////////////////////////////
+
+    @Override
+    public void initialize() {
+        Label label = new AutoTooltipLabel(Res.get("support.filter"));
+        HBox.setMargin(label, new Insets(5, 0, 0, 0));
+        HBox.setHgrow(label, Priority.NEVER);
+
+        filterTextField = new InputTextField();
+        Tooltip tooltip = new Tooltip();
+        tooltip.setShowDelay(Duration.millis(100));
+        tooltip.setShowDuration(Duration.seconds(10));
+        filterTextField.setTooltip(tooltip);
+        HBox.setHgrow(filterTextField, Priority.NEVER);
+
+        filterTextField.setText("open");
+
+        setupTable();
+    }
+    @Override
+    protected void activate() {
+        FilteredList<SignedOfferListItem> filteredList = new FilteredList<>(model.getList());
+        SortedList<SignedOfferListItem> sortedList = new SortedList<>(filteredList);
+        sortedList.comparatorProperty().bind(tableView.comparatorProperty());
+        tableView.setItems(sortedList);
+        filterBox.initialize(filteredList, tableView);
+        filterBox.activate();
+
+        contextMenu = new ContextMenu();
+        MenuItem makerPenalization = new MenuItem(
+                Res.get("support.contextmenu.penalize.msg", Res.get("shared.maker").toLowerCase())
+        );
+        MenuItem copyToClipboard = new MenuItem(Res.get("shared.copyToClipboard"));
+        contextMenu.getItems().addAll(makerPenalization, copyToClipboard);
+
+        tableView.setRowFactory(tv -> {
+            TableRow<SignedOfferListItem> row = new TableRow<>();
+            row.setOnContextMenuRequested(event -> contextMenu.show(row, event.getScreenX(), event.getScreenY()));
+            return row;
+        });
+
+        copyToClipboard.setOnAction(event -> {
+            selectedSignedOffer = tableView.getSelectionModel().getSelectedItem();
+            Utilities.copyToClipboard(selectedSignedOffer.getSignedOffer().toJson());
+        });
+
+        makerPenalization.setOnAction(event -> {
+            selectedSignedOffer = tableView.getSelectionModel().getSelectedItem();
+            if(selectedSignedOffer != null) {
+                SignedOffer signedOffer = selectedSignedOffer.getSignedOffer();
+                new Popup().warning(Res.get("support.prompt.signedOffer.penalty.msg",
+                        signedOffer.getOfferId(),
+                        HavenoUtils.formatXmr(signedOffer.getPenaltyAmount(), true),
+                        HavenoUtils.formatXmr(signedOffer.getReserveTxMinerFee(), true),
+                        signedOffer.getReserveTxHash(),
+                        signedOffer.getReserveTxKeyImages())
+                ).onAction(() -> OfferViewUtil.submitTransactionHex(xmrWalletService, tableView,
+                        signedOffer.getReserveTxHex())).show();
+            } else {
+                new Popup().error(Res.get("support.prompt.signedOffer.error.msg")).show();
+            }
+        });
+
+        GUIUtil.requestFocus(tableView);
+    }
+
+    @Override
+    protected void deactivate() {
+        filterBox.deactivate();
+    }
+
+
+    ///////////////////////////////////////////////////////////////////////////////////////////
+    // SignedOfferView
+    ///////////////////////////////////////////////////////////////////////////////////////////
+
+    protected void setupTable() {
+        tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
+        Label placeholder = new AutoTooltipLabel(Res.get("support.noTickets"));
+        placeholder.setWrapText(true);
+        tableView.setPlaceholder(placeholder);
+        tableView.getSelectionModel().clearSelection();
+
+        dateColumn = getDateColumn();
+        tableView.getColumns().add(dateColumn);
+
+        traderIdColumn = getTraderIdColumn();
+        tableView.getColumns().add(traderIdColumn);
+
+        offerIdColumn = getOfferIdColumn();
+        tableView.getColumns().add(offerIdColumn);
+
+        makerPenaltyFeeColumn = getMakerPenaltyFeeColumn();
+        tableView.getColumns().add(makerPenaltyFeeColumn);
+
+        reserveTxKeyImages = getReserveTxKeyImagesColumn();
+        tableView.getColumns().add(reserveTxKeyImages);
+
+        traderIdColumn.setComparator(Comparator.comparing(o -> o.getSignedOffer().getTraderId()));
+        offerIdColumn.setComparator(Comparator.comparing(o -> o.getSignedOffer().getOfferId()));
+        dateColumn.setComparator(Comparator.comparing(o -> o.getSignedOffer().getTimeStamp()));
+
+        dateColumn.setSortType(TableColumn.SortType.DESCENDING);
+        tableView.getSortOrder().add(dateColumn);
+    }
+
+    private TableColumn<SignedOfferListItem, SignedOfferListItem> getDateColumn() {
+        TableColumn<SignedOfferListItem, SignedOfferListItem> column = new AutoTooltipTableColumn<>(Res.get("shared.date")) {
+            {
+                setMinWidth(180);
+            }
+        };
+        column.setCellValueFactory((signedOffer) -> new ReadOnlyObjectWrapper<>(signedOffer.getValue()));
+        column.setCellFactory(
+                new Callback<>() {
+                    @Override
+                    public TableCell<SignedOfferListItem, SignedOfferListItem> call(TableColumn<SignedOfferListItem, SignedOfferListItem> column) {
+                        return new TableCell<>() {
+                            @Override
+                            public void updateItem(final SignedOfferListItem item, boolean empty) {
+                                super.updateItem(item, empty);
+                                if (item != null && !empty)
+                                    setText(DisplayUtils.formatDateTime(new Date(item.getSignedOffer().getTimeStamp())));
+                                else
+                                    setText("");
+                            }
+                        };
+                    }
+                });
+        return column;
+    }
+
+    private TableColumn<SignedOfferListItem, SignedOfferListItem> getTraderIdColumn() {
+        TableColumn<SignedOfferListItem, SignedOfferListItem> column = new AutoTooltipTableColumn<>(Res.get("shared.traderId")) {
+            {
+                setMinWidth(110);
+            }
+        };
+        column.setCellValueFactory(signedOffer -> new ReadOnlyObjectWrapper<>(signedOffer.getValue()));
+        column.setCellFactory(
+                new Callback<>() {
+                    @Override
+                    public TableCell<SignedOfferListItem, SignedOfferListItem> call(TableColumn<SignedOfferListItem, SignedOfferListItem> column) {
+                        return new TableCell<>() {
+                            private HyperlinkWithIcon field;
+
+                            @Override
+                            public void updateItem(final SignedOfferListItem item, boolean empty) {
+                                super.updateItem(item, empty);
+
+                                if (item != null && !empty) {
+                                    setText(String.valueOf(item.getSignedOffer().getTraderId()));
+                                    setGraphic(field);
+                                } else {
+                                    setGraphic(null);
+                                    setText("");
+                                    if (field != null)
+                                        field.setOnAction(null);
+                                }
+                            }
+                        };
+                    }
+                });
+        return column;
+    }
+
+    private TableColumn<SignedOfferListItem, SignedOfferListItem> getOfferIdColumn() {
+        TableColumn<SignedOfferListItem, SignedOfferListItem> column = new AutoTooltipTableColumn<>(Res.get("shared.offerId")) {
+            {
+                setMinWidth(110);
+            }
+        };
+        column.setCellValueFactory((signedOffer) -> new ReadOnlyObjectWrapper<>(signedOffer.getValue()));
+        column.setCellFactory(
+                new Callback<>() {
+                    @Override
+                    public TableCell<SignedOfferListItem, SignedOfferListItem> call(TableColumn<SignedOfferListItem, SignedOfferListItem> column) {
+                        return new TableCell<>() {
+                            private HyperlinkWithIcon field;
+
+                            @Override
+                            public void updateItem(final SignedOfferListItem item, boolean empty) {
+                                super.updateItem(item, empty);
+
+                                if (item != null && !empty) {
+                                    setText(String.valueOf(item.getSignedOffer().getOfferId()));
+                                    setGraphic(field);
+                                } else {
+                                    setGraphic(null);
+                                    setText("");
+                                    if (field != null)
+                                        field.setOnAction(null);
+                                }
+                            }
+                        };
+                    }
+                });
+        return column;
+    }
+
+    private TableColumn<SignedOfferListItem, SignedOfferListItem> getMakerPenaltyFeeColumn() {
+        TableColumn<SignedOfferListItem, SignedOfferListItem> column = new AutoTooltipTableColumn<>(Res.get("support.maker.penalty.fee")) {
+            {
+                setMinWidth(160);
+            }
+        };
+        column.setCellValueFactory((signedOffer) -> new ReadOnlyObjectWrapper<>(signedOffer.getValue()));
+        column.setCellFactory(
+                new Callback<>() {
+                    @Override
+                    public TableCell<SignedOfferListItem, SignedOfferListItem> call(TableColumn<SignedOfferListItem, SignedOfferListItem> column) {
+                        return new TableCell<>() {
+                            @Override
+                            public void updateItem(final SignedOfferListItem item, boolean empty) {
+                                super.updateItem(item, empty);
+                                if (item != null && !empty)
+                                    setText(HavenoUtils.formatXmr(item.getSignedOffer().getPenaltyAmount(), true));
+                                else
+                                    setText("");
+                            }
+                        };
+                    }
+                });
+        return column;
+    }
+
+    private TableColumn<SignedOfferListItem, SignedOfferListItem> getReserveTxKeyImagesColumn() {
+        TableColumn<SignedOfferListItem, SignedOfferListItem> column = new AutoTooltipTableColumn<>(Res.get("support.txKeyImages")) {
+            {
+                setMinWidth(160);
+            }
+        };
+        column.setCellValueFactory((signedOffer) -> new ReadOnlyObjectWrapper<>(signedOffer.getValue()));
+        column.setCellFactory(
+                new Callback<>() {
+                    @Override
+                    public TableCell<SignedOfferListItem, SignedOfferListItem> call(TableColumn<SignedOfferListItem, SignedOfferListItem> column) {
+                        return new TableCell<>() {
+                            @Override
+                            public void updateItem(final SignedOfferListItem item, boolean empty) {
+                                super.updateItem(item, empty);
+                                if (item != null && !empty)
+                                    setText(item.getSignedOffer().getReserveTxKeyImages().toString());
+                                else
+                                    setText("");
+                            }
+                        };
+                    }
+                });
+        return column;
+    }
+}
diff --git a/desktop/src/main/java/haveno/desktop/main/offer/signedoffer/SignedOffersDataModel.java b/desktop/src/main/java/haveno/desktop/main/offer/signedoffer/SignedOffersDataModel.java
new file mode 100644
index 0000000000..bdd66388f1
--- /dev/null
+++ b/desktop/src/main/java/haveno/desktop/main/offer/signedoffer/SignedOffersDataModel.java
@@ -0,0 +1,70 @@
+/*
+ * This file is part of Haveno.
+ *
+ * Haveno 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.
+ *
+ * Haveno 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 Haveno. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package haveno.desktop.main.offer.signedoffer;
+
+import com.google.inject.Inject;
+
+import javafx.collections.FXCollections;
+import javafx.collections.ListChangeListener;
+import javafx.collections.ObservableList;
+
+import java.util.stream.Collectors;
+
+
+
+import haveno.core.offer.OpenOfferManager;
+import haveno.core.offer.SignedOffer;
+import haveno.desktop.common.model.ActivatableDataModel;
+import java.sql.Date;
+
+class SignedOffersDataModel extends ActivatableDataModel {
+    private final OpenOfferManager openOfferManager;
+    private final ObservableList<SignedOfferListItem> list = FXCollections.observableArrayList();
+    private final ListChangeListener<SignedOffer> tradesListChangeListener;
+
+    @Inject
+    public SignedOffersDataModel(OpenOfferManager openOfferManager) {
+        this.openOfferManager = openOfferManager;
+
+        tradesListChangeListener = change -> applyList();
+    }
+
+    @Override
+    protected void activate() {
+        openOfferManager.getObservableSignedOffersList().addListener(tradesListChangeListener);
+        applyList();
+    }
+
+    @Override
+    protected void deactivate() {
+        openOfferManager.getObservableSignedOffersList().removeListener(tradesListChangeListener);
+    }
+
+    public ObservableList<SignedOfferListItem> getList() {
+        return list;
+    }
+
+    private void applyList() {
+        list.clear();
+
+        list.addAll(openOfferManager.getObservableSignedOffersList().stream().map(SignedOfferListItem::new).collect(Collectors.toList()));
+
+        // we sort by date, the earliest first
+        list.sort((o1, o2) -> new Date(o2.getSignedOffer().getTimeStamp()).compareTo(new Date(o1.getSignedOffer().getTimeStamp())));
+    }
+}
diff --git a/desktop/src/main/java/haveno/desktop/main/offer/signedoffer/SignedOffersViewModel.java b/desktop/src/main/java/haveno/desktop/main/offer/signedoffer/SignedOffersViewModel.java
new file mode 100644
index 0000000000..511f6ca4bb
--- /dev/null
+++ b/desktop/src/main/java/haveno/desktop/main/offer/signedoffer/SignedOffersViewModel.java
@@ -0,0 +1,39 @@
+/*
+ * This file is part of Haveno.
+ *
+ * Haveno 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.
+ *
+ * Haveno 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 Haveno. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package haveno.desktop.main.offer.signedoffer;
+
+import com.google.inject.Inject;
+
+import javafx.collections.ObservableList;
+
+
+
+import haveno.desktop.common.model.ActivatableWithDataModel;
+import haveno.desktop.common.model.ViewModel;
+
+class SignedOffersViewModel extends ActivatableWithDataModel<SignedOffersDataModel> implements ViewModel {
+
+    @Inject
+    public SignedOffersViewModel(SignedOffersDataModel model) {
+        super(model);
+    }
+
+    public ObservableList<SignedOfferListItem> getList() {
+        return dataModel.getList();
+    }
+}
diff --git a/desktop/src/main/java/haveno/desktop/main/support/SupportView.java b/desktop/src/main/java/haveno/desktop/main/support/SupportView.java
index 8fa04ba674..3dd7864de0 100644
--- a/desktop/src/main/java/haveno/desktop/main/support/SupportView.java
+++ b/desktop/src/main/java/haveno/desktop/main/support/SupportView.java
@@ -38,7 +38,7 @@ import haveno.desktop.common.view.View;
 import haveno.desktop.common.view.ViewLoader;
 import haveno.desktop.main.MainView;
 import haveno.desktop.main.overlays.popups.Popup;
-import haveno.desktop.main.support.dispute.agent.SignedOfferView;
+import haveno.desktop.main.offer.signedoffer.SignedOfferView;
 import haveno.desktop.main.support.dispute.agent.arbitration.ArbitratorView;
 import haveno.desktop.main.support.dispute.agent.mediation.MediatorView;
 import haveno.desktop.main.support.dispute.agent.refund.RefundAgentView;
diff --git a/desktop/src/main/java/haveno/desktop/main/support/dispute/agent/SignedOfferView.java b/desktop/src/main/java/haveno/desktop/main/support/dispute/agent/SignedOfferView.java
deleted file mode 100644
index 4e5ad2be5d..0000000000
--- a/desktop/src/main/java/haveno/desktop/main/support/dispute/agent/SignedOfferView.java
+++ /dev/null
@@ -1,438 +0,0 @@
-/*
- * This file is part of Haveno.
- *
- * Haveno 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.
- *
- * Haveno 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 Haveno. If not, see <http://www.gnu.org/licenses/>.
- */
-
-package haveno.desktop.main.support.dispute.agent;
-
-import haveno.common.UserThread;
-import haveno.common.util.Utilities;
-import haveno.core.locale.Res;
-import haveno.core.offer.OpenOfferManager;
-import haveno.core.offer.SignedOffer;
-import haveno.core.trade.HavenoUtils;
-import haveno.core.xmr.wallet.XmrWalletService;
-import haveno.desktop.common.view.ActivatableView;
-import haveno.desktop.common.view.FxmlView;
-import haveno.desktop.components.AutoTooltipLabel;
-import haveno.desktop.components.AutoTooltipTableColumn;
-import haveno.desktop.components.HyperlinkWithIcon;
-import haveno.desktop.components.InputTextField;
-import haveno.desktop.main.offer.OfferViewUtil;
-import haveno.desktop.main.overlays.popups.Popup;
-import haveno.desktop.util.DisplayUtils;
-import haveno.desktop.util.GUIUtil;
-import javafx.beans.property.ReadOnlyObjectWrapper;
-import javafx.collections.ListChangeListener;
-import javafx.collections.transformation.SortedList;
-import javafx.fxml.FXML;
-import javafx.geometry.Insets;
-import javafx.scene.control.ContextMenu;
-import javafx.scene.control.Label;
-import javafx.scene.control.MenuItem;
-import javafx.scene.control.TableCell;
-import javafx.scene.control.TableColumn;
-import javafx.scene.control.TableRow;
-import javafx.scene.control.TableView;
-import javafx.scene.control.Tooltip;
-import javafx.scene.layout.HBox;
-import javafx.scene.layout.Priority;
-import javafx.scene.layout.Region;
-import javafx.scene.layout.VBox;
-import javafx.util.Callback;
-import javafx.util.Duration;
-
-import javax.inject.Inject;
-import java.util.Comparator;
-import java.util.Date;
-
-@FxmlView
-public class SignedOfferView extends ActivatableView<VBox, Void> {
-
-    private final OpenOfferManager openOfferManager;
-
-    @FXML
-    protected TableView<SignedOffer> tableView;
-    @FXML
-    TableColumn<SignedOffer, SignedOffer> dateColumn;
-    @FXML
-    TableColumn<SignedOffer, SignedOffer> offerIdColumn;
-    @FXML
-    TableColumn<SignedOffer, SignedOffer> reserveTxHashColumn;
-    @FXML
-    TableColumn<SignedOffer, SignedOffer> reserveTxHexColumn;
-    @FXML
-    TableColumn<SignedOffer, SignedOffer> reserveTxKeyImages;
-    @FXML
-    TableColumn<SignedOffer, SignedOffer> arbitratorSignatureColumn;
-    @FXML
-    TableColumn<SignedOffer, SignedOffer> reserveTxMinerFeeColumn;
-    @FXML
-    TableColumn<SignedOffer, SignedOffer> makerPenaltyFeeColumn;
-    @FXML
-    InputTextField filterTextField;
-    @FXML
-    Label numItems;
-    @FXML
-    Region footerSpacer;
-
-    private SignedOffer selectedSignedOffer;
-
-    private XmrWalletService xmrWalletService;
-
-    private ContextMenu contextMenu;
-
-    private final ListChangeListener<SignedOffer> signedOfferListChangeListener;
-
-    @Inject
-    public SignedOfferView(OpenOfferManager openOfferManager, XmrWalletService xmrWalletService) {
-        this.openOfferManager = openOfferManager;
-        this.xmrWalletService = xmrWalletService;
-
-        signedOfferListChangeListener = change -> applyList();
-    }
-
-    private void applyList() {
-        UserThread.execute(() -> {
-            SortedList<SignedOffer> sortedList = new SortedList<>(openOfferManager.getObservableSignedOffersList());
-            sortedList.comparatorProperty().bind(tableView.comparatorProperty());
-            tableView.setItems(sortedList);
-            numItems.setText(Res.get("shared.numItemsLabel", sortedList.size()));
-        });
-    }
-
-    ///////////////////////////////////////////////////////////////////////////////////////////
-    // Life cycle
-    ///////////////////////////////////////////////////////////////////////////////////////////
-
-    @Override
-    public void initialize() {
-        Label label = new AutoTooltipLabel(Res.get("support.filter"));
-        HBox.setMargin(label, new Insets(5, 0, 0, 0));
-        HBox.setHgrow(label, Priority.NEVER);
-
-        filterTextField = new InputTextField();
-        Tooltip tooltip = new Tooltip();
-        tooltip.setShowDelay(Duration.millis(100));
-        tooltip.setShowDuration(Duration.seconds(10));
-        filterTextField.setTooltip(tooltip);
-        HBox.setHgrow(filterTextField, Priority.NEVER);
-
-        filterTextField.setText("open");
-
-        setupTable();
-    }
-    @Override
-    protected void activate() {
-        super.activate();
-
-        applyList();
-        openOfferManager.getObservableSignedOffersList().addListener(signedOfferListChangeListener);
-        contextMenu = new ContextMenu();
-        MenuItem item1 = new MenuItem(Res.get("support.contextmenu.penalize.msg",
-                Res.get("shared.maker")));
-        contextMenu.getItems().addAll(item1);
-
-        tableView.setRowFactory(tv -> {
-            TableRow<SignedOffer> row = new TableRow<>();
-            row.setOnContextMenuRequested(event -> {
-                contextMenu.show(row, event.getScreenX(), event.getScreenY());
-            });
-            return row;
-        });
-
-        item1.setOnAction(event -> {
-            selectedSignedOffer = tableView.getSelectionModel().getSelectedItem();
-            if(selectedSignedOffer != null) {
-                new Popup().warning(Res.get("support.prompt.signedOffer.penalty.msg",
-                        selectedSignedOffer.getOfferId(),
-                        HavenoUtils.formatXmr(selectedSignedOffer.getPenaltyAmount(), true),
-                        HavenoUtils.formatXmr(selectedSignedOffer.getReserveTxMinerFee(), true),
-                        selectedSignedOffer.getReserveTxHash(),
-                        selectedSignedOffer.getReserveTxKeyImages())
-                ).onAction(() -> OfferViewUtil.submitTransactionHex(xmrWalletService, tableView,
-                        selectedSignedOffer.getReserveTxHex())).show();
-            } else {
-                new Popup().error(Res.get("support.prompt.signedOffer.error.msg")).show();
-            }
-        });
-
-        GUIUtil.requestFocus(tableView);
-    }
-
-    @Override
-    protected void deactivate() {
-        super.deactivate();
-    }
-
-
-    ///////////////////////////////////////////////////////////////////////////////////////////
-    // SignedOfferView
-    ///////////////////////////////////////////////////////////////////////////////////////////
-
-    protected void setupTable() {
-        tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
-        Label placeholder = new AutoTooltipLabel(Res.get("support.noTickets"));
-        placeholder.setWrapText(true);
-        tableView.setPlaceholder(placeholder);
-        tableView.getSelectionModel().clearSelection();
-
-        dateColumn = getDateColumn();
-        tableView.getColumns().add(dateColumn);
-
-        offerIdColumn = getOfferIdColumn();
-        tableView.getColumns().add(offerIdColumn);
-
-        reserveTxHashColumn = getReserveTxHashColumn();
-        tableView.getColumns().add(reserveTxHashColumn);
-
-        reserveTxHexColumn = getReserveTxHexColumn();
-        tableView.getColumns().add(reserveTxHexColumn);
-
-        reserveTxKeyImages = getReserveTxKeyImagesColumn();
-        tableView.getColumns().add(reserveTxKeyImages);
-
-        arbitratorSignatureColumn = getArbitratorSignatureColumn();
-        tableView.getColumns().add(arbitratorSignatureColumn);
-
-        makerPenaltyFeeColumn = getMakerPenaltyFeeColumn();
-        tableView.getColumns().add(makerPenaltyFeeColumn);
-
-        reserveTxMinerFeeColumn = getReserveTxMinerFeeColumn();
-        tableView.getColumns().add(reserveTxMinerFeeColumn);
-
-        offerIdColumn.setComparator(Comparator.comparing(SignedOffer::getOfferId));
-        dateColumn.setComparator(Comparator.comparing(SignedOffer::getTimeStamp));
-
-        dateColumn.setSortType(TableColumn.SortType.DESCENDING);
-        tableView.getSortOrder().add(dateColumn);
-    }
-
-    private TableColumn<SignedOffer, SignedOffer> getDateColumn() {
-        TableColumn<SignedOffer, SignedOffer> column = new AutoTooltipTableColumn<>(Res.get("shared.date")) {
-            {
-                setMinWidth(180);
-            }
-        };
-        column.setCellValueFactory((signedOffer) -> new ReadOnlyObjectWrapper<>(signedOffer.getValue()));
-        column.setCellFactory(
-                new Callback<>() {
-                    @Override
-                    public TableCell<SignedOffer, SignedOffer> call(TableColumn<SignedOffer, SignedOffer> column) {
-                        return new TableCell<>() {
-                            @Override
-                            public void updateItem(final SignedOffer item, boolean empty) {
-                                super.updateItem(item, empty);
-                                if (item != null && !empty)
-                                    setText(DisplayUtils.formatDateTime(new Date(item.getTimeStamp())));
-                                else
-                                    setText("");
-                            }
-                        };
-                    }
-                });
-        return column;
-    }
-
-    private TableColumn<SignedOffer, SignedOffer> getOfferIdColumn() {
-        TableColumn<SignedOffer, SignedOffer> column = new AutoTooltipTableColumn<>(Res.get("shared.offerId")) {
-            {
-                setMinWidth(110);
-            }
-        };
-        column.setCellValueFactory((signedOffer) -> new ReadOnlyObjectWrapper<>(signedOffer.getValue()));
-        column.setCellFactory(
-                new Callback<>() {
-                    @Override
-                    public TableCell<SignedOffer, SignedOffer> call(TableColumn<SignedOffer, SignedOffer> column) {
-                        return new TableCell<>() {
-                            private HyperlinkWithIcon field;
-
-                            @Override
-                            public void updateItem(final SignedOffer item, boolean empty) {
-                                super.updateItem(item, empty);
-
-                                if (item != null && !empty) {
-                                    setText(item.getOfferId());
-                                    setGraphic(field);
-                                } else {
-                                    setGraphic(null);
-                                    setText("");
-                                    if (field != null)
-                                        field.setOnAction(null);
-                                }
-                            }
-                        };
-                    }
-                });
-        return column;
-    }
-
-    private TableColumn<SignedOffer, SignedOffer> getReserveTxHashColumn() {
-        TableColumn<SignedOffer, SignedOffer> column = new AutoTooltipTableColumn<>(Res.get("support.txHash")) {
-            {
-                setMinWidth(160);
-            }
-        };
-        column.setCellValueFactory((signedOffer) -> new ReadOnlyObjectWrapper<>(signedOffer.getValue()));
-        column.setCellFactory(
-                new Callback<>() {
-                    @Override
-                    public TableCell<SignedOffer, SignedOffer> call(TableColumn<SignedOffer, SignedOffer> column) {
-                        return new TableCell<>() {
-                            @Override
-                            public void updateItem(final SignedOffer item, boolean empty) {
-                                super.updateItem(item, empty);
-                                if (item != null && !empty)
-                                    setText(item.getReserveTxHash());
-                                else
-                                    setText("");
-                            }
-                        };
-                    }
-                });
-        return column;
-    }
-
-    private TableColumn<SignedOffer, SignedOffer> getReserveTxHexColumn() {
-        TableColumn<SignedOffer, SignedOffer> column = new AutoTooltipTableColumn<>(Res.get("support.txHex")) {
-            {
-                setMinWidth(160);
-            }
-        };
-        column.setCellValueFactory((signedOffer) -> new ReadOnlyObjectWrapper<>(signedOffer.getValue()));
-        column.setCellFactory(
-                new Callback<>() {
-                    @Override
-                    public TableCell<SignedOffer, SignedOffer> call(TableColumn<SignedOffer, SignedOffer> column) {
-                        return new TableCell<>() {
-                            @Override
-                            public void updateItem(final SignedOffer item, boolean empty) {
-                                super.updateItem(item, empty);
-                                if (item != null && !empty)
-                                    setText(item.getReserveTxHex());
-                                else
-                                    setText("");
-                            }
-                        };
-                    }
-                });
-        return column;
-    }
-
-    private TableColumn<SignedOffer, SignedOffer> getReserveTxKeyImagesColumn() {
-        TableColumn<SignedOffer, SignedOffer> column = new AutoTooltipTableColumn<>(Res.get("support.txKeyImages")) {
-            {
-                setMinWidth(160);
-            }
-        };
-        column.setCellValueFactory((signedOffer) -> new ReadOnlyObjectWrapper<>(signedOffer.getValue()));
-        column.setCellFactory(
-                new Callback<>() {
-                    @Override
-                    public TableCell<SignedOffer, SignedOffer> call(TableColumn<SignedOffer, SignedOffer> column) {
-                        return new TableCell<>() {
-                            @Override
-                            public void updateItem(final SignedOffer item, boolean empty) {
-                                super.updateItem(item, empty);
-                                if (item != null && !empty)
-                                    setText(item.getReserveTxKeyImages().toString());
-                                else
-                                    setText("");
-                            }
-                        };
-                    }
-                });
-        return column;
-    }
-
-    private TableColumn<SignedOffer, SignedOffer> getArbitratorSignatureColumn() {
-        TableColumn<SignedOffer, SignedOffer> column = new AutoTooltipTableColumn<>(Res.get("support.signature")) {
-            {
-                setMinWidth(160);
-            }
-        };
-        column.setCellValueFactory((signedOffer) -> new ReadOnlyObjectWrapper<>(signedOffer.getValue()));
-        column.setCellFactory(
-                new Callback<>() {
-                    @Override
-                    public TableCell<SignedOffer, SignedOffer> call(TableColumn<SignedOffer, SignedOffer> column) {
-                        return new TableCell<>() {
-                            @Override
-                            public void updateItem(final SignedOffer item, boolean empty) {
-                                super.updateItem(item, empty);
-                                if (item != null && !empty)
-                                    setText(Utilities.bytesAsHexString(item.getArbitratorSignature()));
-                                else
-                                    setText("");
-                            }
-                        };
-                    }
-                });
-        return column;
-    }
-
-    private TableColumn<SignedOffer, SignedOffer> getMakerPenaltyFeeColumn() {
-        TableColumn<SignedOffer, SignedOffer> column = new AutoTooltipTableColumn<>(Res.get("support.maker.penalty.fee")) {
-            {
-                setMinWidth(160);
-            }
-        };
-        column.setCellValueFactory((signedOffer) -> new ReadOnlyObjectWrapper<>(signedOffer.getValue()));
-        column.setCellFactory(
-                new Callback<>() {
-                    @Override
-                    public TableCell<SignedOffer, SignedOffer> call(TableColumn<SignedOffer, SignedOffer> column) {
-                        return new TableCell<>() {
-                            @Override
-                            public void updateItem(final SignedOffer item, boolean empty) {
-                                super.updateItem(item, empty);
-                                if (item != null && !empty)
-                                    setText(HavenoUtils.formatXmr(item.getPenaltyAmount(), true));
-                                else
-                                    setText("");
-                            }
-                        };
-                    }
-                });
-        return column;
-    }
-
-    private TableColumn<SignedOffer, SignedOffer> getReserveTxMinerFeeColumn() {
-        TableColumn<SignedOffer, SignedOffer> column = new AutoTooltipTableColumn<>(Res.get("support.tx.miner.fee")) {
-            {
-                setMinWidth(160);
-            }
-        };
-        column.setCellValueFactory((signedOffer) -> new ReadOnlyObjectWrapper<>(signedOffer.getValue()));
-        column.setCellFactory(
-                new Callback<>() {
-                    @Override
-                    public TableCell<SignedOffer, SignedOffer> call(TableColumn<SignedOffer, SignedOffer> column) {
-                        return new TableCell<>() {
-                            @Override
-                            public void updateItem(final SignedOffer item, boolean empty) {
-                                super.updateItem(item, empty);
-                                if (item != null && !empty)
-                                    setText(HavenoUtils.formatXmr(item.getReserveTxMinerFee(), true));
-                                else
-                                    setText("");
-                            }
-                        };
-                    }
-                });
-        return column;
-    }
-}