From 39ca987e654933170ca5d0c47e624bc1ab0d2da3 Mon Sep 17 00:00:00 2001 From: danieskj Date: Fri, 15 May 2026 22:26:02 +0200 Subject: [PATCH 1/7] feat: Create percentage calculator for net worth increase or decrease --- .../edu/ntnu/idi/idatt/model/player/Player.java | 3 +++ .../elements/PlayerPortfolioComponent.java | 14 +++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/main/java/edu/ntnu/idi/idatt/model/player/Player.java b/src/main/java/edu/ntnu/idi/idatt/model/player/Player.java index 6f981a7..d0a21e2 100644 --- a/src/main/java/edu/ntnu/idi/idatt/model/player/Player.java +++ b/src/main/java/edu/ntnu/idi/idatt/model/player/Player.java @@ -89,5 +89,8 @@ public String getStatus() { return "Novice"; } + public BigDecimal getStartingMoney(){ + return startingMoney; + } } diff --git a/src/main/java/edu/ntnu/idi/idatt/view/components/elements/PlayerPortfolioComponent.java b/src/main/java/edu/ntnu/idi/idatt/view/components/elements/PlayerPortfolioComponent.java index 6744dd4..a4f218e 100644 --- a/src/main/java/edu/ntnu/idi/idatt/view/components/elements/PlayerPortfolioComponent.java +++ b/src/main/java/edu/ntnu/idi/idatt/view/components/elements/PlayerPortfolioComponent.java @@ -3,11 +3,15 @@ import edu.ntnu.idi.idatt.model.portfolio.Portfolio; import edu.ntnu.idi.idatt.session.UserSession; import edu.ntnu.idi.idatt.view.components.ui.UICompositor; +import edu.ntnu.idi.idatt.view.util.CssUtils; import javafx.geometry.Pos; import javafx.scene.control.Label; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; +import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; import java.util.ArrayList; import java.util.Collections; @@ -16,13 +20,19 @@ public class PlayerPortfolioComponent extends HBox { public PlayerPortfolioComponent(Portfolio portfolio) { this.setMaxSize(Double.MAX_VALUE, 500); this.getStyleClass().add("light"); + BigDecimal startingMoney = UserSession.getInstance().getPlayer().getStartingMoney(); + BigDecimal netMoney = UserSession.getInstance().getPlayer().getNetWorth(); + BigDecimal netPercentage = netMoney.divide(startingMoney) + .multiply(new BigDecimal("100")) + .setScale(2, RoundingMode.HALF_UP) + .subtract(new BigDecimal("100")); Label userTitle = new Label(String.format("%s's Portfolio", UserSession.getInstance().getPlayer().getName())); Label netWorth = new Label(); netWorth.textProperty().bind( UserSession.getInstance().netWorthProperty().asString("Net Worth: %.2f $")); Label percentageChange = new Label( - "Percentage change: " + UserSession.getInstance().netWorthProperty().get()); + "Percentage change: " + netPercentage + "%"); Label playerStatus = new Label("Player status: " + UserSession.getInstance().getPlayer().getStatus()); Label totalShares = new Label( "Total shares owned: " + UserSession.getInstance().getPlayer().getPortfolio().getShares().size()); @@ -30,6 +40,8 @@ public PlayerPortfolioComponent(Portfolio portfolio) { Collections.addAll(labels, netWorth, percentageChange, playerStatus, totalShares); labels.forEach(e -> e.getStyleClass().add("portfolio-box-text")); userTitle.getStyleClass().add("portfolio-box-title"); + String color = CssUtils.generateValueColors(netPercentage); + CssUtils.apply(percentageChange, color); UICompositor playerPortfolioComponent = new UICompositor.Builder() .parent(new HBox()) From f94ea8f0b0217e0d6194ce991d25fd36aab4b083 Mon Sep 17 00:00:00 2001 From: pawelsa Date: Sat, 16 May 2026 17:06:16 +0200 Subject: [PATCH 2/7] fix(Portfolio): CalculateTotal -> CalculateGross while checking profits. --- .../java/edu/ntnu/idi/idatt/model/portfolio/Portfolio.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/main/java/edu/ntnu/idi/idatt/model/portfolio/Portfolio.java b/src/main/java/edu/ntnu/idi/idatt/model/portfolio/Portfolio.java index 78a591b..4018da2 100644 --- a/src/main/java/edu/ntnu/idi/idatt/model/portfolio/Portfolio.java +++ b/src/main/java/edu/ntnu/idi/idatt/model/portfolio/Portfolio.java @@ -74,12 +74,7 @@ public BigDecimal getOwnedAmount(String symbol) { } public BigDecimal getProfitFromStock(String symbol) { - return getShares(symbol).stream().map(s -> { - BigDecimal total = new SaleCalculator(s).calculateTotal(); - BigDecimal buyPrice = s.getQuantity().multiply(s.getPurchasePrice()); - - return total.subtract(buyPrice); - }).reduce(BigDecimal.ZERO, BigDecimal::add); + return getShares(symbol).stream().map(s -> s.getProfit()).reduce(BigDecimal.ZERO, BigDecimal::add); } public BigDecimal getChangeFromStock(String symbol) { From a88f6d6ab9c671e2b8a045c1a6a6052f32ca962f Mon Sep 17 00:00:00 2001 From: pawelsa Date: Sat, 16 May 2026 17:07:07 +0200 Subject: [PATCH 3/7] Refactor(Stock MVC): Split code into sections (view (section), viewmodel) --- .../view/primary/stock/StockController.java | 50 +++++----- .../idatt/view/primary/stock/StockModel.java | 74 ++------------- .../idatt/view/primary/stock/StockView.java | 93 +++++++------------ .../stock/sections/PriceInfoSection.java | 38 ++++++++ .../primary/stock/sections/TradeSection.java | 63 +++++++++++++ .../stock/viewmodel/PriceInfoModel.java | 46 +++++++++ .../primary/stock/viewmodel/TradeModel.java | 38 ++++++++ 7 files changed, 250 insertions(+), 152 deletions(-) create mode 100644 src/main/java/edu/ntnu/idi/idatt/view/primary/stock/sections/PriceInfoSection.java create mode 100644 src/main/java/edu/ntnu/idi/idatt/view/primary/stock/sections/TradeSection.java create mode 100644 src/main/java/edu/ntnu/idi/idatt/view/primary/stock/viewmodel/PriceInfoModel.java create mode 100644 src/main/java/edu/ntnu/idi/idatt/view/primary/stock/viewmodel/TradeModel.java diff --git a/src/main/java/edu/ntnu/idi/idatt/view/primary/stock/StockController.java b/src/main/java/edu/ntnu/idi/idatt/view/primary/stock/StockController.java index 0d80656..fbda060 100644 --- a/src/main/java/edu/ntnu/idi/idatt/view/primary/stock/StockController.java +++ b/src/main/java/edu/ntnu/idi/idatt/view/primary/stock/StockController.java @@ -73,30 +73,30 @@ public void renderGraph() { } private void resetDisplayBuffers() { - model.getBuyPrice().set("0 $"); - model.getBuyCost().set("0 $"); - model.getTotalPrice().set("0 $"); - model.getResultMessage().set(""); - model.getResultMessageColorProperty().set(CssUtils.RED); + model.tradeModel().getBuyPrice().set("0 $"); + model.tradeModel().getBuyCost().set("0 $"); + model.tradeModel().getTotalPrice().set("0 $"); + model.tradeModel().getResultMessage().set(""); + model.tradeModel().getResultMessageColorProperty().set(CssUtils.RED); } private void initHooks() { resetDisplayBuffers(); - model.getBuyInputField().addListener((obervable, oldVal, newVal) -> displayBuyInfo(newVal)); + model.tradeModel().getBuyInputField().addListener((obervable, oldVal, newVal) -> displayBuyInfo(newVal)); } public void buyButtonClicked() { if (share == null || purchaseCalculator == null) { resetDisplayBuffers(); // Flush after color change and new press. - model.getResultMessage().set("Invalid purchase."); + model.tradeModel().getResultMessage().set("Invalid purchase."); return; } BigDecimal purchase = session.getPlayer().getMoney().subtract(purchaseCalculator.calculateTotal()); if (purchase.compareTo(BigDecimal.ZERO) <= 0) { // Flush after color change and new press, while still calculating everything. - model.getResultMessageColorProperty().set(CssUtils.RED); - model.getResultMessage().set("Balance too low."); + model.tradeModel().getResultMessageColorProperty().set(CssUtils.RED); + model.tradeModel().getResultMessage().set("Balance too low."); return; } @@ -108,16 +108,16 @@ public void buyButtonClicked() { boolean result = buyConfirmation.displayAwaitResponse(); if (!result) { - model.getResultMessageColorProperty().set(CssUtils.RED); - model.getResultMessage().set("Transaction canceled."); + model.tradeModel().getResultMessageColorProperty().set(CssUtils.RED); + model.tradeModel().getResultMessage().set("Transaction canceled."); return; } session.getExchange().buy(share.getStock().getSymbol(), share.getQuantity(), session.getPlayer()); this.setOwnedAmount(); this.setTotalProfits(); - model.getResultMessageColorProperty().set(CssUtils.GREEN); - model.getResultMessage().set("Purchase completed!"); + model.tradeModel().getResultMessageColorProperty().set(CssUtils.GREEN); + model.tradeModel().getResultMessage().set("Purchase completed!"); session.updateGameState(); } @@ -133,12 +133,12 @@ private void displayBuyInfo(String amountString) { try { value = new BigDecimal(amountString); } catch (NumberFormatException e) { - model.getResultMessage().set("Only numbers allowed!"); + model.tradeModel().getResultMessage().set("Only numbers allowed!"); return; } if (value.compareTo(BigDecimal.ZERO) <= 0) { - model.getResultMessage().set("Invalid amount!"); + model.tradeModel().getResultMessage().set("Invalid amount!"); return; } @@ -152,13 +152,13 @@ private void displayBuyInfo(String amountString) { formatter.format(purchaseCalculator.calculateCommision().add(purchaseCalculator.calculateTax()))); String total = String.format("%s $", formatter.format(purchaseCalculator.calculateTotal())); - model.getBuyPrice().set(gross); - model.getBuyCost().set(cost); - model.getTotalPrice().set(total); + model.tradeModel().getBuyPrice().set(gross); + model.tradeModel().getBuyCost().set(cost); + model.tradeModel().getTotalPrice().set(total); } public void setCurrentPrice() { - model.getStockPrice().set(String.format("%.2f $", this.stock.getSalesPrice())); + model.priceInfoModel().getStockPrice().set(String.format("%.2f $", this.stock.getSalesPrice())); } public void setLatestChange() { @@ -167,8 +167,8 @@ public void setLatestChange() { String format = String.format("%.2f $ ( %.2f %%)", change, changePercent); - model.getLatestChange().set(format); - model.getLatestChangeColorProperty().set(CssUtils.generateValueColors(change.doubleValue())); + model.priceInfoModel().getLatestChange().set(format); + model.priceInfoModel().getLatestChangeColorProperty().set(CssUtils.generateValueColors(change.doubleValue())); } public void setAllTimeScore() { @@ -177,7 +177,7 @@ public void setAllTimeScore() { String format = String.format("%.2f / %.2f", highest, lowest); - model.getAllTimeScore().set(format); + model.priceInfoModel().getAllTimeScore().set(format); } public void setOwnedAmount() { @@ -187,7 +187,7 @@ public void setOwnedAmount() { String format = String.format("%.2f [%s]", ownedAmount, symbol); - model.getOwnedAmount().set(format); + model.priceInfoModel().getOwnedAmount().set(format); } public void setTotalProfits() { @@ -198,7 +198,7 @@ public void setTotalProfits() { String format = String.format("%.2f $ (%.2f %%)", profit, profitPercent); - model.getTotalProfits().set(format); - model.getTotalProfitsColorProperty().set(CssUtils.generateValueColors(profit)); + model.priceInfoModel().getTotalProfits().set(format); + model.priceInfoModel().getTotalProfitsColorProperty().set(CssUtils.generateValueColors(profit)); } } diff --git a/src/main/java/edu/ntnu/idi/idatt/view/primary/stock/StockModel.java b/src/main/java/edu/ntnu/idi/idatt/view/primary/stock/StockModel.java index 8b5c6c8..6890812 100644 --- a/src/main/java/edu/ntnu/idi/idatt/view/primary/stock/StockModel.java +++ b/src/main/java/edu/ntnu/idi/idatt/view/primary/stock/StockModel.java @@ -1,7 +1,8 @@ package edu.ntnu.idi.idatt.view.primary.stock; import edu.ntnu.idi.idatt.view.components.Model; -import javafx.beans.property.SimpleStringProperty; +import edu.ntnu.idi.idatt.view.primary.stock.viewmodel.PriceInfoModel; +import edu.ntnu.idi.idatt.view.primary.stock.viewmodel.TradeModel; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.scene.Node; @@ -12,80 +13,21 @@ public class StockModel implements Model { private final ObservableList graphNodes = FXCollections.observableArrayList(); // Price properties - private final SimpleStringProperty stockPrice = new SimpleStringProperty(); - - private final SimpleStringProperty latestChange = new SimpleStringProperty(); - private final SimpleStringProperty latestChangeColorProperty = new SimpleStringProperty(); - - private final SimpleStringProperty allTimeScore = new SimpleStringProperty(); - - private final SimpleStringProperty ownedAmount = new SimpleStringProperty(); - - private final SimpleStringProperty totalProfits = new SimpleStringProperty(); - private final SimpleStringProperty totalProfitsColorProperty = new SimpleStringProperty(); + private final PriceInfoModel priceInfoModel = new PriceInfoModel(); // Trade properties - private final SimpleStringProperty buyInputField = new SimpleStringProperty(); - private final SimpleStringProperty resultMessage = new SimpleStringProperty(); - private final SimpleStringProperty resultMessageColorProperty = new SimpleStringProperty(); - private final SimpleStringProperty buyPrice = new SimpleStringProperty(); - private final SimpleStringProperty buyCost = new SimpleStringProperty(); - private final SimpleStringProperty totalPrice = new SimpleStringProperty(); + private final TradeModel tradeModel = new TradeModel(); public ObservableList getGraphNodes() { return this.graphNodes; } - public SimpleStringProperty getStockPrice() { - return stockPrice; - } - - public SimpleStringProperty getLatestChange() { - return latestChange; - } - - public SimpleStringProperty getLatestChangeColorProperty() { - return latestChangeColorProperty; - } - - public SimpleStringProperty getAllTimeScore() { - return allTimeScore; - } - - public SimpleStringProperty getOwnedAmount() { - return ownedAmount; - } - - public SimpleStringProperty getTotalProfits() { - return totalProfits; - } - - public SimpleStringProperty getTotalProfitsColorProperty() { - return totalProfitsColorProperty; - } - - public SimpleStringProperty getBuyInputField() { - return buyInputField; - } - - public SimpleStringProperty getResultMessage() { - return resultMessage; - } - - public SimpleStringProperty getResultMessageColorProperty() { - return resultMessageColorProperty; - } - - public SimpleStringProperty getBuyPrice() { - return buyPrice; - } - - public SimpleStringProperty getBuyCost() { - return buyCost; + public PriceInfoModel priceInfoModel() { + return priceInfoModel; } - public SimpleStringProperty getTotalPrice() { - return totalPrice; + public TradeModel tradeModel() { + return tradeModel; } } diff --git a/src/main/java/edu/ntnu/idi/idatt/view/primary/stock/StockView.java b/src/main/java/edu/ntnu/idi/idatt/view/primary/stock/StockView.java index 60a9c66..fe2da56 100644 --- a/src/main/java/edu/ntnu/idi/idatt/view/primary/stock/StockView.java +++ b/src/main/java/edu/ntnu/idi/idatt/view/primary/stock/StockView.java @@ -4,17 +4,18 @@ import edu.ntnu.idi.idatt.view.SceneFactory; import edu.ntnu.idi.idatt.view.SceneManager; import edu.ntnu.idi.idatt.view.components.AbstractViewUI; -import edu.ntnu.idi.idatt.view.components.elements.TextValueComponent; import edu.ntnu.idi.idatt.view.components.ui.UICompositor; import edu.ntnu.idi.idatt.view.components.ui.UIFactory; +import edu.ntnu.idi.idatt.view.primary.stock.sections.PriceInfoSection; +import edu.ntnu.idi.idatt.view.primary.stock.sections.TradeSection; +import edu.ntnu.idi.idatt.view.primary.stock.viewmodel.PriceInfoModel; +import edu.ntnu.idi.idatt.view.primary.stock.viewmodel.TradeModel; import edu.ntnu.idi.idatt.view.util.CssUtils; import javafx.beans.binding.Bindings; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Parent; -import javafx.scene.control.Button; import javafx.scene.control.Label; -import javafx.scene.control.TextField; import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; import javafx.scene.layout.VBox; @@ -25,20 +26,10 @@ public class StockView extends AbstractViewUI { private Label title; // Prices - TextValueComponent stockPrice; - TextValueComponent latestChange; - TextValueComponent allTimeScore; - TextValueComponent ownedAmount; - TextValueComponent totalProfits; + private PriceInfoSection priceInfoSection; // Trade menu - Button buyButton; - Button portfolioButton; - TextField buyInputField; - TextValueComponent buyPrice; - TextValueComponent buyCost; - TextValueComponent totalPrice; - TextValueComponent resultMessage; + private TradeSection tradeSection; @Override public Parent createContent() { @@ -57,40 +48,11 @@ public Parent createContent() { .parent(new HBox()) .growWithAlignment(Pos.TOP_CENTER) .properties((parent) -> parent.setPadding(new Insets(20.0))) - - .wrap(new VBox()) - .addContent(stockPrice = new TextValueComponent("Current price: ")) - .addContent(latestChange = new TextValueComponent("Latest Price Change: ")) - .addContent(allTimeScore = new TextValueComponent("All time H/L: ")) - .addContent(ownedAmount = new TextValueComponent("Owned amount: ")) - .addContent(totalProfits = new TextValueComponent("Total profits: ")) - .unwrap() - + .addContent(priceInfoSection = new PriceInfoSection()) .filler() - - .wrap(new VBox()) - - .wrap(new HBox()) - .addContent(buyButton = new Button("Buy")) - .addContent(portfolioButton = new Button("Sell in portfolio...")) - .properties((wrapper) -> ((HBox) wrapper).setSpacing(20.0)) - .unwrap() - - .wrap(new VBox()) - .properties((wrapper) -> wrapper.setPadding(new Insets(20))) - .properties((wrapper) -> wrapper.setMaxWidth(400)) - .addContent(buyInputField = new TextField()) - .addContent(buyPrice = new TextValueComponent("Price: ")) - .addContent(buyCost = new TextValueComponent("Taxes & commision: ")) - .addContent(totalPrice = new TextValueComponent("Total: ")) - .unwrap() - .addContent(resultMessage = new TextValueComponent("")) - .unwrap() + .addContent(tradeSection = new TradeSection()) .build(); - // Detailing - buyInputField.setPromptText("Amount of stocks..."); - root.getChildren().addAll(graphContainer, userSection.makeUI()); return root; } @@ -127,31 +89,40 @@ public void setModel(StockModel model) { Bindings.bindContent(graphContainer.getChildren(), model.getGraphNodes()); // Price display bindings - stockPrice.valueProperty().bind(model.getStockPrice()); + setPriceInfoSectionModel(model.priceInfoModel()); - latestChange.valueProperty().bind(model.getLatestChange()); - latestChange.colorProperty().bind(model.getLatestChangeColorProperty()); + // Trade section bindings + setTradeSectionModel(model.tradeModel()); - allTimeScore.valueProperty().bind(model.getAllTimeScore()); + } - ownedAmount.valueProperty().bind(model.getOwnedAmount()); + public void setPriceInfoSectionModel(PriceInfoModel model) { + priceInfoSection.getStockPrice().valueProperty().bind(model.getStockPrice()); - totalProfits.valueProperty().bind(model.getTotalProfits()); - totalProfits.colorProperty().bind(model.getTotalProfitsColorProperty()); + priceInfoSection.getLatestChange().valueProperty().bind(model.getLatestChange()); + priceInfoSection.getLatestChange().colorProperty().bind(model.getLatestChangeColorProperty()); + + priceInfoSection.getAllTimeScore().valueProperty().bind(model.getAllTimeScore()); + + priceInfoSection.getOwnedAmount().valueProperty().bind(model.getOwnedAmount()); + + priceInfoSection.getTotalProfits().valueProperty().bind(model.getTotalProfits()); + priceInfoSection.getTotalProfits().colorProperty().bind(model.getTotalProfitsColorProperty()); + } - // Trade menu bindings - buyInputField.textProperty().bindBidirectional(model.getBuyInputField()); - buyPrice.valueProperty().bind(model.getBuyPrice()); - buyCost.valueProperty().bind(model.getBuyCost()); - totalPrice.valueProperty().bind(model.getTotalPrice()); - resultMessage.valueProperty().bind(model.getResultMessage()); - resultMessage.colorProperty().bind(model.getResultMessageColorProperty()); + public void setTradeSectionModel(TradeModel model) { + tradeSection.getBuyInputField().textProperty().bindBidirectional(model.getBuyInputField()); + tradeSection.getBuyPrice().valueProperty().bind(model.getBuyPrice()); + tradeSection.getBuyCost().valueProperty().bind(model.getBuyCost()); + tradeSection.getTotalPrice().valueProperty().bind(model.getTotalPrice()); + tradeSection.getResultMessage().valueProperty().bind(model.getResultMessage()); + tradeSection.getResultMessage().colorProperty().bind(model.getResultMessageColorProperty()); } public void setController(StockController controller) { title.setText(controller.getSymbol()); - buyButton.setOnAction((e) -> controller.buyButtonClicked()); + tradeSection.getBuyButton().setOnAction((e) -> controller.buyButtonClicked()); } } diff --git a/src/main/java/edu/ntnu/idi/idatt/view/primary/stock/sections/PriceInfoSection.java b/src/main/java/edu/ntnu/idi/idatt/view/primary/stock/sections/PriceInfoSection.java new file mode 100644 index 0000000..d375fde --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt/view/primary/stock/sections/PriceInfoSection.java @@ -0,0 +1,38 @@ +package edu.ntnu.idi.idatt.view.primary.stock.sections; + +import edu.ntnu.idi.idatt.view.components.elements.TextValueComponent; +import javafx.scene.layout.VBox; + +public class PriceInfoSection extends VBox { + + TextValueComponent stockPrice = new TextValueComponent("Current price: "); + TextValueComponent latestChange = new TextValueComponent("Latest Price Change: "); + TextValueComponent allTimeScore = new TextValueComponent("All time H/L: "); + TextValueComponent ownedAmount = new TextValueComponent("Owned amount: "); + TextValueComponent totalProfits = new TextValueComponent("Total profits: "); + + public PriceInfoSection() { + this.getChildren().addAll(stockPrice, latestChange, allTimeScore, ownedAmount, totalProfits); + } + + public TextValueComponent getStockPrice() { + return stockPrice; + } + + public TextValueComponent getLatestChange() { + return latestChange; + } + + public TextValueComponent getAllTimeScore() { + return allTimeScore; + } + + public TextValueComponent getOwnedAmount() { + return ownedAmount; + } + + public TextValueComponent getTotalProfits() { + return totalProfits; + } + +} diff --git a/src/main/java/edu/ntnu/idi/idatt/view/primary/stock/sections/TradeSection.java b/src/main/java/edu/ntnu/idi/idatt/view/primary/stock/sections/TradeSection.java new file mode 100644 index 0000000..19a599d --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt/view/primary/stock/sections/TradeSection.java @@ -0,0 +1,63 @@ +package edu.ntnu.idi.idatt.view.primary.stock.sections; + +import edu.ntnu.idi.idatt.view.components.elements.TextValueComponent; +import javafx.geometry.Insets; +import javafx.scene.control.Button; +import javafx.scene.control.TextField; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; + +public class TradeSection extends VBox { + + Button buyButton = new Button("Buy"); + Button portfolioButton = new Button("Sell in portfolio..."); + TextField buyInputField = new TextField(); + TextValueComponent buyPrice = new TextValueComponent("Price: "); + TextValueComponent buyCost = new TextValueComponent("Taxes & Comission: "); + TextValueComponent totalPrice = new TextValueComponent("Total: "); + TextValueComponent resultMessage = new TextValueComponent(""); + + public TradeSection() { + + HBox wrapper = new HBox(); + wrapper.setSpacing(20.0); + wrapper.getChildren().addAll(buyButton, portfolioButton); + + // Detailing + buyInputField.setPromptText("Amount of stocks..."); + + this.setPadding(new Insets(20)); + this.setMaxWidth(400); + this.getChildren().addAll(wrapper, buyInputField, buyPrice, buyCost, totalPrice, resultMessage); + + } + + public Button getBuyButton() { + return buyButton; + } + + public Button getPortfolioButton() { + return portfolioButton; + } + + public TextField getBuyInputField() { + return buyInputField; + } + + public TextValueComponent getBuyPrice() { + return buyPrice; + } + + public TextValueComponent getBuyCost() { + return buyCost; + } + + public TextValueComponent getTotalPrice() { + return totalPrice; + } + + public TextValueComponent getResultMessage() { + return resultMessage; + } + +} diff --git a/src/main/java/edu/ntnu/idi/idatt/view/primary/stock/viewmodel/PriceInfoModel.java b/src/main/java/edu/ntnu/idi/idatt/view/primary/stock/viewmodel/PriceInfoModel.java new file mode 100644 index 0000000..67c4599 --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt/view/primary/stock/viewmodel/PriceInfoModel.java @@ -0,0 +1,46 @@ +package edu.ntnu.idi.idatt.view.primary.stock.viewmodel; + +import javafx.beans.property.SimpleStringProperty; + +public class PriceInfoModel { + private final SimpleStringProperty stockPrice = new SimpleStringProperty(); + + private final SimpleStringProperty latestChange = new SimpleStringProperty(); + private final SimpleStringProperty latestChangeColorProperty = new SimpleStringProperty(); + + private final SimpleStringProperty allTimeScore = new SimpleStringProperty(); + + private final SimpleStringProperty ownedAmount = new SimpleStringProperty(); + + private final SimpleStringProperty totalProfits = new SimpleStringProperty(); + private final SimpleStringProperty totalProfitsColorProperty = new SimpleStringProperty(); + + public SimpleStringProperty getStockPrice() { + return stockPrice; + } + + public SimpleStringProperty getLatestChange() { + return latestChange; + } + + public SimpleStringProperty getLatestChangeColorProperty() { + return latestChangeColorProperty; + } + + public SimpleStringProperty getAllTimeScore() { + return allTimeScore; + } + + public SimpleStringProperty getOwnedAmount() { + return ownedAmount; + } + + public SimpleStringProperty getTotalProfits() { + return totalProfits; + } + + public SimpleStringProperty getTotalProfitsColorProperty() { + return totalProfitsColorProperty; + } + +} diff --git a/src/main/java/edu/ntnu/idi/idatt/view/primary/stock/viewmodel/TradeModel.java b/src/main/java/edu/ntnu/idi/idatt/view/primary/stock/viewmodel/TradeModel.java new file mode 100644 index 0000000..4c55bf7 --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt/view/primary/stock/viewmodel/TradeModel.java @@ -0,0 +1,38 @@ +package edu.ntnu.idi.idatt.view.primary.stock.viewmodel; + +import javafx.beans.property.SimpleStringProperty; + +public class TradeModel { + + private final SimpleStringProperty buyInputField = new SimpleStringProperty(); + private final SimpleStringProperty resultMessage = new SimpleStringProperty(); + private final SimpleStringProperty resultMessageColorProperty = new SimpleStringProperty(); + private final SimpleStringProperty buyPrice = new SimpleStringProperty(); + private final SimpleStringProperty buyCost = new SimpleStringProperty(); + private final SimpleStringProperty totalPrice = new SimpleStringProperty(); + + public SimpleStringProperty getBuyInputField() { + return buyInputField; + } + + public SimpleStringProperty getResultMessage() { + return resultMessage; + } + + public SimpleStringProperty getResultMessageColorProperty() { + return resultMessageColorProperty; + } + + public SimpleStringProperty getBuyPrice() { + return buyPrice; + } + + public SimpleStringProperty getBuyCost() { + return buyCost; + } + + public SimpleStringProperty getTotalPrice() { + return totalPrice; + } + +} From b26bdbbdb8251794fe20c791d17c51e9ef5a02c2 Mon Sep 17 00:00:00 2001 From: pawelsa Date: Sat, 16 May 2026 19:54:53 +0200 Subject: [PATCH 4/7] refactor: Math corrections in game model. --- .../idi/idatt/model/portfolio/Portfolio.java | 22 ++++++++++++++++++- .../ntnu/idi/idatt/model/portfolio/Share.java | 12 ++++++++-- .../service/transaction/SaleCalculator.java | 3 ++- 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/src/main/java/edu/ntnu/idi/idatt/model/portfolio/Portfolio.java b/src/main/java/edu/ntnu/idi/idatt/model/portfolio/Portfolio.java index 4018da2..c255340 100644 --- a/src/main/java/edu/ntnu/idi/idatt/model/portfolio/Portfolio.java +++ b/src/main/java/edu/ntnu/idi/idatt/model/portfolio/Portfolio.java @@ -73,13 +73,33 @@ public BigDecimal getOwnedAmount(String symbol) { .reduce(BigDecimal.ZERO, BigDecimal::add); } + public BigDecimal getOwnedAmount() { + return getShares().stream().map(s -> s.getQuantity()) + .reduce(BigDecimal.ZERO, BigDecimal::add); + } + public BigDecimal getProfitFromStock(String symbol) { return getShares(symbol).stream().map(s -> s.getProfit()).reduce(BigDecimal.ZERO, BigDecimal::add); } + public BigDecimal getProfitFromStock() { + return getShares().stream().map(s -> s.getProfit()).reduce(BigDecimal.ZERO, BigDecimal::add); + } + public BigDecimal getChangeFromStock(String symbol) { BigDecimal profitTotal = getProfitFromStock(symbol); - BigDecimal costTotal = getShares(symbol).stream().map(s -> s.getPurchasePrice().multiply(s.getQuantity())) + BigDecimal costTotal = getShares(symbol).stream().map(s -> s.getTotalPurchasePrice()) + .reduce(BigDecimal.ZERO, BigDecimal::add); + + if (costTotal.compareTo(BigDecimal.ZERO) <= 0) + return BigDecimal.ZERO; + + return profitTotal.divide(costTotal, 2, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100)); + } + + public BigDecimal getChangeFromStock() { + BigDecimal profitTotal = getProfitFromStock(); + BigDecimal costTotal = getShares().stream().map(s -> s.getTotalPurchasePrice()) .reduce(BigDecimal.ZERO, BigDecimal::add); if (costTotal.compareTo(BigDecimal.ZERO) <= 0) diff --git a/src/main/java/edu/ntnu/idi/idatt/model/portfolio/Share.java b/src/main/java/edu/ntnu/idi/idatt/model/portfolio/Share.java index a00a65f..186cb43 100644 --- a/src/main/java/edu/ntnu/idi/idatt/model/portfolio/Share.java +++ b/src/main/java/edu/ntnu/idi/idatt/model/portfolio/Share.java @@ -1,6 +1,7 @@ package edu.ntnu.idi.idatt.model.portfolio; import java.math.BigDecimal; +import java.math.RoundingMode; import edu.ntnu.idi.idatt.model.market.Stock; import edu.ntnu.idi.idatt.service.transaction.SaleCalculator; @@ -52,9 +53,16 @@ public BigDecimal getPurchasePrice() { } // TODO: JAVADOCS, JUNIT + public BigDecimal getTotalPurchasePrice() { + return purchasePrice.multiply(quantity); + } + public BigDecimal getProfit() { - BigDecimal totalCost = purchasePrice.multiply(quantity); - return new SaleCalculator(this).calculateGross().subtract(totalCost); + return new SaleCalculator(this).calculateGross().subtract(getTotalPurchasePrice()); + } + + public BigDecimal getProfitPercent() { + return getProfit().divide(getTotalPurchasePrice(), 2, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100)); } } diff --git a/src/main/java/edu/ntnu/idi/idatt/service/transaction/SaleCalculator.java b/src/main/java/edu/ntnu/idi/idatt/service/transaction/SaleCalculator.java index 102f1a5..47819d5 100644 --- a/src/main/java/edu/ntnu/idi/idatt/service/transaction/SaleCalculator.java +++ b/src/main/java/edu/ntnu/idi/idatt/service/transaction/SaleCalculator.java @@ -3,6 +3,7 @@ import edu.ntnu.idi.idatt.model.portfolio.Share; import java.math.BigDecimal; +import java.math.RoundingMode; /** * SaleCalculator class @@ -78,7 +79,7 @@ public BigDecimal calculateTotal() { // TODO: Javadocs, junit public BigDecimal calculateProfit() { - return calculateTotal().divide(purchasePrice.multiply(quantity)); + return calculateGross().subtract(purchasePrice.multiply(quantity)); } } From 6074d5892e79f3f65951c540ded7fb75430c947a Mon Sep 17 00:00:00 2001 From: pawelsa Date: Sat, 16 May 2026 19:56:09 +0200 Subject: [PATCH 5/7] chore: Update TransacitionComponent based on math changes. --- .../elements/TransactionComponent.java | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/main/java/edu/ntnu/idi/idatt/view/components/elements/TransactionComponent.java b/src/main/java/edu/ntnu/idi/idatt/view/components/elements/TransactionComponent.java index 26fd7d1..4939ebd 100644 --- a/src/main/java/edu/ntnu/idi/idatt/view/components/elements/TransactionComponent.java +++ b/src/main/java/edu/ntnu/idi/idatt/view/components/elements/TransactionComponent.java @@ -6,6 +6,7 @@ import edu.ntnu.idi.idatt.model.transaction.Purchase; import edu.ntnu.idi.idatt.model.transaction.Sale; import edu.ntnu.idi.idatt.model.transaction.Transaction; +import edu.ntnu.idi.idatt.service.transaction.PurchaseCalculator; import edu.ntnu.idi.idatt.service.transaction.SaleCalculator; import edu.ntnu.idi.idatt.service.transaction.TransactionCalculator; import edu.ntnu.idi.idatt.view.components.ui.UICompositor; @@ -20,7 +21,6 @@ public class TransactionComponent extends VBox { public TransactionComponent(Transaction transaction) { Stock stock = transaction.getShare().getStock(); - TransactionCalculator calculator = transaction.getCalculator(); Label title; Label name = new Label(stock.toString()); @@ -30,18 +30,20 @@ public TransactionComponent(Transaction transaction) { Label amountOfShares; Label saleProfit; - totalValue = new Label(String.format("Total: %.2f USD", calculator.calculateTotal())); - - taxComissionValue = new Label(String.format( - "Tax & Comission: %.2f USD", calculator.calculateTax().add(calculator.calculateCommision()))); - - grossValue = new Label(String.format("Gross: %.2f USD", calculator.calculateGross())); - amountOfShares = new Label( String.format("Shares: %.2f [%s]", transaction.getShare().getQuantity(), stock.getSymbol())); if (transaction instanceof Purchase) { title = new Label("PURCHASE"); + PurchaseCalculator pCalc = (PurchaseCalculator) transaction.getCalculator(); + + totalValue = new Label(String.format("Total: %.2f USD", pCalc.calculateTotal())); + + taxComissionValue = new Label(String.format( + "Tax & Comission: %.2f USD", pCalc.calculateTax().add(pCalc.calculateCommision()))); + + grossValue = new Label(String.format("Gross: %.2f USD", pCalc.calculateGross())); + saleProfit = new Label(); CssUtils.apply(title, CssUtils.GREEN); @@ -50,9 +52,17 @@ public TransactionComponent(Transaction transaction) { CssUtils.apply(title, CssUtils.RED); SaleCalculator sCalc = (SaleCalculator) transaction.getCalculator(); + + totalValue = new Label(String.format("Total: %.2f USD", sCalc.calculateTotal())); + + taxComissionValue = new Label(String.format( + "Tax & Comission: %.2f USD", sCalc.calculateTax().add(sCalc.calculateCommision()))); + + grossValue = new Label(String.format("Gross: %.2f USD", sCalc.calculateGross())); + saleProfit = new Label(String.format("Profit: %.2f USD", sCalc.calculateProfit())); - CssUtils.apply(saleProfit, CssUtils.generateValueColors(sCalc.calculateProfit().doubleValue())); + CssUtils.apply(saleProfit, CssUtils.generateValueColors(sCalc.calculateProfit())); } else { From ffe69393ea6be680c6521df6062f2dcdf3759080 Mon Sep 17 00:00:00 2001 From: pawelsa Date: Sat, 16 May 2026 19:56:54 +0200 Subject: [PATCH 6/7] feat: Portfolio-view related classes refactored. --- .../elements/PlayerPortfolioComponent.java | 6 +- .../components/elements/ShareComponent.java | 83 +++++----- .../primary/portfolio/PortfolioModel.java | 23 +-- .../view/primary/portfolio/PortfolioView.java | 148 ++++++++++-------- .../portfolio/sections/PlayerInfoSection.java | 75 +++++++++ .../portfolio/viewmodel/PlayerInfoModel.java | 40 +++++ 6 files changed, 266 insertions(+), 109 deletions(-) create mode 100644 src/main/java/edu/ntnu/idi/idatt/view/primary/portfolio/sections/PlayerInfoSection.java create mode 100644 src/main/java/edu/ntnu/idi/idatt/view/primary/portfolio/viewmodel/PlayerInfoModel.java diff --git a/src/main/java/edu/ntnu/idi/idatt/view/components/elements/PlayerPortfolioComponent.java b/src/main/java/edu/ntnu/idi/idatt/view/components/elements/PlayerPortfolioComponent.java index a4f218e..5b15d78 100644 --- a/src/main/java/edu/ntnu/idi/idatt/view/components/elements/PlayerPortfolioComponent.java +++ b/src/main/java/edu/ntnu/idi/idatt/view/components/elements/PlayerPortfolioComponent.java @@ -23,9 +23,9 @@ public PlayerPortfolioComponent(Portfolio portfolio) { BigDecimal startingMoney = UserSession.getInstance().getPlayer().getStartingMoney(); BigDecimal netMoney = UserSession.getInstance().getPlayer().getNetWorth(); BigDecimal netPercentage = netMoney.divide(startingMoney) - .multiply(new BigDecimal("100")) - .setScale(2, RoundingMode.HALF_UP) - .subtract(new BigDecimal("100")); + .multiply(new BigDecimal("100")) + .setScale(2, RoundingMode.HALF_UP) + .subtract(new BigDecimal("100")); Label userTitle = new Label(String.format("%s's Portfolio", UserSession.getInstance().getPlayer().getName())); Label netWorth = new Label(); diff --git a/src/main/java/edu/ntnu/idi/idatt/view/components/elements/ShareComponent.java b/src/main/java/edu/ntnu/idi/idatt/view/components/elements/ShareComponent.java index 7546158..486b994 100644 --- a/src/main/java/edu/ntnu/idi/idatt/view/components/elements/ShareComponent.java +++ b/src/main/java/edu/ntnu/idi/idatt/view/components/elements/ShareComponent.java @@ -8,44 +8,57 @@ import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; -import java.util.ArrayList; -import java.util.Collections; - +import java.util.List; +import java.util.function.Consumer; public class ShareComponent extends HBox { - public ShareComponent(Share share){ - this.setMaxSize(Double.MAX_VALUE, 300); - this.setPadding(new Insets(30)); - this.getStyleClass().add("rowBox"); - - Label name = new Label(share.getStock().getCompany()); - Label quantity = new Label("Owned shares: "+share.getQuantity().toString()); - Label ticker = new Label("("+share.getStock().getSymbol()+")"); - Label latestPrice = new Label("Latest price: "+share.getStock().getSalesPrice().toString()); - Button sellButton = new Button("Sell"); - - ArrayList