Add Notifications

This commit is contained in:
Manfred Karrer 2016-02-15 01:48:49 +01:00
parent 6bf2adae7f
commit 2049e384dc
13 changed files with 301 additions and 698 deletions

View file

@ -136,12 +136,6 @@
<version>8.0.0</version> <version>8.0.0</version>
</dependency> </dependency>
<dependency>
<groupId>eu.hansolo.enzo</groupId>
<artifactId>Enzo</artifactId>
<version>0.1.5</version>
</dependency>
<!-- <dependency> <!-- <dependency>
<groupId>org.fxmisc.richtext</groupId> <groupId>org.fxmisc.richtext</groupId>
<artifactId>richtextfx</artifactId> <artifactId>richtextfx</artifactId>

View file

@ -1,544 +0,0 @@
/*
* Copyright (c) 2013 by Gerrit Grunwald
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package eu.hansolo.enzo.notification;
import io.bitsquare.common.UserThread;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ObjectPropertyBase;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.event.EventType;
import javafx.event.WeakEventHandler;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Region;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.util.Duration;
import org.controlsfx.control.PopOver;
import java.util.stream.IntStream;
/**
* A copy of the original {@link eu.hansolo.enzo.notification.Notification} class at revision eb1d321, containing
* several changes that were otherwise not possible through subclassing or other customization via the existing
* Notification API. See git history for this file for exact details as to what has been changed. All other
* {@code eu.hansolo.enzo.*} types are loaded from the enzo jar (see build.gradle for details).
*/
public class Notification {
private static final Image INFO_ICON = new Image(Notifier.class.getResourceAsStream("info.png"));
private static final Image WARNING_ICON = new Image(Notifier.class.getResourceAsStream("warning.png"));
private static final Image SUCCESS_ICON = new Image(Notifier.class.getResourceAsStream("success.png"));
private static final Image ERROR_ICON = new Image(Notifier.class.getResourceAsStream("error.png"));
private final String TITLE;
private final String MESSAGE;
private final Image IMAGE;
// ******************** Constructors **************************************
public Notification(final String TITLE, final String MESSAGE) {
this(TITLE, MESSAGE, null);
}
public Notification(final String MESSAGE, final Image IMAGE) {
this("", MESSAGE, IMAGE);
}
private Notification(final String TITLE, final String MESSAGE, final Image IMAGE) {
this.TITLE = TITLE;
this.MESSAGE = MESSAGE;
this.IMAGE = IMAGE;
}
// ******************** Inner Classes *************************************
public enum Notifier {
INSTANCE;
private static final double ICON_WIDTH = 24;
private static final double ICON_HEIGHT = 24;
private static double width = 321;
private static double height = 49;
private static double offsetX = 0;
private static double offsetY = 2;
private static double spacingY = 5;
private static Pos popupLocation = Pos.TOP_RIGHT;
private static Stage stageRef = null;
private Duration popupLifetime;
private Duration popupAnimationTime;
private Stage stage;
private Scene scene;
private ObservableList<PopOver> popups;
// ******************** Constructor ***************************************
Notifier() {
init();
initGraphics();
}
// ******************** Initialization ************************************
private void init() {
popupLifetime = Duration.millis(5000);
popupAnimationTime = Duration.millis(500);
popups = FXCollections.observableArrayList();
}
private void initGraphics() {
scene = new Scene(new Region());
scene.setFill(null);
scene.getStylesheets().setAll(
getClass().getResource("/io/bitsquare/gui/bitsquare.css").toExternalForm(),
getClass().getResource("/io/bitsquare/gui/images.css").toExternalForm());
stage = new Stage();
stage.initStyle(StageStyle.TRANSPARENT);
}
// ******************** Methods *******************************************
/**
* @param STAGE_REF The Notification will be positioned relative to the given Stage.<br>
* If null then the Notification will be positioned relative to the primary Screen.
* @param POPUP_LOCATION The default is TOP_RIGHT of primary Screen.
*/
public static void setPopupLocation(final Stage STAGE_REF, final Pos POPUP_LOCATION) {
if (null != STAGE_REF) {
INSTANCE.stage.initOwner(STAGE_REF);
Notifier.stageRef = STAGE_REF;
}
Notifier.popupLocation = POPUP_LOCATION;
}
/**
* Sets the Notification's owner stage so that when the owner
* stage is closed Notifications will be shut down as well.<br>
* This is only needed if <code>setPopupLocation</code> is called
* <u>without</u> a stage reference.
*
* @param OWNER
*/
public static void setNotificationOwner(final Stage OWNER) {
INSTANCE.stage.initOwner(OWNER);
}
/**
* @param OFFSET_X The horizontal shift required.
* <br> The default is 0 px.
*/
public static void setOffsetX(final double OFFSET_X) {
Notifier.offsetX = OFFSET_X;
}
/**
* @param OFFSET_Y The vertical shift required.
* <br> The default is 25 px.
*/
public static void setOffsetY(final double OFFSET_Y) {
Notifier.offsetY = OFFSET_Y;
}
/**
* @param WIDTH The default is 300 px.
*/
public static void setWidth(final double WIDTH) {
Notifier.width = WIDTH;
}
/**
* @param HEIGHT The default is 80 px.
*/
public static void setHeight(final double HEIGHT) {
Notifier.height = HEIGHT;
}
/**
* @param SPACING_Y The spacing between multiple Notifications.
* <br> The default is 5 px.
*/
public static void setSpacingY(final double SPACING_Y) {
Notifier.spacingY = SPACING_Y;
}
public void stop() {
popups.clear();
stage.close();
}
/**
* Returns the Duration that the notification will stay on screen before it
* will fade out. The default is 5000 ms
*
* @return the Duration the popup notification will stay on screen
*/
public Duration getPopupLifetime() {
return popupLifetime;
}
/**
* Defines the Duration that the popup notification will stay on screen before it
* will fade out. The parameter is limited to values between 2 and 20 seconds.
*
* @param POPUP_LIFETIME
*/
public void setPopupLifetime(final Duration POPUP_LIFETIME) {
popupLifetime = Duration.millis(clamp(2000, 20000, POPUP_LIFETIME.toMillis()));
}
/**
* Returns the Duration that it takes to fade out the notification
* The parameter is limited to values between 0 and 1000 ms
*
* @return the Duration that it takes to fade out the notification
*/
public Duration getPopupAnimationTime() {
return popupAnimationTime;
}
/**
* Defines the Duration that it takes to fade out the notification
* The parameter is limited to values between 0 and 1000 ms
* Default value is 500 ms
*
* @param POPUP_ANIMATION_TIME
*/
public void setPopupAnimationTime(final Duration POPUP_ANIMATION_TIME) {
popupAnimationTime = Duration.millis(clamp(0, 1000, POPUP_ANIMATION_TIME.toMillis()));
}
/**
* Show the given Notification on the screen
*
* @param NOTIFICATION
*/
public void notify(final Notification NOTIFICATION) {
preOrder();
showPopup(NOTIFICATION);
}
/**
* Show a Notification with the given parameters on the screen
*
* @param TITLE
* @param MESSAGE
* @param IMAGE
*/
public void notify(final String TITLE, final String MESSAGE, final Image IMAGE) {
notify(new Notification(TITLE, MESSAGE, IMAGE));
}
/**
* Show a Notification with the given title and message and an Info icon
*
* @param TITLE
* @param MESSAGE
*/
public void notifyInfo(final String TITLE, final String MESSAGE) {
notify(new Notification(TITLE, MESSAGE, Notification.INFO_ICON));
}
/**
* Show a Notification with the given title and message and a Warning icon
*
* @param TITLE
* @param MESSAGE
*/
public void notifyWarning(final String TITLE, final String MESSAGE) {
notify(new Notification(TITLE, MESSAGE, Notification.WARNING_ICON));
}
/**
* Show a Notification with the given title and message and a Checkmark icon
*
* @param TITLE
* @param MESSAGE
*/
public void notifySuccess(final String TITLE, final String MESSAGE) {
notify(new Notification(TITLE, MESSAGE, Notification.SUCCESS_ICON));
}
/**
* Show a Notification with the given title and message and an Error icon
*
* @param TITLE
* @param MESSAGE
*/
public void notifyError(final String TITLE, final String MESSAGE) {
notify(new Notification(TITLE, MESSAGE, Notification.ERROR_ICON));
}
/**
* Makes sure that the given VALUE is within the range of MIN to MAX
*
* @param MIN
* @param MAX
* @param VALUE
* @return
*/
private double clamp(final double MIN, final double MAX, final double VALUE) {
if (VALUE < MIN) return MIN;
if (VALUE > MAX) return MAX;
return VALUE;
}
/**
* Reorder the popup Notifications on screen so that the latest Notification will stay on top
*/
private void preOrder() {
if (popups.isEmpty()) return;
IntStream.range(0, popups.size()).parallel().forEachOrdered(
i -> UserThread.execute(() -> {
switch (popupLocation) {
case TOP_LEFT:
case TOP_CENTER:
case TOP_RIGHT:
popups.get(i).setY(popups.get(i).getY() + height + spacingY);
break;
case BOTTOM_LEFT:
case BOTTOM_CENTER:
case BOTTOM_RIGHT:
popups.get(i).setY(popups.get(i).getY() - height - spacingY);
break;
default:
popups.get(i).setY(popups.get(i).getY() - height - spacingY);
break;
}
})
);
}
/**
* Creates and shows a popup with the data from the given Notification object
*
* @param NOTIFICATION
*/
private void showPopup(final Notification NOTIFICATION) {
ImageView icon = new ImageView(
new Image(Notifier.class.getResourceAsStream("/images/notification_logo.png")));
icon.relocate(10, 7);
Label title = new Label(NOTIFICATION.TITLE);
title.setStyle(" -fx-text-fill:#333333; -fx-font-size:12; -fx-font-weight:bold;");
title.relocate(60, 6);
Label message = new Label(NOTIFICATION.MESSAGE);
message.relocate(60, 25);
message.setStyle(" -fx-text-fill:#333333; -fx-font-size:11; ");
Pane popupLayout = new Pane();
popupLayout.setPrefSize(width, height);
popupLayout.getChildren().addAll(icon, title, message);
PopOver popOver = new PopOver(popupLayout);
popOver.setDetachable(false);
popOver.setArrowSize(0);
popOver.setX(getX());
popOver.setY(getY());
popOver.addEventHandler(MouseEvent.MOUSE_PRESSED, new WeakEventHandler<>(event ->
fireNotificationEvent(new NotificationEvent(NOTIFICATION, Notifier.this, popOver,
NotificationEvent.NOTIFICATION_PRESSED))
));
popups.add(popOver);
// Add a timeline for popup fade out
KeyValue fadeOutBegin = new KeyValue(popOver.opacityProperty(), 1.0);
KeyValue fadeOutEnd = new KeyValue(popOver.opacityProperty(), 0.0);
KeyFrame kfBegin = new KeyFrame(Duration.ZERO, fadeOutBegin);
KeyFrame kfEnd = new KeyFrame(popupAnimationTime, fadeOutEnd);
Timeline timeline = new Timeline(kfBegin, kfEnd);
timeline.setDelay(popupLifetime);
timeline.setOnFinished(actionEvent -> UserThread.execute(() -> {
popOver.hide();
popups.remove(popOver);
fireNotificationEvent(new NotificationEvent(NOTIFICATION, Notifier.this, popOver,
NotificationEvent.HIDE_NOTIFICATION));
}));
if (stage.isShowing()) {
stage.toFront();
} else {
stage.show();
}
popOver.show(stage);
fireNotificationEvent(new NotificationEvent(NOTIFICATION, Notifier.this, popOver,
NotificationEvent.SHOW_NOTIFICATION));
timeline.play();
}
private double getX() {
if (null == stageRef) return calcX(0.0, Screen.getPrimary().getBounds().getWidth());
return calcX(stageRef.getX(), stageRef.getWidth());
}
private double getY() {
if (null == stageRef) return calcY(0.0, Screen.getPrimary().getBounds().getHeight());
return calcY(stageRef.getY(), stageRef.getHeight());
}
private double calcX(final double LEFT, final double TOTAL_WIDTH) {
switch (popupLocation) {
case TOP_LEFT:
case CENTER_LEFT:
case BOTTOM_LEFT:
return LEFT + offsetX;
case TOP_CENTER:
case CENTER:
case BOTTOM_CENTER:
return LEFT + (TOTAL_WIDTH - width) * 0.5 - offsetX;
case TOP_RIGHT:
case CENTER_RIGHT:
case BOTTOM_RIGHT:
return LEFT + TOTAL_WIDTH - width - offsetX;
default:
return 0.0;
}
}
private double calcY(final double TOP, final double TOTAL_HEIGHT) {
switch (popupLocation) {
case TOP_LEFT:
case TOP_CENTER:
case TOP_RIGHT:
return TOP + offsetY;
case CENTER_LEFT:
case CENTER:
case CENTER_RIGHT:
return TOP + (TOTAL_HEIGHT - height) / 2 - offsetY;
case BOTTOM_LEFT:
case BOTTOM_CENTER:
case BOTTOM_RIGHT:
return TOP + TOTAL_HEIGHT - height - offsetY;
default:
return 0.0;
}
}
// ******************** Event handling ********************************
public final ObjectProperty<EventHandler<NotificationEvent>> onNotificationPressedProperty() {
return onNotificationPressed;
}
public final void setOnNotificationPressed(EventHandler<NotificationEvent> value) {
onNotificationPressedProperty().set(value);
}
public final EventHandler<NotificationEvent> getOnNotificationPressed() {
return onNotificationPressedProperty().get();
}
private final ObjectProperty<EventHandler<NotificationEvent>> onNotificationPressed = new
ObjectPropertyBase<EventHandler<NotificationEvent>>() {
@Override
public Object getBean() {
return this;
}
@Override
public String getName() {
return "onNotificationPressed";
}
};
public final ObjectProperty<EventHandler<NotificationEvent>> onShowNotificationProperty() {
return onShowNotification;
}
public final void setOnShowNotification(EventHandler<NotificationEvent> value) {
onShowNotificationProperty().set(value);
}
public final EventHandler<NotificationEvent> getOnShowNotification() {
return onShowNotificationProperty().get();
}
private final ObjectProperty<EventHandler<NotificationEvent>> onShowNotification = new
ObjectPropertyBase<EventHandler<NotificationEvent>>() {
@Override
public Object getBean() {
return this;
}
@Override
public String getName() {
return "onShowNotification";
}
};
public final ObjectProperty<EventHandler<NotificationEvent>> onHideNotificationProperty() {
return onHideNotification;
}
public final void setOnHideNotification(EventHandler<NotificationEvent> value) {
onHideNotificationProperty().set(value);
}
public final EventHandler<NotificationEvent> getOnHideNotification() {
return onHideNotificationProperty().get();
}
private final ObjectProperty<EventHandler<NotificationEvent>> onHideNotification = new
ObjectPropertyBase<EventHandler<NotificationEvent>>() {
@Override
public Object getBean() {
return this;
}
@Override
public String getName() {
return "onHideNotification";
}
};
public void fireNotificationEvent(final NotificationEvent EVENT) {
final EventType TYPE = EVENT.getEventType();
final EventHandler<NotificationEvent> HANDLER;
if (NotificationEvent.NOTIFICATION_PRESSED == TYPE) {
HANDLER = getOnNotificationPressed();
} else if (NotificationEvent.SHOW_NOTIFICATION == TYPE) {
HANDLER = getOnShowNotification();
} else if (NotificationEvent.HIDE_NOTIFICATION == TYPE) {
HANDLER = getOnHideNotification();
} else {
HANDLER = null;
}
if (null == HANDLER) return;
HANDLER.handle(EVENT);
}
}
}

