diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/ViewControllers/DesktopViewController.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/ViewControllers/DesktopViewController.java index c2f2dfa..10115b0 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/ViewControllers/DesktopViewController.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/ViewControllers/DesktopViewController.java @@ -33,13 +33,9 @@ * Handles app button creation, resizing, click events, and drag-and-drop. */ public final class DesktopViewController { - /** Controller for popups. */ private final PopupController popupController; - /** View for the desktop. */ private final DesktopView desktopView; - /** Player model. */ private final Player player; - /** Time and weather controller. */ private final TimeAndWeatherController timeAndWeatherController; /** diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Apps/BankApp.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Apps/BankApp.java index 616ff9b..d991cbd 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Apps/BankApp.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Apps/BankApp.java @@ -1,5 +1,5 @@ package edu.ntnu.idi.idatt2003.gruppe42.View.Apps; - + import edu.ntnu.idi.idatt2003.gruppe42.Model.App; import edu.ntnu.idi.idatt2003.gruppe42.Model.Share; import edu.ntnu.idi.idatt2003.gruppe42.View.Popup; @@ -14,225 +14,109 @@ import javafx.scene.control.ListView; import javafx.scene.layout.ColumnConstraints; import javafx.scene.layout.GridPane; -import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; -import javafx.scene.layout.Region; import javafx.scene.layout.VBox; import javafx.scene.paint.Color; -import javafx.scene.text.Font; -import javafx.scene.text.FontWeight; /** - * A popup for the Bank app. + * Bank app popup showing portfolio summary and individual share holdings. */ public class BankApp extends Popup { + + private static final String POSITIVE_STYLE = "stock-price-positive"; + private static final String NEGATIVE_STYLE = "stock-price-negative"; + private final ListView portfolioList; private final Label netWorthLabel = new Label(); - private final Label unInvestedMoneyLabel = new Label(); - private final Label investedMoneyLabel = new Label(); - private final Label growthPercentageLabel = new Label(); + private final Label cashLabel = new Label(); + private final Label investedLabel = new Label(); + private final Label totalReturnLabel = new Label(); + private Consumer onShareSelected; - /** - * Constructs a new Bank popup. - */ public BankApp() { super(500, 550, 220, 100, App.BANK); - this.portfolioList = new ListView<>(); - this.portfolioList.setSelectionModel(null); - this.portfolioList.getStyleClass().add("portfolio-list"); - - portfolioList.setCellFactory(listView -> new ListCell() { - private final GridPane grid = new GridPane(); - private final Label symbolLabel = new Label(); - private final Label companyLabel = new Label(); - private final Label qLabel = new Label(); - private final Label avgPLabel = new Label(); - private final Label curPLabel = new Label(); - private final Label mktVLabel = new Label(); - private final Label gainL = new Label(); - private final Label gainPctL = new Label(); - - { - grid.setHgap(10); - grid.setAlignment(Pos.CENTER_LEFT); - grid.getStyleClass().add("stock-list-item"); - grid.setPadding(new Insets(10, 5, 10, 5)); - - ColumnConstraints col0 = new ColumnConstraints(); - col0.setPercentWidth(30); - ColumnConstraints col1 = new ColumnConstraints(); - col1.setPercentWidth(20); - ColumnConstraints col2 = new ColumnConstraints(); - col2.setPercentWidth(25); - ColumnConstraints col3 = new ColumnConstraints(); - col3.setPercentWidth(25); - grid.getColumnConstraints().addAll(col0, col1, col2, col3); - - VBox names = new VBox(2, symbolLabel, companyLabel); - symbolLabel.getStyleClass().add("stock-symbol"); - companyLabel.getStyleClass().add("stock-company"); - grid.add(names, 0, 0); - - VBox qtyBox = new VBox(2, qLabel, avgPLabel); - qtyBox.setAlignment(Pos.CENTER_LEFT); - qLabel.getStyleClass().add("bank-share-quantity"); - avgPLabel.getStyleClass().add("bank-share-avg-price"); - grid.add(qtyBox, 1, 0); - - VBox valBox = new VBox(2, curPLabel, mktVLabel); - valBox.setAlignment(Pos.CENTER_RIGHT); - curPLabel.getStyleClass().add("bank-share-current-price"); - mktVLabel.getStyleClass().add("bank-share-value"); - grid.add(valBox, 2, 0); - - VBox gainBox = new VBox(2, gainL, gainPctL); - gainBox.setAlignment(Pos.CENTER_RIGHT); - gainL.getStyleClass().add("bank-share-gain"); - gainPctL.getStyleClass().add("bank-share-gain"); - grid.add(gainBox, 3, 0); - } - @Override - protected void updateItem(final Share share, final boolean empty) { - super.updateItem(share, empty); - - if (empty || share == null) { - setGraphic(null); - return; - } - - symbolLabel.setText(share.getStock().getSymbol()); - companyLabel.setText(share.getStock().getCompany()); - - BigDecimal quantity = share.getQuantity(); - BigDecimal purchasePrice = share.getPurchasePrice(); - BigDecimal currentPrice = share.getStock().getSalesPrice(); - BigDecimal marketValue = currentPrice.multiply(quantity); - BigDecimal totalCost = purchasePrice.multiply(quantity); - BigDecimal gain = marketValue.subtract(totalCost); - BigDecimal gainPercent = BigDecimal.ZERO; - if (totalCost.compareTo(BigDecimal.ZERO) > 0) { - gainPercent = gain.multiply(new BigDecimal("100")) - .divide(totalCost, 2, RoundingMode.HALF_UP); - } - - qLabel.setText(quantity.stripTrailingZeros().toPlainString() + " Shares"); - avgPLabel.setText("Avg: $" + String.format("%,.2f", purchasePrice)); - curPLabel.setText("$" + String.format("%,.2f", currentPrice)); - mktVLabel.setText("$" + String.format("%,.2f", marketValue)); - - gainL.setText((gain.compareTo(BigDecimal.ZERO) >= 0 ? "+" : "") - + "$" + String.format("%,.2f", gain)); - gainPctL.setText((gain.compareTo(BigDecimal.ZERO) >= 0 ? "+" : "") - + String.format("%.2f", gainPercent) + "%"); - - gainL.getStyleClass().removeAll("stock-price-positive", "stock-price-negative"); - gainPctL.getStyleClass().removeAll("stock-price-positive", "stock-price-negative"); - - String gainClass = gain.compareTo(BigDecimal.ZERO) >= 0 - ? "stock-price-positive" : "stock-price-negative"; - gainL.getStyleClass().add(gainClass); - gainPctL.getStyleClass().add(gainClass); - - grid.setOnMouseClicked(event -> { - if (onShareSelected != null) { - onShareSelected.accept(share); - } - }); - - setGraphic(grid); - } - }); + portfolioList = new ListView<>(); + portfolioList.setSelectionModel(null); + portfolioList.getStyleClass().add("portfolio-list"); + portfolioList.setCellFactory(lv -> new ShareCell()); content.setPadding(new Insets(20)); content.setSpacing(20); - updateContent(); + buildLayout(); } - private void updateContent() { + private void buildLayout() { content.getChildren().clear(); + content.getChildren().addAll(buildSummaryCard(), buildPortfolioSection()); + } - VBox summaryBox = new VBox(20); - VBox.setVgrow(summaryBox, Priority.NEVER); - summaryBox.getStyleClass().add("bank-summary-card"); - - Label summaryTitle = new Label("Total Balance"); - summaryTitle.getStyleClass().add("bank-summary-title"); - + private VBox buildSummaryCard() { + Label title = new Label("Total Balance"); + title.getStyleClass().add("bank-summary-title"); netWorthLabel.getStyleClass().add("bank-summary-label-bold"); - VBox totalBalanceBox = new VBox(5, summaryTitle, netWorthLabel); - totalBalanceBox.setAlignment(Pos.CENTER_LEFT); + VBox balanceBox = new VBox(5, title, netWorthLabel); + balanceBox.setAlignment(Pos.CENTER_LEFT); + + cashLabel.getStyleClass().add("bank-summary-label-normal"); + investedLabel.getStyleClass().add("bank-summary-label-normal"); + totalReturnLabel.getStyleClass().add("bank-summary-label-normal"); GridPane grid = new GridPane(); grid.setHgap(40); grid.setVgap(15); + grid.add(summaryItem("Cash Available", cashLabel), 0, 0); + grid.add(summaryItem("Invested", investedLabel), 1, 0); + grid.add(summaryItem("Total Return", totalReturnLabel), 2, 0); - unInvestedMoneyLabel.getStyleClass().add("bank-summary-label-normal"); - investedMoneyLabel.getStyleClass().add("bank-summary-label-normal"); - growthPercentageLabel.getStyleClass().add("bank-summary-label-normal"); - - grid.add(createSummaryItem("Cash Available", unInvestedMoneyLabel), 0, 0); - grid.add(createSummaryItem("Invested", investedMoneyLabel), 1, 0); - grid.add(createSummaryItem("Total Return", growthPercentageLabel), 2, 0); + VBox card = new VBox(20, balanceBox, grid); + card.getStyleClass().add("bank-summary-card"); + return card; + } - summaryBox.getChildren().addAll(totalBalanceBox, grid); + private VBox buildPortfolioSection() { + Label title = new Label("Investment Portfolio"); + title.getStyleClass().add("bank-portfolio-title"); - VBox portfolioBox = new VBox(10); - portfolioBox.setPadding(new Insets(10, 0, 0, 0)); - Label portfolioTitle = new Label("Investment Portfolio"); - portfolioTitle.getStyleClass().add("bank-portfolio-title"); - VBox.setVgrow(portfolioBox, Priority.ALWAYS); - VBox.setVgrow(portfolioList, Priority.ALWAYS); portfolioList.setPrefHeight(300); portfolioList.setMinHeight(200); - portfolioBox.getChildren().addAll(portfolioTitle, portfolioList); + VBox.setVgrow(portfolioList, Priority.ALWAYS); - content.getChildren().addAll(summaryBox, portfolioBox); + VBox section = new VBox(10, title, portfolioList); + section.setPadding(new Insets(10, 0, 0, 0)); + VBox.setVgrow(section, Priority.ALWAYS); + return section; } - private VBox createSummaryItem(String title, Label valueLabel) { - VBox vbox = new VBox(5); - vbox.setAlignment(Pos.CENTER_LEFT); + private VBox summaryItem(String title, Label valueLabel) { Label titleLabel = new Label(title); titleLabel.getStyleClass().add("bank-summary-item-title"); - vbox.getChildren().addAll(titleLabel, valueLabel); - return vbox; + + VBox box = new VBox(5, titleLabel, valueLabel); + box.setAlignment(Pos.CENTER_LEFT); + return box; } /** - * Updates the financial status displayed in the bank app. + * Refreshes the summary labels with the latest financial figures. * - * @param netWorth total net worth - * @param unInvestedMoney available cash - * @param investedMoney value of shares - * @param startingMoney the starting money to calculate growth + * @param netWorth total net worth (cash + portfolio value) + * @param cash uninvested cash available + * @param invested current market value of shares held + * @param startingMoney initial balance used as the growth baseline */ - public void updateStatus( - final double netWorth, - final double unInvestedMoney, - final double investedMoney, - final double startingMoney - ) { + public void updateStatus(double netWorth, double cash, double invested, double startingMoney) { Platform.runLater(() -> { - netWorthLabel.setText("$" + String.format("%,.2f", netWorth)); - unInvestedMoneyLabel.setText("$" + String.format("%,.2f", unInvestedMoney)); - investedMoneyLabel.setText("$" + String.format("%,.2f", investedMoney)); - - double growth = 0; - if (startingMoney > 0) { - growth = ((netWorth - startingMoney) / startingMoney) * 100; - } else if (netWorth > 0) { - growth = 100; - } - - growthPercentageLabel.setText((growth >= 0 ? "+" : "") + String.format("%.2f", growth) + "%"); - if (growth >= 0) { - growthPercentageLabel.setTextFill(Color.LIGHTGREEN); - } else { - growthPercentageLabel.setTextFill(Color.web("#ff6b6b")); - } + netWorthLabel.setText(formatUsd(netWorth)); + cashLabel.setText(formatUsd(cash)); + investedLabel.setText(formatUsd(invested)); + + double growthPct = calculateGrowthPercent(netWorth, startingMoney); + String sign = growthPct >= 0 ? "+" : ""; + totalReturnLabel.setText(sign + String.format("%.2f", growthPct) + "%"); + totalReturnLabel.setTextFill(growthPct >= 0 ? Color.LIGHTGREEN : Color.web("#ff6b6b")); }); } @@ -240,14 +124,134 @@ public ListView getPortfolioList() { return portfolioList; } - /** - * Sets the action to perform when a share is selected. - * - * @param onShareSelected the action to perform - */ public void setOnShareSelected(Consumer onShareSelected) { this.onShareSelected = onShareSelected; } -} + private static double calculateGrowthPercent(double netWorth, double startingMoney) { + if (startingMoney > 0) return ((netWorth - startingMoney) / startingMoney) * 100; + if (netWorth > 0) return 100; + return 0; + } + + private static String formatUsd(double amount) { + return "$" + String.format("%,.2f", amount); + } + + /** + * List cell that renders a single share holding as a four-column grid row. + */ + private class ShareCell extends ListCell { + + private final GridPane grid = new GridPane(); + + // Column 0 — identity + private final Label symbolLabel = new Label(); + private final Label companyLabel = new Label(); + + // Column 1 — quantity & cost basis + private final Label quantityLabel = new Label(); + private final Label avgPriceLabel = new Label(); + + // Column 2 — current valuation + private final Label currentPriceLabel = new Label(); + private final Label marketValueLabel = new Label(); + + // Column 3 — gain / loss + private final Label gainLabel = new Label(); + private final Label gainPctLabel = new Label(); + + ShareCell() { + grid.setHgap(10); + grid.setAlignment(Pos.CENTER_LEFT); + grid.getStyleClass().add("stock-list-item"); + grid.setPadding(new Insets(10, 5, 10, 5)); + configureColumns(); + addCellContent(); + grid.setOnMouseClicked(e -> { if (onShareSelected != null) onShareSelected.accept(getItem()); }); + } + + private void configureColumns() { + for (int pct : new int[]{30, 20, 25, 25}) { + ColumnConstraints col = new ColumnConstraints(); + col.setPercentWidth(pct); + grid.getColumnConstraints().add(col); + } + } + + private void addCellContent() { + symbolLabel.getStyleClass().add("stock-symbol"); + companyLabel.getStyleClass().add("stock-company"); + grid.add(new VBox(2, symbolLabel, companyLabel), 0, 0); + + quantityLabel.getStyleClass().add("bank-share-quantity"); + avgPriceLabel.getStyleClass().add("bank-share-avg-price"); + grid.add(new VBox(2, quantityLabel, avgPriceLabel), 1, 0); + + currentPriceLabel.getStyleClass().add("bank-share-current-price"); + marketValueLabel.getStyleClass().add("bank-share-value"); + VBox valBox = new VBox(2, currentPriceLabel, marketValueLabel); + valBox.setAlignment(Pos.CENTER_RIGHT); + grid.add(valBox, 2, 0); + + gainLabel.getStyleClass().add("bank-share-gain"); + gainPctLabel.getStyleClass().add("bank-share-gain"); + VBox gainBox = new VBox(2, gainLabel, gainPctLabel); + gainBox.setAlignment(Pos.CENTER_RIGHT); + grid.add(gainBox, 3, 0); + } + + @Override + protected void updateItem(Share share, boolean empty) { + super.updateItem(share, empty); + + if (empty || share == null) { + setGraphic(null); + return; + } + populateLabels(share); + setGraphic(grid); + } + + private void populateLabels(Share share) { + BigDecimal qty = share.getQuantity(); + BigDecimal purchasePrice = share.getPurchasePrice(); + BigDecimal currentPrice = share.getStock().getSalesPrice(); + BigDecimal marketValue = currentPrice.multiply(qty); + BigDecimal totalCost = purchasePrice.multiply(qty); + BigDecimal gain = marketValue.subtract(totalCost); + BigDecimal gainPercent = computeGainPercent(gain, totalCost); + + symbolLabel.setText(share.getStock().getSymbol()); + companyLabel.setText(share.getStock().getCompany()); + + quantityLabel.setText(qty.stripTrailingZeros().toPlainString() + " Shares"); + avgPriceLabel.setText("Avg: $" + String.format("%,.2f", purchasePrice)); + + currentPriceLabel.setText("$" + String.format("%,.2f", currentPrice)); + marketValueLabel.setText("$" + String.format("%,.2f", marketValue)); + + String sign = gain.compareTo(BigDecimal.ZERO) >= 0 ? "+" : ""; + gainLabel.setText(sign + "$" + String.format("%,.2f", gain)); + gainPctLabel.setText(sign + String.format("%.2f", gainPercent) + "%"); + + applyGainStyle(gain.compareTo(BigDecimal.ZERO) >= 0); + } + + private BigDecimal computeGainPercent(BigDecimal gain, BigDecimal totalCost) { + if (totalCost.compareTo(BigDecimal.ZERO) <= 0) return BigDecimal.ZERO; + return gain.multiply(BigDecimal.valueOf(100)) + .divide(totalCost, 2, RoundingMode.HALF_UP); + } + + private void applyGainStyle(boolean positive) { + String add = positive ? POSITIVE_STYLE : NEGATIVE_STYLE; + String remove = positive ? NEGATIVE_STYLE : POSITIVE_STYLE; + gainLabel.getStyleClass().removeAll(remove); + gainPctLabel.getStyleClass().removeAll(remove); + gainLabel.getStyleClass().add(add); + gainPctLabel.getStyleClass().add(add); + } + } +} \ No newline at end of file diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Views/DesktopView.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Views/DesktopView.java index 7f167f8..a43361c 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Views/DesktopView.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Views/DesktopView.java @@ -93,7 +93,7 @@ public BorderPane getRoot() { } /** - * Creates the 6x4 grid with app buttons. + * Creates the 10x6 grid with app buttons. * * @return the grid pane. */