diff --git a/core/src/main/java/bisq/core/user/Cookie.java b/core/src/main/java/bisq/core/user/Cookie.java new file mode 100644 index 0000000000..bc73d6bbd0 --- /dev/null +++ b/core/src/main/java/bisq/core/user/Cookie.java @@ -0,0 +1,63 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.user; + +import bisq.common.proto.ProtoUtil; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import javax.annotation.Nullable; + +/** + * Serves as flexible container for persisting UI states, layout,... + * Should not be over-used for domain specific data where type safety and data integrity is important. + */ +public class Cookie extends HashMap { + + public void putAsDouble(CookieKey key, double value) { + put(key, String.valueOf(value)); + } + + public Optional getAsOptionalDouble(CookieKey key) { + try { + return containsKey(key) ? + Optional.of(Double.parseDouble(get(key))) : + Optional.empty(); + } catch (Throwable t) { + return Optional.empty(); + } + } + + public Map toProtoMessage() { + Map protoMap = new HashMap<>(); + this.forEach((key, value) -> protoMap.put(key.name(), value)); + return protoMap; + } + + public static Cookie fromProto(@Nullable Map protoMap) { + Cookie cookie = new Cookie(); + if (protoMap != null) { + protoMap.forEach((key, value) -> cookie.put(ProtoUtil.enumFromProto(CookieKey.class, key), value)); + } + return cookie; + } + + +} diff --git a/core/src/main/java/bisq/core/user/CookieKey.java b/core/src/main/java/bisq/core/user/CookieKey.java new file mode 100644 index 0000000000..c8653e54bb --- /dev/null +++ b/core/src/main/java/bisq/core/user/CookieKey.java @@ -0,0 +1,26 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.core.user; + +// Used for persistence of Cookie. Entries must not be changes or removed. Only adding entries is permitted. +public enum CookieKey { + STAGE_X, + STAGE_Y, + STAGE_W, + STAGE_H +} diff --git a/core/src/main/java/bisq/core/user/User.java b/core/src/main/java/bisq/core/user/User.java index 2f8480e162..04edaf82b5 100644 --- a/core/src/main/java/bisq/core/user/User.java +++ b/core/src/main/java/bisq/core/user/User.java @@ -514,4 +514,8 @@ public class User implements PersistedDataHost { private boolean paymentAccountExists(PaymentAccount paymentAccount) { return getPaymentAccountsAsObservable().stream().anyMatch(e -> e.equals(paymentAccount)); } + + public Cookie getCookie() { + return userPayload.getCookie(); + } } diff --git a/core/src/main/java/bisq/core/user/UserPayload.java b/core/src/main/java/bisq/core/user/UserPayload.java index 212570a6f7..3f2a892f06 100644 --- a/core/src/main/java/bisq/core/user/UserPayload.java +++ b/core/src/main/java/bisq/core/user/UserPayload.java @@ -80,6 +80,11 @@ public class UserPayload implements PersistableEnvelope { @Nullable private List acceptedRefundAgents = new ArrayList<>(); + // Added at 1.5.3 + // Generic map for persisting various UI states. We keep values un-typed as string to + // provide sufficient flexibility. + private Cookie cookie = new Cookie(); + public UserPayload() { } @@ -118,6 +123,7 @@ public class UserPayload implements PersistableEnvelope { Optional.ofNullable(acceptedRefundAgents) .ifPresent(e -> builder.addAllAcceptedRefundAgents(ProtoUtil.collectionToProto(acceptedRefundAgents, message -> ((protobuf.StoragePayload) message).getRefundAgent()))); + Optional.ofNullable(cookie).ifPresent(e -> builder.putAllCookie(cookie.toProtoMessage())); return protobuf.PersistableEnvelope.newBuilder().setUserPayload(builder).build(); } @@ -147,7 +153,8 @@ public class UserPayload implements PersistableEnvelope { proto.hasRegisteredRefundAgent() ? RefundAgent.fromProto(proto.getRegisteredRefundAgent()) : null, proto.getAcceptedRefundAgentsList().isEmpty() ? new ArrayList<>() : new ArrayList<>(proto.getAcceptedRefundAgentsList().stream() .map(RefundAgent::fromProto) - .collect(Collectors.toList())) + .collect(Collectors.toList())), + Cookie.fromProto(proto.getCookieMap()) ); } } diff --git a/desktop/src/main/java/bisq/desktop/app/BisqApp.java b/desktop/src/main/java/bisq/desktop/app/BisqApp.java index d5c0ac46b2..9a54c68158 100644 --- a/desktop/src/main/java/bisq/desktop/app/BisqApp.java +++ b/desktop/src/main/java/bisq/desktop/app/BisqApp.java @@ -38,7 +38,10 @@ import bisq.core.dao.governance.voteresult.MissingDataRequestService; import bisq.core.locale.Res; import bisq.core.offer.OpenOffer; import bisq.core.offer.OpenOfferManager; +import bisq.core.user.Cookie; +import bisq.core.user.CookieKey; import bisq.core.user.Preferences; +import bisq.core.user.User; import bisq.common.app.DevEnv; import bisq.common.app.Log; @@ -65,6 +68,8 @@ import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; import javafx.scene.layout.StackPane; +import javafx.geometry.BoundingBox; + import java.awt.GraphicsEnvironment; import java.awt.Rectangle; @@ -102,6 +107,7 @@ public class BisqApp extends Application implements UncaughtExceptionHandler { private boolean popupOpened; private Scene scene; private boolean shutDownRequested; + private MainView mainView; public BisqApp() { shutDownHandler = this::stop; @@ -126,7 +132,7 @@ public class BisqApp extends Application implements UncaughtExceptionHandler { public void startApplication(Runnable onApplicationStartedHandler) { try { - MainView mainView = loadMainView(injector); + mainView = loadMainView(injector); mainView.setOnApplicationStartedHandler(onApplicationStartedHandler); scene = createAndConfigScene(mainView, injector); setupStage(scene); @@ -256,10 +262,47 @@ public class BisqApp extends Application implements UncaughtExceptionHandler { stage.setMinHeight(MIN_WINDOW_HEIGHT); stage.getIcons().add(ImageUtil.getApplicationIconImage()); + User user = injector.getInstance(User.class); + layoutStageFromPersistedData(stage, user); + addStageLayoutListeners(stage, user); + // make the UI visible stage.show(); } + private void layoutStageFromPersistedData(Stage stage, User user) { + Cookie cookie = user.getCookie(); + cookie.getAsOptionalDouble(CookieKey.STAGE_X).flatMap(x -> + cookie.getAsOptionalDouble(CookieKey.STAGE_Y).flatMap(y -> + cookie.getAsOptionalDouble(CookieKey.STAGE_W).flatMap(w -> + cookie.getAsOptionalDouble(CookieKey.STAGE_H).map(h -> new BoundingBox(x, y, w, h))))) + .ifPresent(stageBoundingBox -> { + stage.setX(stageBoundingBox.getMinX()); + stage.setY(stageBoundingBox.getMinY()); + stage.setWidth(stageBoundingBox.getWidth()); + stage.setHeight(stageBoundingBox.getHeight()); + }); + } + + private void addStageLayoutListeners(Stage stage, User user) { + stage.widthProperty().addListener((observable, oldValue, newValue) -> { + user.getCookie().putAsDouble(CookieKey.STAGE_W, (double) newValue); + user.requestPersistence(); + }); + stage.heightProperty().addListener((observable, oldValue, newValue) -> { + user.getCookie().putAsDouble(CookieKey.STAGE_H, (double) newValue); + user.requestPersistence(); + }); + stage.xProperty().addListener((observable, oldValue, newValue) -> { + user.getCookie().putAsDouble(CookieKey.STAGE_X, (double) newValue); + user.requestPersistence(); + }); + stage.yProperty().addListener((observable, oldValue, newValue) -> { + user.getCookie().putAsDouble(CookieKey.STAGE_Y, (double) newValue); + user.requestPersistence(); + }); + } + private MainView loadMainView(Injector injector) { CachingViewLoader viewLoader = injector.getInstance(CachingViewLoader.class); return (MainView) viewLoader.load(MainView.class); diff --git a/proto/src/main/proto/pb.proto b/proto/src/main/proto/pb.proto index c804938b74..66af295531 100644 --- a/proto/src/main/proto/pb.proto +++ b/proto/src/main/proto/pb.proto @@ -1643,6 +1643,7 @@ message UserPayload { repeated MarketAlertFilter market_alert_filters = 13; repeated RefundAgent accepted_refund_agents = 14; RefundAgent registered_refund_agent = 15; + map cookie = 16; } ///////////////////////////////////////////////////////////////////////////////////////////