View file

@ -840,10 +840,27 @@ textfield */
-fx-text-fill: #333; -fx-text-fill: #333;
} }
#popup-message { #popup-bg {
-fx-font-size: 15; -fx-font-size: 15;
-fx-text-fill: #333;
-fx-background-color: white;
-fx-background-radius: 5 5 5 5;
-fx-background-insets: 10;
-fx-effect: dropshadow(gaussian, #999, 10, 0, 0, 0);
} }
#popup-button { #notification-popup-headline {
-fx-font-size: 15; -fx-font-size: 12;
-fx-font-weight: bold;
-fx-text-fill: #3c3c3c;
}
#notification-popup-bg {
-fx-font-size: 11;
-fx-text-fill: #3c3c3c;
-fx-background-color: linear-gradient(to bottom, #fcfcfc, #e5e5e5);
-fx-background-radius: 5 5 5 5;
-fx-background-insets: 10;
-fx-effect: dropshadow(gaussian, #434343, 10, 0, 0, 0);
} }

View file

@ -1,38 +0,0 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare 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.
*
* Bitsquare 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 Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.components;
import eu.hansolo.enzo.notification.Notification;
import eu.hansolo.enzo.notification.NotificationBuilder;
import eu.hansolo.enzo.notification.NotifierBuilder;
import io.bitsquare.common.util.Utilities;
/**
* Not sure if we stick with the eu.hansolo.enzo.notification.Notification implementation, so keep it behind a service
*/
public class SystemNotification {
private static final Notification.Notifier notifier = NotifierBuilder.create().build();
public static void openInfoNotification(String title, String message) {
// On windows it causes problems with the hidden stage used in the hansolo Notification implementation
// Lets deactivate it for the moment and fix that with a more native-like or real native solution later.
// Lets deactivate it for Linux as well, as it is not much tested yet
if (Utilities.isOSX())
notifier.notify(NotificationBuilder.create().title(title).message(message).build());
}
}

