package io.bitsquare.gui.main.overlays.notifications; import com.google.inject.Inject; import io.bitsquare.app.Log; import io.bitsquare.arbitration.DisputeManager; import io.bitsquare.common.UserThread; import io.bitsquare.gui.Navigation; import io.bitsquare.gui.main.MainView; import io.bitsquare.gui.main.disputes.DisputesView; import io.bitsquare.gui.main.disputes.trader.TraderDisputeView; import io.bitsquare.gui.main.portfolio.PortfolioView; import io.bitsquare.gui.main.portfolio.pendingtrades.PendingTradesView; import io.bitsquare.trade.Trade; import io.bitsquare.trade.TradeManager; import io.bitsquare.user.Preferences; import javafx.collections.ListChangeListener; import org.fxmisc.easybind.EasyBind; import org.fxmisc.easybind.Subscription; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.Nullable; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.Consumer; public class NotificationCenter { private static final Logger log = LoggerFactory.getLogger(NotificationCenter.class); /////////////////////////////////////////////////////////////////////////////////////////// // Static /////////////////////////////////////////////////////////////////////////////////////////// private final static List<Notification> notifications = new ArrayList<>(); private Consumer<String> selectItemByTradeIdConsumer; static void add(Notification notification) { notifications.add(notification); } static boolean useAnimations; /////////////////////////////////////////////////////////////////////////////////////////// // Instance fields /////////////////////////////////////////////////////////////////////////////////////////// private final TradeManager tradeManager; private final DisputeManager disputeManager; private Preferences preferences; private final Navigation navigation; private final Map<String, Subscription> disputeStateSubscriptionsMap = new HashMap<>(); private final Map<String, Subscription> tradeStateSubscriptionsMap = new HashMap<>(); @Nullable private String selectedTradeId; /////////////////////////////////////////////////////////////////////////////////////////// // Constructor, initialisation /////////////////////////////////////////////////////////////////////////////////////////// @Inject public NotificationCenter(TradeManager tradeManager, DisputeManager disputeManager, Preferences preferences, Navigation navigation) { this.tradeManager = tradeManager; this.disputeManager = disputeManager; this.preferences = preferences; this.navigation = navigation; EasyBind.subscribe(preferences.useAnimationsProperty(), useAnimations -> NotificationCenter.useAnimations = useAnimations); } public void onAllServicesAndViewsInitialized() { tradeManager.getTrades().addListener((ListChangeListener<Trade>) change -> { change.next(); if (change.wasRemoved()) { change.getRemoved().stream().forEach(trade -> { String tradeId = trade.getId(); if (disputeStateSubscriptionsMap.containsKey(tradeId)) { disputeStateSubscriptionsMap.get(tradeId).unsubscribe(); disputeStateSubscriptionsMap.remove(tradeId); } if (tradeStateSubscriptionsMap.containsKey(tradeId)) { tradeStateSubscriptionsMap.get(tradeId).unsubscribe(); tradeStateSubscriptionsMap.remove(tradeId); } }); } if (change.wasAdded()) { change.getAddedSubList().stream().forEach(trade -> { String tradeId = trade.getId(); if (disputeStateSubscriptionsMap.containsKey(tradeId)) { log.debug("We have already an entry in disputeStateSubscriptionsMap."); } else { Subscription disputeStateSubscription = EasyBind.subscribe(trade.disputeStateProperty(), disputeState -> onDisputeStateChanged(trade, disputeState)); disputeStateSubscriptionsMap.put(tradeId, disputeStateSubscription); } if (tradeStateSubscriptionsMap.containsKey(tradeId)) { log.debug("We have already an entry in tradeStateSubscriptionsMap."); } else { Subscription tradeStateSubscription = EasyBind.subscribe(trade.stateProperty(), tradeState -> onTradeStateChanged(trade, tradeState)); tradeStateSubscriptionsMap.put(tradeId, tradeStateSubscription); } }); } }); tradeManager.getTrades().stream() .forEach(trade -> { String tradeId = trade.getId(); Subscription disputeStateSubscription = EasyBind.subscribe(trade.disputeStateProperty(), disputeState -> onDisputeStateChanged(trade, disputeState)); disputeStateSubscriptionsMap.put(tradeId, disputeStateSubscription); Subscription tradeStateSubscription = EasyBind.subscribe(trade.stateProperty(), tradeState -> onTradeStateChanged(trade, tradeState)); tradeStateSubscriptionsMap.put(tradeId, tradeStateSubscription); }); } /////////////////////////////////////////////////////////////////////////////////////////// // Setter/Getter /////////////////////////////////////////////////////////////////////////////////////////// @org.jetbrains.annotations.Nullable public String getSelectedTradeId() { return selectedTradeId; } public void setSelectedTradeId(@Nullable String selectedTradeId) { this.selectedTradeId = selectedTradeId; } public void setSelectItemByTradeIdConsumer(Consumer<String> selectItemByTradeIdConsumer) { this.selectItemByTradeIdConsumer = selectItemByTradeIdConsumer; } /////////////////////////////////////////////////////////////////////////////////////////// // Private /////////////////////////////////////////////////////////////////////////////////////////// private void onTradeStateChanged(Trade trade, Trade.State tradeState) { Log.traceCall(tradeState.toString()); String message = null; if (tradeState == Trade.State.PAYOUT_BROAD_CASTED) { message = "The trade is now completed and you can withdraw your funds."; } else { if (tradeManager.isBuyer(trade.getOffer())) { switch (tradeState) { case OFFERER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG: message = "Your offer has been accepted by a BTC seller."; break; case DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN: message = "Your trade has at least one blockchain confirmation.\n" + "You can start the payment now."; break; } } else { switch (tradeState) { case OFFERER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG: message = "Your offer has been accepted by a BTC buyer."; break; case SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG: message = "The BTC buyer has started the payment."; break; } } } if (message != null) { String key = tradeState.name() + trade.getId(); if (preferences.showAgain(key)) { Notification notification = new Notification().tradeHeadLine(trade.getShortId()).message(message); if (navigation.getCurrentPath() != null && !navigation.getCurrentPath().contains(PendingTradesView.class)) { notification.actionButtonText("Go to \"Open trades\"") .onAction(() -> { preferences.dontShowAgain(key, true); navigation.navigateTo(MainView.class, PortfolioView.class, PendingTradesView.class); if (selectItemByTradeIdConsumer != null) UserThread.runAfter(() -> selectItemByTradeIdConsumer.accept(trade.getId()), 1); }) .onClose(() -> preferences.dontShowAgain(key, true)) .show(); } else if (selectedTradeId != null && !trade.getId().equals(selectedTradeId)) { notification.actionButtonText("Select trade") .onAction(() -> { preferences.dontShowAgain(key, true); if (selectItemByTradeIdConsumer != null) selectItemByTradeIdConsumer.accept(trade.getId()); }) .onClose(() -> preferences.dontShowAgain(key, true)) .show(); } } } } private void onDisputeStateChanged(Trade trade, Trade.DisputeState disputeState) { Log.traceCall(disputeState.toString()); String message = null; if (disputeManager.findOwnDispute(trade.getId()).isPresent()) { boolean supportTicket = disputeManager.findOwnDispute(trade.getId()).get().isSupportTicket(); switch (disputeState) { case NONE: break; case DISPUTE_REQUESTED: break; case DISPUTE_STARTED_BY_PEER: message = supportTicket ? "Your trading peer has opened a support ticket." : "Your trading peer has requested a dispute."; break; case DISPUTE_CLOSED: message = supportTicket ? "The support ticket hase been closed." : "The dispute has been closed."; break; } if (message != null) { Notification notification = new Notification().disputeHeadLine(trade.getShortId()).message(message); if (navigation.getCurrentPath() != null && !navigation.getCurrentPath().contains(TraderDisputeView.class)) { notification.actionButtonText("Go to \"Support\"") .onAction(() -> navigation.navigateTo(MainView.class, DisputesView.class, TraderDisputeView.class)) .show(); } else { notification.show(); } } } } }