View file

@ -176,11 +176,6 @@ public class MainViewModel implements ViewModel {
TxIdTextField.setWalletService(walletService); TxIdTextField.setWalletService(walletService);
BalanceTextField.setWalletService(walletService); BalanceTextField.setWalletService(walletService);
BalanceWithConfirmationTextField.setWalletService(walletService); BalanceWithConfirmationTextField.setWalletService(walletService);
if (BitsquareApp.DEV_MODE) {
preferences.setUseAnimations(false);
preferences.setUseEffects(false);
}
} }

View file

@ -1,49 +1,17 @@
package io.bitsquare.gui.main.intructions; package io.bitsquare.gui.main.intructions;
import com.google.inject.Inject; import com.google.inject.Inject;
import io.bitsquare.gui.main.notifications.Notification;
import io.bitsquare.trade.TradeManager; import io.bitsquare.trade.TradeManager;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue;
public class InstructionCenter { public class InstructionCenter {
private final Logger log = LoggerFactory.getLogger(InstructionCenter.class); private final Logger log = LoggerFactory.getLogger(InstructionCenter.class);
private Queue<io.bitsquare.gui.main.notifications.Notification> notifications = new LinkedBlockingQueue<>(3);
private io.bitsquare.gui.main.notifications.Notification displayedNotification; private final TradeManager tradeManager;
private TradeManager tradeManager;
@Inject @Inject
public InstructionCenter(TradeManager tradeManager) { public InstructionCenter(TradeManager tradeManager) {
this.tradeManager = tradeManager; this.tradeManager = tradeManager;
} }
void queueForDisplay(io.bitsquare.gui.main.notifications.Notification notification) {
boolean result = notifications.offer(notification);
if (!result)
log.warn("The capacity is full with popups in the queue.\n\t" +
"Not added new notification=" + notification);
displayNext();
}
void isHidden(Notification notification) {
if (displayedNotification == null || displayedNotification == notification) {
displayedNotification = null;
displayNext();
} else {
log.warn("We got a isHidden called with a wrong notification.\n\t" +
"notification (argument)=" + notification + "\n\tdisplayedPopup=" + displayedNotification);
}
}
private void displayNext() {
if (displayedNotification == null) {
if (!notifications.isEmpty()) {
displayedNotification = notifications.poll();
displayedNotification.display();
}
}
}
} }

View file

@ -1,6 +1,18 @@
package io.bitsquare.gui.main.notifications; package io.bitsquare.gui.main.notifications;
import io.bitsquare.gui.main.popups.Popup; import io.bitsquare.gui.main.popups.Popup;
import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Camera;
import javafx.scene.PerspectiveCamera;
import javafx.scene.transform.Rotate;
import javafx.stage.Modality;
import javafx.stage.Window;
import javafx.util.Duration;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -9,6 +21,7 @@ public class Notification extends Popup {
private boolean hasBeenDisplayed; private boolean hasBeenDisplayed;
public Notification() { public Notification() {
width = 320;
NotificationCenter.add(this); NotificationCenter.add(this);
} }
@ -28,15 +41,138 @@ public class Notification extends Popup {
return (Notification) super.message(message); return (Notification) super.message(message);
} }
@Override
protected void addSeparator() {
// dont show a separator
}
@Override
public void show() { public void show() {
super.show(); super.show();
hasBeenDisplayed = true; hasBeenDisplayed = true;
} }
public void hide() { @Override
super.hide(); public void display() {
super.display();
} }
@Override
protected void animateHide(Runnable onFinishedHandler) {
if (NotificationCenter.useAnimations) {
double duration = 400;
Interpolator interpolator = Interpolator.SPLINE(0.25, 0.1, 0.25, 1);
gridPane.setRotationAxis(Rotate.X_AXIS);
Camera camera = gridPane.getScene().getCamera();
gridPane.getScene().setCamera(new PerspectiveCamera());
Timeline timeline = new Timeline();
ObservableList<KeyFrame> keyFrames = timeline.getKeyFrames();
keyFrames.add(new KeyFrame(Duration.millis(0),
new KeyValue(gridPane.rotateProperty(), 0, interpolator),
new KeyValue(gridPane.opacityProperty(), 1, interpolator)
));
keyFrames.add(new KeyFrame(Duration.millis(duration),
new KeyValue(gridPane.rotateProperty(), -90, interpolator),
new KeyValue(gridPane.opacityProperty(), 0, interpolator)
));
timeline.setOnFinished(event -> {
gridPane.setRotate(0);
gridPane.setRotationAxis(Rotate.Z_AXIS);
gridPane.getScene().setCamera(camera);
onFinishedHandler.run();
});
timeline.play();
} else {
onFinishedHandler.run();
}
}
@Override
protected void animateDisplay() {
if (NotificationCenter.useAnimations) {
double startX = 320;
double duration = 600;
Interpolator interpolator = Interpolator.SPLINE(0.25, 0.1, 0.25, 1);
Timeline timeline = new Timeline();
ObservableList<KeyFrame> keyFrames = timeline.getKeyFrames();
keyFrames.add(new KeyFrame(Duration.millis(0),
new KeyValue(gridPane.opacityProperty(), 0, interpolator),
new KeyValue(gridPane.translateXProperty(), startX, interpolator)
));
//bouncing
/* keyFrames.add(new KeyFrame(Duration.millis(duration * 0.6),
new KeyValue(gridPane.opacityProperty(), 1, interpolator),
new KeyValue(gridPane.translateXProperty(), -12, interpolator)
));
keyFrames.add(new KeyFrame(Duration.millis(duration * 0.8),
new KeyValue(gridPane.opacityProperty(), 1, interpolator),
new KeyValue(gridPane.translateXProperty(), 4, interpolator)
));*/
keyFrames.add(new KeyFrame(Duration.millis(duration),
new KeyValue(gridPane.opacityProperty(), 1, interpolator),
new KeyValue(gridPane.translateXProperty(), 0, interpolator)
));
timeline.play();
}
}
@Override
protected void createGridPane() {
super.createGridPane();
gridPane.setPadding(new Insets(20, 20, 20, 20));
}
@Override
protected void addCloseButton() {
buttonDistance = 10;
super.addCloseButton();
}
@Override
protected void applyStyles() {
gridPane.setId("notification-popup-bg");
if (headLineLabel != null)
headLineLabel.setId("notification-popup-headline");
}
@Override
protected void setModality() {
stage.initModality(Modality.NONE);
}
@Override
protected void layout() {
Window window = owner.getScene().getWindow();
double titleBarHeight = window.getHeight() - owner.getScene().getHeight();
stage.setX(Math.round(window.getX() + window.getWidth() - stage.getWidth()));
stage.setY(Math.round(window.getY() + titleBarHeight));
}
@Override
protected void addEffectToBackground() {
}
@Override
protected void removeEffectFromBackground() {
}
/* @Override
protected void addCloseButton() {
closeButton = new Button("Close");
closeButton.setOnAction(event -> {
hide();
closeHandlerOptional.ifPresent(closeHandler -> closeHandler.run());
});
GridPane.setHalignment(closeButton, HPos.RIGHT);
GridPane.setRowIndex(closeButton, ++rowIndex);
GridPane.setColumnIndex(closeButton, 1);
gridPane.getChildren().add(closeButton);
}*/
public boolean isHasBeenDisplayed() { public boolean isHasBeenDisplayed() {
return hasBeenDisplayed; return hasBeenDisplayed;
} }

View file

@ -3,18 +3,26 @@ package io.bitsquare.gui.main.notifications;
import com.google.inject.Inject; import com.google.inject.Inject;
import io.bitsquare.app.Log; import io.bitsquare.app.Log;
import io.bitsquare.arbitration.DisputeManager; 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.portfolio.PortfolioView;
import io.bitsquare.gui.main.portfolio.pendingtrades.PendingTradesView;
import io.bitsquare.trade.Trade; import io.bitsquare.trade.Trade;
import io.bitsquare.trade.TradeManager; import io.bitsquare.trade.TradeManager;
import io.bitsquare.user.Preferences;
import javafx.collections.ListChangeListener; import javafx.collections.ListChangeListener;
import org.fxmisc.easybind.EasyBind; import org.fxmisc.easybind.EasyBind;
import org.fxmisc.easybind.Subscription; import org.fxmisc.easybind.Subscription;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Consumer;
public class NotificationCenter { public class NotificationCenter {
private static final Logger log = LoggerFactory.getLogger(NotificationCenter.class); private static final Logger log = LoggerFactory.getLogger(NotificationCenter.class);
@ -25,11 +33,14 @@ public class NotificationCenter {
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
private final static List<Notification> notifications = new ArrayList<>(); private final static List<Notification> notifications = new ArrayList<>();
private Consumer<String> selectItemByTradeIdConsumer;
static void add(Notification notification) { static void add(Notification notification) {
notifications.add(notification); notifications.add(notification);
} }
static boolean useAnimations;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Instance fields // Instance fields
@ -37,9 +48,12 @@ public class NotificationCenter {
private TradeManager tradeManager; private TradeManager tradeManager;
private DisputeManager disputeManager; private DisputeManager disputeManager;
private Preferences preferences;
private Navigation navigation;
private final Map<String, Subscription> disputeStateSubscriptionsMap = new HashMap<>(); private final Map<String, Subscription> disputeStateSubscriptionsMap = new HashMap<>();
private final Map<String, Subscription> tradeStateSubscriptionsMap = new HashMap<>(); private final Map<String, Subscription> tradeStateSubscriptionsMap = new HashMap<>();
@Nullable
private String selectedTradeId; private String selectedTradeId;
@ -48,16 +62,18 @@ public class NotificationCenter {
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@Inject @Inject
public NotificationCenter(TradeManager tradeManager, DisputeManager disputeManager) { public NotificationCenter(TradeManager tradeManager, DisputeManager disputeManager, Preferences preferences, Navigation navigation) {
this.tradeManager = tradeManager; this.tradeManager = tradeManager;
this.disputeManager = disputeManager; this.disputeManager = disputeManager;
this.preferences = preferences;
this.navigation = navigation;
EasyBind.subscribe(preferences.useAnimationsProperty(), useAnimations -> NotificationCenter.useAnimations = useAnimations);
} }
public void onAllServicesInitialized() { public void onAllServicesInitialized() {
tradeManager.getTrades().addListener((ListChangeListener<Trade>) change -> { tradeManager.getTrades().addListener((ListChangeListener<Trade>) change -> {
change.next(); change.next();
log.error("change getRemoved " + change.getRemoved());
log.error("change getAddedSubList " + change.getAddedSubList());
if (change.wasRemoved()) { if (change.wasRemoved()) {
change.getRemoved().stream().forEach(trade -> { change.getRemoved().stream().forEach(trade -> {
String tradeId = trade.getId(); String tradeId = trade.getId();
@ -112,10 +128,14 @@ public class NotificationCenter {
return selectedTradeId; return selectedTradeId;
} }
public void setSelectedTradeId(String selectedTradeId) { public void setSelectedTradeId(@Nullable String selectedTradeId) {
this.selectedTradeId = selectedTradeId; this.selectedTradeId = selectedTradeId;
} }
public void setSelectItemByTradeIdConsumer(Consumer<String> selectItemByTradeIdConsumer) {
this.selectItemByTradeIdConsumer = selectItemByTradeIdConsumer;
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Private // Private
@ -127,43 +147,54 @@ public class NotificationCenter {
if (tradeManager.isBuyer(trade.getOffer())) { if (tradeManager.isBuyer(trade.getOffer())) {
switch (tradeState) { switch (tradeState) {
case DEPOSIT_PUBLISHED_MSG_RECEIVED: case DEPOSIT_PUBLISHED_MSG_RECEIVED:
message = "Your offer has been accepted by a seller.\n" + message = "Your offer has been accepted by a seller.";
"You need to wait for one blockchain confirmation before starting the payment.";
break; break;
case DEPOSIT_CONFIRMED: case DEPOSIT_CONFIRMED:
message = "The deposit transaction of your trade has got the first blockchain confirmation.\n" + message = "Your trade has at least one blockchain confirmation.\n" +
"You have to start the payment to the bitcoin seller now."; "You can start the payment now.";
break; break;
/* case FIAT_PAYMENT_RECEIPT_MSG_RECEIVED: /* case FIAT_PAYMENT_RECEIPT_MSG_RECEIVED:
case PAYOUT_TX_COMMITTED: case PAYOUT_TX_COMMITTED:
case PAYOUT_TX_SENT:*/ case PAYOUT_TX_SENT:*/
case PAYOUT_BROAD_CASTED: case PAYOUT_BROAD_CASTED:
message = "The bitcoin seller has confirmed the receipt of your payment and the payout transaction has been published.\n" + message = "The trade is now completed and you can withdraw your funds.";
"The trade is now completed and you can withdraw your funds.";
break; break;
} }
} else { } else {
switch (tradeState) { switch (tradeState) {
case DEPOSIT_PUBLISHED_MSG_RECEIVED: case DEPOSIT_PUBLISHED_MSG_RECEIVED:
message = "Your offer has been accepted by a buyer.\n" + message = "Your offer has been accepted by a buyer.";
"You need to wait for one blockchain confirmation before starting the payment.";
break; break;
case FIAT_PAYMENT_STARTED_MSG_RECEIVED: case FIAT_PAYMENT_STARTED_MSG_RECEIVED:
message = "The bitcoin buyer has started the payment.\n" + message = "The bitcoin buyer has started the payment.";
"Please check your payment account if you have received his payment.";
break; break;
/* case FIAT_PAYMENT_RECEIPT_MSG_SENT: /* case FIAT_PAYMENT_RECEIPT_MSG_SENT:
case PAYOUT_TX_RECEIVED: case PAYOUT_TX_RECEIVED:
case PAYOUT_TX_COMMITTED:*/ case PAYOUT_TX_COMMITTED:*/
case PAYOUT_BROAD_CASTED: case PAYOUT_BROAD_CASTED:
message = "The payout transaction has been published.\n" + message = "The trade is now completed and you can withdraw your funds.";
"The trade is now completed and you can withdraw your funds.";
} }
} }
if (message != null && !trade.getId().equals(selectedTradeId)) if (message != null) {
new Notification().tradeHeadLine(trade.getShortId()).message(message).show(); 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(() -> {
navigation.navigateTo(MainView.class, PortfolioView.class, PendingTradesView.class);
UserThread.runAfter(() -> {
selectItemByTradeIdConsumer.accept(trade.getId());
}, 1);
})
.show();
} else if (selectedTradeId != null && !trade.getId().equals(selectedTradeId)) {
notification.actionButtonText("Select trade")
.onAction(() -> selectItemByTradeIdConsumer.accept(trade.getId()))
.show();
}
}
} }
private void onDisputeStateChanged(Trade trade, Trade.DisputeState disputeState) { private void onDisputeStateChanged(Trade trade, Trade.DisputeState disputeState) {

View file

@ -27,7 +27,6 @@ import javafx.beans.value.ChangeListener;
import javafx.geometry.HPos; import javafx.geometry.HPos;
import javafx.geometry.Insets; import javafx.geometry.Insets;
import javafx.geometry.Orientation; import javafx.geometry.Orientation;
import javafx.geometry.Point2D;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.scene.control.*; import javafx.scene.control.*;
import javafx.scene.layout.*; import javafx.scene.layout.*;
@ -74,6 +73,7 @@ public class Popup {
private Preferences preferences; private Preferences preferences;
private ChangeListener<Number> positionListener; private ChangeListener<Number> positionListener;
private Timer centerTime; private Timer centerTime;
protected double buttonDistance = 20;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -86,6 +86,7 @@ public class Popup {
public void show() { public void show() {
createGridPane(); createGridPane();
addHeadLine(); addHeadLine();
addSeparator();
if (showProgressIndicator) if (showProgressIndicator)
addProgressIndicator(); addProgressIndicator();
@ -96,24 +97,34 @@ public class Popup {
addCloseButton(); addCloseButton();
addDontShowAgainCheckBox(); addDontShowAgainCheckBox();
applyStyles();
PopupManager.queueForDisplay(this); PopupManager.queueForDisplay(this);
} }
public void hide() { public void hide() {
owner.getScene().getWindow().xProperty().removeListener(positionListener); animateHide(() -> {
owner.getScene().getWindow().yProperty().removeListener(positionListener); Window window = owner.getScene().getWindow();
window.xProperty().removeListener(positionListener);
window.yProperty().removeListener(positionListener);
window.widthProperty().removeListener(positionListener);
if (centerTime != null) if (centerTime != null)
centerTime.cancel(); centerTime.cancel();
MainView.removeBlur(); removeEffectFromBackground();
if (stage != null)
stage.hide();
else
log.warn("Stage is null");
cleanup(); if (stage != null)
PopupManager.isHidden(this); stage.hide();
else
log.warn("Stage is null");
cleanup();
PopupManager.isHidden(Popup.this);
});
}
protected void animateHide(Runnable onFinishedHandler) {
onFinishedHandler.run();
} }
protected void cleanup() { protected void cleanup() {
@ -217,11 +228,6 @@ public class Popup {
gridPane.setVgap(5); gridPane.setVgap(5);
gridPane.setPadding(new Insets(30, 30, 30, 30)); gridPane.setPadding(new Insets(30, 30, 30, 30));
gridPane.setPrefWidth(width); gridPane.setPrefWidth(width);
gridPane.setStyle("-fx-background-color: white;" +
"-fx-background-radius: 5 5 5 5;" +
"-fx-effect: dropshadow(gaussian, #999, 10, 0, 0, 0);" +
"-fx-background-insets: 10;"
);
ColumnConstraints columnConstraints1 = new ColumnConstraints(); ColumnConstraints columnConstraints1 = new ColumnConstraints();
columnConstraints1.setHalignment(HPos.RIGHT); columnConstraints1.setHalignment(HPos.RIGHT);
@ -232,7 +238,7 @@ public class Popup {
} }
protected void blurAgain() { protected void blurAgain() {
FxTimer.runLater(Duration.ofMillis(Transitions.DEFAULT_DURATION), () -> MainView.blurLight()); FxTimer.runLater(Duration.ofMillis(Transitions.DEFAULT_DURATION), MainView::blurLight);
} }
public void display() { public void display() {
@ -244,14 +250,15 @@ public class Popup {
scene.getStylesheets().setAll(owner.getScene().getStylesheets()); scene.getStylesheets().setAll(owner.getScene().getStylesheets());
scene.setFill(Color.TRANSPARENT); scene.setFill(Color.TRANSPARENT);
stage.setScene(scene); stage.setScene(scene);
stage.initModality(Modality.WINDOW_MODAL); setModality();
stage.initStyle(StageStyle.TRANSPARENT); stage.initStyle(StageStyle.TRANSPARENT);
stage.initOwner(owner.getScene().getWindow()); Window window = owner.getScene().getWindow();
stage.initOwner(window);
stage.show(); stage.show();
centerPopup(); layout();
MainView.blurLight(); addEffectToBackground();
// On Linux the owner stage does not move the child stage as it does on Mac // On Linux the owner stage does not move the child stage as it does on Mac
// So we need to apply centerPopup. Further with fast movements the handler loses // So we need to apply centerPopup. Further with fast movements the handler loses
@ -259,34 +266,61 @@ public class Popup {
// Also on Mac sometimes the popups are positioned outside of the main app, so keep it for all OS // Also on Mac sometimes the popups are positioned outside of the main app, so keep it for all OS
positionListener = (observable, oldValue, newValue) -> { positionListener = (observable, oldValue, newValue) -> {
if (stage != null) { if (stage != null) {
centerPopup(); layout();
if (centerTime != null) if (centerTime != null)
centerTime.cancel(); centerTime.cancel();
centerTime = UserThread.runAfter(this::centerPopup, 3); centerTime = UserThread.runAfter(this::layout, 3);
} }
}; };
owner.getScene().getWindow().xProperty().addListener(positionListener); window.xProperty().addListener(positionListener);
owner.getScene().getWindow().yProperty().addListener(positionListener); window.yProperty().addListener(positionListener);
window.widthProperty().addListener(positionListener);
animateDisplay();
} }
protected void centerPopup() { protected void animateDisplay() {
}
protected void setModality() {
stage.initModality(Modality.WINDOW_MODAL);
}
protected void applyStyles() {
gridPane.setId("popup-bg");
if (headLineLabel != null)
headLineLabel.setId("popup-headline");
}
protected void addEffectToBackground() {
MainView.blurLight();
}
protected void removeEffectFromBackground() {
MainView.removeBlur();
}
protected void layout() {
Window window = owner.getScene().getWindow(); Window window = owner.getScene().getWindow();
double titleBarHeight = window.getHeight() - owner.getScene().getHeight(); double titleBarHeight = window.getHeight() - owner.getScene().getHeight();
Point2D point = owner.localToScene(0, 0); stage.setX(Math.round(window.getX() + (owner.getWidth() - stage.getWidth()) / 2));
stage.setX(Math.round(window.getX() + point.getX() + (owner.getWidth() - stage.getWidth()) / 2)); stage.setY(Math.round(window.getY() + titleBarHeight + (owner.getHeight() - stage.getHeight()) / 2));
stage.setY(Math.round(window.getY() + titleBarHeight + point.getY() + (owner.getHeight() - stage.getHeight()) / 2));
} }
protected void addHeadLine() { protected void addHeadLine() {
if (headLine != null) { if (headLine != null) {
headLineLabel = new Label(BSResources.get(headLine)); headLineLabel = new Label(BSResources.get(headLine));
headLineLabel.setMouseTransparent(true); headLineLabel.setMouseTransparent(true);
headLineLabel.setId("popup-headline");
GridPane.setHalignment(headLineLabel, HPos.LEFT); GridPane.setHalignment(headLineLabel, HPos.LEFT);
GridPane.setRowIndex(headLineLabel, ++rowIndex); GridPane.setRowIndex(headLineLabel, ++rowIndex);
GridPane.setColumnSpan(headLineLabel, 2); GridPane.setColumnSpan(headLineLabel, 2);
gridPane.getChildren().addAll(headLineLabel);
}
}
protected void addSeparator() {
if (headLine != null) {
Separator separator = new Separator(); Separator separator = new Separator();
separator.setMouseTransparent(true); separator.setMouseTransparent(true);
separator.setOrientation(Orientation.HORIZONTAL); separator.setOrientation(Orientation.HORIZONTAL);
@ -295,7 +329,7 @@ public class Popup {
GridPane.setRowIndex(separator, ++rowIndex); GridPane.setRowIndex(separator, ++rowIndex);
GridPane.setColumnSpan(separator, 2); GridPane.setColumnSpan(separator, 2);
gridPane.getChildren().addAll(headLineLabel, separator); gridPane.getChildren().add(separator);
} }
} }
@ -322,7 +356,6 @@ public class Popup {
"It will make debugging easier if you can attach the bitsquare.log file which you can find in the application directory."); "It will make debugging easier if you can attach the bitsquare.log file which you can find in the application directory.");
Button githubButton = new Button("Report to Github issue tracker"); Button githubButton = new Button("Report to Github issue tracker");
githubButton.setId("popup-button");
GridPane.setMargin(githubButton, new Insets(20, 0, 0, 0)); GridPane.setMargin(githubButton, new Insets(20, 0, 0, 0));
GridPane.setHalignment(githubButton, HPos.RIGHT); GridPane.setHalignment(githubButton, HPos.RIGHT);
GridPane.setRowIndex(githubButton, ++rowIndex); GridPane.setRowIndex(githubButton, ++rowIndex);
@ -335,7 +368,6 @@ public class Popup {
}); });
Button mailButton = new Button("Report by email"); Button mailButton = new Button("Report by email");
mailButton.setId("popup-button");
GridPane.setHalignment(mailButton, HPos.RIGHT); GridPane.setHalignment(mailButton, HPos.RIGHT);
GridPane.setRowIndex(mailButton, ++rowIndex); GridPane.setRowIndex(mailButton, ++rowIndex);
GridPane.setColumnIndex(mailButton, 1); GridPane.setColumnIndex(mailButton, 1);
@ -373,7 +405,6 @@ public class Popup {
protected void addCloseButton() { protected void addCloseButton() {
closeButton = new Button(closeButtonText == null ? "Close" : closeButtonText); closeButton = new Button(closeButtonText == null ? "Close" : closeButtonText);
closeButton.setId("popup-button");
closeButton.setOnAction(event -> { closeButton.setOnAction(event -> {
hide(); hide();
closeHandlerOptional.ifPresent(closeHandler -> closeHandler.run()); closeHandlerOptional.ifPresent(closeHandler -> closeHandler.run());
@ -381,7 +412,6 @@ public class Popup {
if (actionHandlerOptional.isPresent() || actionButtonText != null) { if (actionHandlerOptional.isPresent() || actionButtonText != null) {
actionButton = new Button(actionButtonText == null ? "Ok" : actionButtonText); actionButton = new Button(actionButtonText == null ? "Ok" : actionButtonText);
actionButton.setId("popup-button");
actionButton.setDefaultButton(true); actionButton.setDefaultButton(true);
//TODO app wide focus //TODO app wide focus
//actionButton.requestFocus(); //actionButton.requestFocus();
@ -399,13 +429,13 @@ public class Popup {
GridPane.setHalignment(hBox, HPos.RIGHT); GridPane.setHalignment(hBox, HPos.RIGHT);
GridPane.setRowIndex(hBox, ++rowIndex); GridPane.setRowIndex(hBox, ++rowIndex);
GridPane.setColumnSpan(hBox, 2); GridPane.setColumnSpan(hBox, 2);
GridPane.setMargin(hBox, new Insets(30, 0, 0, 0)); GridPane.setMargin(hBox, new Insets(buttonDistance, 0, 0, 0));
gridPane.getChildren().add(hBox); gridPane.getChildren().add(hBox);
} else { } else {
closeButton.setDefaultButton(true); closeButton.setDefaultButton(true);
GridPane.setHalignment(closeButton, HPos.RIGHT); GridPane.setHalignment(closeButton, HPos.RIGHT);
if (!showReportErrorButtons) if (!showReportErrorButtons)
GridPane.setMargin(closeButton, new Insets(20, 0, 0, 0)); GridPane.setMargin(closeButton, new Insets(buttonDistance, 0, 0, 0));
GridPane.setRowIndex(closeButton, ++rowIndex); GridPane.setRowIndex(closeButton, ++rowIndex);
GridPane.setColumnIndex(closeButton, 1); GridPane.setColumnIndex(closeButton, 1);
gridPane.getChildren().add(closeButton); gridPane.getChildren().add(closeButton);

View file

@ -32,6 +32,7 @@ import io.bitsquare.gui.Navigation;
import io.bitsquare.gui.common.model.ActivatableDataModel; import io.bitsquare.gui.common.model.ActivatableDataModel;
import io.bitsquare.gui.main.MainView; import io.bitsquare.gui.main.MainView;
import io.bitsquare.gui.main.disputes.DisputesView; import io.bitsquare.gui.main.disputes.DisputesView;
import io.bitsquare.gui.main.notifications.NotificationCenter;
import io.bitsquare.gui.main.popups.SelectDepositTxPopup; import io.bitsquare.gui.main.popups.SelectDepositTxPopup;
import io.bitsquare.gui.main.popups.WalletPasswordPopup; import io.bitsquare.gui.main.popups.WalletPasswordPopup;
import io.bitsquare.payment.PaymentAccountContractData; import io.bitsquare.payment.PaymentAccountContractData;
@ -71,6 +72,7 @@ public class PendingTradesDataModel extends ActivatableDataModel {
public final DisputeManager disputeManager; public final DisputeManager disputeManager;
private final Navigation navigation; private final Navigation navigation;
private final WalletPasswordPopup walletPasswordPopup; private final WalletPasswordPopup walletPasswordPopup;
private NotificationCenter notificationCenter;
final ObservableList<PendingTradesListItem> list = FXCollections.observableArrayList(); final ObservableList<PendingTradesListItem> list = FXCollections.observableArrayList();
private final ListChangeListener<Trade> tradesListChangeListener; private final ListChangeListener<Trade> tradesListChangeListener;
@ -79,6 +81,7 @@ public class PendingTradesDataModel extends ActivatableDataModel {
final ObjectProperty<PendingTradesListItem> selectedItemProperty = new SimpleObjectProperty<>(); final ObjectProperty<PendingTradesListItem> selectedItemProperty = new SimpleObjectProperty<>();
public final StringProperty txId = new SimpleStringProperty(); public final StringProperty txId = new SimpleStringProperty();
public final Preferences preferences; public final Preferences preferences;
private boolean activated;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -88,7 +91,7 @@ public class PendingTradesDataModel extends ActivatableDataModel {
@Inject @Inject
public PendingTradesDataModel(TradeManager tradeManager, WalletService walletService, TradeWalletService tradeWalletService, public PendingTradesDataModel(TradeManager tradeManager, WalletService walletService, TradeWalletService tradeWalletService,
User user, KeyRing keyRing, DisputeManager disputeManager, Preferences preferences, User user, KeyRing keyRing, DisputeManager disputeManager, Preferences preferences,
Navigation navigation, WalletPasswordPopup walletPasswordPopup) { Navigation navigation, WalletPasswordPopup walletPasswordPopup, NotificationCenter notificationCenter) {
this.tradeManager = tradeManager; this.tradeManager = tradeManager;
this.walletService = walletService; this.walletService = walletService;
this.tradeWalletService = tradeWalletService; this.tradeWalletService = tradeWalletService;
@ -98,19 +101,27 @@ public class PendingTradesDataModel extends ActivatableDataModel {
this.preferences = preferences; this.preferences = preferences;
this.navigation = navigation; this.navigation = navigation;
this.walletPasswordPopup = walletPasswordPopup; this.walletPasswordPopup = walletPasswordPopup;
this.notificationCenter = notificationCenter;
tradesListChangeListener = change -> onListChanged(); tradesListChangeListener = change -> onListChanged();
notificationCenter.setSelectItemByTradeIdConsumer(this::selectItemByTradeId);
} }
@Override @Override
protected void activate() { protected void activate() {
tradeManager.getTrades().addListener(tradesListChangeListener); tradeManager.getTrades().addListener(tradesListChangeListener);
onListChanged(); onListChanged();
if (selectedItemProperty.get() != null)
notificationCenter.setSelectedTradeId(selectedItemProperty.get().getTrade().getId());
activated = true;
} }
@Override @Override
protected void deactivate() { protected void deactivate() {
tradeManager.getTrades().removeListener(tradesListChangeListener); tradeManager.getTrades().removeListener(tradesListChangeListener);
notificationCenter.setSelectedTradeId(null);
activated = false;
} }
@ -254,12 +265,20 @@ public class PendingTradesDataModel extends ActivatableDataModel {
doSelectItem(null); doSelectItem(null);
} }
private void selectItemByTradeId(String tradeId) {
if (activated)
list.stream().filter(e -> e.getTrade().getId().equals(tradeId)).findAny().ifPresent(this::doSelectItem);
}
private void doSelectItem(PendingTradesListItem item) { private void doSelectItem(PendingTradesListItem item) {
if (item != null) { if (item != null) {
Trade trade = item.getTrade(); Trade trade = item.getTrade();
isOfferer = tradeManager.isMyOffer(trade.getOffer()); isOfferer = tradeManager.isMyOffer(trade.getOffer());
if (trade.getDepositTx() != null) if (trade.getDepositTx() != null)
txId.set(trade.getDepositTx().getHashAsString()); txId.set(trade.getDepositTx().getHashAsString());
notificationCenter.setSelectedTradeId(trade.getId());
} else {
notificationCenter.setSelectedTradeId(null);
} }
selectedItemProperty.set(item); selectedItemProperty.set(item);
} }

View file

@ -45,10 +45,6 @@ public class PendingTradesListItem {
return trade.tradeVolumeProperty(); return trade.tradeVolumeProperty();
} }
public String getId() {
return trade.getShortId();
}
public Fiat getPrice() { public Fiat getPrice() {
return trade.getOffer().getPrice(); return trade.getOffer().getPrice();
} }

View file

@ -217,7 +217,7 @@ public class PendingTradesView extends ActivatableViewAndModel<VBox, PendingTrad
super.updateItem(item, empty); super.updateItem(item, empty);
if (item != null && !empty) { if (item != null && !empty) {
field = new HyperlinkWithIcon(item.getId(), true); field = new HyperlinkWithIcon(item.getTrade().getShortId(), true);
field.setOnAction(event -> tradeDetailsPopup.show(item.getTrade())); field.setOnAction(event -> tradeDetailsPopup.show(item.getTrade()));
field.setTooltip(new Tooltip("Open popup for details")); field.setTooltip(new Tooltip("Open popup for details"));
setGraphic(field); setGraphic(field);

View file

@ -112,9 +112,8 @@ public class BuyerStep5View extends TradeStepView {
() -> { () -> {
String id = "TradeCompletedInfoPopup"; String id = "TradeCompletedInfoPopup";
if (preferences.showAgain(id)) { if (preferences.showAgain(id)) {
new Popup() new Popup().information("You can review your completed trades under \"Portfolio/History\" or " +
.information("You can review your completed trades under \"Portfolio/History\" or " + "review your transactions under \"Funds/Transactions\"")
"review your transactions under \"Funds/Transactions\"")
.dontShowAgainId(id, preferences) .dontShowAgainId(id, preferences)
.show(); .show();
} }