diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/AppControllers/BankAppController.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/AppControllers/BankAppController.java index 09ac5a7..01dd1ad 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/AppControllers/BankAppController.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/AppControllers/BankAppController.java @@ -2,6 +2,7 @@ import edu.ntnu.idi.idatt2003.gruppe42.Model.Player; import edu.ntnu.idi.idatt2003.gruppe42.View.Apps.BankApp; +import javafx.application.Platform; /** * Controller for the {@link BankApp}. @@ -19,16 +20,17 @@ public record BankAppController(BankApp bankApp, Player player) implements AppCo @Override public void nextTick() { - bankApp.updateStatus( - player.getNetWorth().doubleValue(), - player.getMoney().doubleValue(), - player.getPortfolio().getNetWorth().doubleValue(), - 0 - ); - System.out.println("[DEBUG] BankAppController.nextTick() - Updating Status"); + Platform.runLater(() -> { + bankApp.updateStatus( + player.getNetWorth().doubleValue(), + player.getMoney().doubleValue(), + player.getPortfolio().getNetWorth().doubleValue(), + player.getStartingMoney().doubleValue() + ); - bankApp.getPortfolioList().getItems().setAll( - player.getPortfolio().getShares() - ); + bankApp.getPortfolioList().getItems().setAll( + player.getPortfolio().getShares() + ); + }); } } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/AppControllers/StockAppController.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/AppControllers/StockAppController.java index 60de256..b17bd67 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/AppControllers/StockAppController.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/AppControllers/StockAppController.java @@ -40,7 +40,7 @@ public StockAppController( stockApp.getSearchField().textProperty().addListener( (obs, old, newValue) -> { - if (stockApp.getCurrentStock() != null) { + if (stockApp.getCurrentStock() != null && !newValue.isEmpty()) { stockApp.openSearchPage(); } stockApp.getStockList().getItems().setAll( @@ -50,14 +50,12 @@ public StockAppController( stockApp.getQuantitySpinner().valueProperty().addListener( (obs, old, newValue) -> { - System.out.println("[DEBUG] Quantity changed: " + old + " -> " + newValue); if (stockApp.getCurrentStock() != null) { updateReceipt(); } }); - stockApp.getConfirmButton().setOnMouseClicked(event -> { - System.out.println("[DEBUG] Confirm button clicked"); + stockApp.getConfirmButton().setOnAction(event -> { handleTransaction(); }); } @@ -68,14 +66,11 @@ public StockAppController( private void handleTransaction() { Stock currentStock = stockApp.getCurrentStock(); if (currentStock == null) { - System.out.println("[DEBUG] Transaction failed: No stock selected"); return; } int quantityValue = stockApp.getQuantitySpinner().getValue(); - System.out.println("[DEBUG] Quantity spinner value: " + quantityValue); if (quantityValue == 0) { - System.out.println("[DEBUG] Transaction skipped: Quantity is 0"); return; } @@ -95,8 +90,6 @@ private void handleTransaction() { * @param quantity the amount to buy */ private void handleBuy(final Stock stock, final BigDecimal quantity) { - System.out.println("[DEBUG] Attempting to buy " + quantity + " of " - + stock.getSymbol()); try { Transaction transaction = marketController.getExchange().buy( stock.getSymbol(), quantity, player @@ -105,13 +98,8 @@ private void handleBuy(final Stock stock, final BigDecimal quantity) { if (transaction != null) { transaction.commit(player); player.getPortfolio().addShare(transaction.getShare()); - System.out.println("[DEBUG] Buy successful"); - } else { - System.out.println("[DEBUG] Buy failed: transaction is null"); } } catch (Exception exception) { - System.out.println("[DEBUG] Buy failed with exception: " - + exception.getMessage()); exception.printStackTrace(); } } @@ -123,16 +111,11 @@ private void handleBuy(final Stock stock, final BigDecimal quantity) { * @param quantity the amount to sell */ private void handleSell(final Stock stock, final BigDecimal quantity) { - System.out.println("[DEBUG] Attempting to sell " + quantity + " of " - + stock.getSymbol()); player.getPortfolio().getShares().stream() .filter(s -> s.getStock().getSymbol().equals(stock.getSymbol())) .findFirst() - .ifPresentOrElse(existingShare -> { + .ifPresent(existingShare -> { if (existingShare.getQuantity().compareTo(quantity) < 0) { - System.out.println("[DEBUG] Sell failed: Not enough shares owned. " - + "Have: " + existingShare.getQuantity() + ", Need: " - + quantity); return; } @@ -142,12 +125,8 @@ private void handleSell(final Stock stock, final BigDecimal quantity) { if (transaction != null) { transaction.commit(player); player.getPortfolio().removeShare(transaction.getShare()); - System.out.println("[DEBUG] Sell successful"); - } else { - System.out.println("[DEBUG] Sell failed: transaction is null"); } - }, () -> System.out.println("[DEBUG] Sell failed: No shares of " - + stock.getSymbol() + " owned")); + }); } /** @@ -171,12 +150,10 @@ private void updateReceipt() { */ @Override public void nextTick() { - System.out.println("[DEBUG] StockAppController.nextTick() triggered"); marketController.updateMarket(); + Platform.runLater(() -> stockApp.getStockList().refresh()); Stock currentStockInView = stockApp.getCurrentStock(); if (currentStockInView != null) { - System.out.println("[DEBUG] Updating UI for stock: " - + currentStockInView.getSymbol()); stockApp.updatePriceLabel(currentStockInView); updateReceipt(); // Graph is updated within marketController.updateMarket() if visible diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/GameController.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/GameController.java index 801486d..0f6e6a6 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/GameController.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/GameController.java @@ -38,7 +38,7 @@ public void addAppController(final AppController appController) { * Starts the game simulation timer. */ public void startGame() { - timer = new Timer(); + timer = new Timer(true); // Set as daemon thread to allow JVM to exit final int delay = 0; final int period = 1000; @@ -46,12 +46,20 @@ public void startGame() { timer.scheduleAtFixedRate(new TimerTask() { @Override public void run() { - System.out.println("[DEBUG] Game Tick Started"); for (AppController controller : appControllers) { controller.nextTick(); } - System.out.println("[DEBUG] Game Tick Finished"); } }, delay, period); } + + /** + * Stops the game simulation timer. + */ + public void stopGame() { + if (timer != null) { + timer.cancel(); + timer.purge(); + } + } } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/MarketController.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/MarketController.java index b3b96bd..9146229 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/MarketController.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/MarketController.java @@ -54,9 +54,6 @@ public List getMarket(final String searchTerm) { * Updates the market by updating all stock prices and graphs. */ public void updateMarket() { - System.out.println("[DEBUG] MarketController.updateMarket() " - + "- Updating prices for " - + exchange.getAllStocks().size() + " stocks"); for (Stock stock : exchange.getAllStocks()) { stock.updatePrice(); stock.updateStockGraph(); diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/PopupController.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/PopupController.java index 2685fd9..1068503 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/PopupController.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/PopupController.java @@ -37,6 +37,7 @@ public boolean show(final App type) { .findFirst() .ifPresent(popup -> { popup.getRoot().setVisible(true); + popup.getRoot().autosize(); bringToFront(popup); }); return true; 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 c0ec44a..c2f2dfa 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 @@ -62,8 +62,8 @@ public DesktopViewController( gameController.addAppController(new AppStoreController(appStoreApp)); HustlersApp hustlersApp = new HustlersApp(); - - StockApp stockApp = new StockApp(); + + StockApp stockApp = new StockApp(player); MarketController marketController = new MarketController(); gameController.addAppController( new StockAppController(stockApp, marketController, player) @@ -84,6 +84,12 @@ public DesktopViewController( popups.add(bankApp); this.popupController = new PopupController(popups); + + bankApp.setOnShareSelected(share -> { + popupController.show(App.STOCK); + stockApp.openStockPage(share.getStock()); + }); + this.desktopView = new DesktopView(this); this.desktopView.getRoot().getChildren().addAll( @@ -126,7 +132,10 @@ public DesktopView getDesktopView() { * @return the app button. */ public Button createAppButton(final App type) { - Button button = new Button(type.toString()); + String buttonText = type.toString().substring(0, 1).toUpperCase() + + type.toString().substring(1).toLowerCase(); + Button button = new Button(buttonText); + button.getStyleClass().add("app-button"); button.setMinSize(0, 0); final double sizeMultiplier = 0.8; @@ -147,7 +156,6 @@ public Button createAppButton(final App type) { // Print when clicked and open popup button.setOnAction(event -> { - System.out.println("button pressed: " + type); openPopup(type); }); @@ -170,7 +178,6 @@ public Button createAppButton(final App type) { * Handles the settings button action. */ public void handleSettings() { - System.out.println("Settings button pressed"); // Placeholder for settings logic } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/ViewControllers/StartViewController.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/ViewControllers/StartViewController.java index f62e659..f116725 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/ViewControllers/StartViewController.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/ViewControllers/StartViewController.java @@ -45,7 +45,15 @@ private void handleStart() { } String resolvedName = resolveUsername(username); - Difficulty difficulty = Difficulty.fromString(startView.getSelectedMode()); + String selectedMode = startView.getSelectedMode(); + Difficulty difficulty = Difficulty.EASY; + if (selectedMode != null) { + if (selectedMode.startsWith("Medium")) { + difficulty = Difficulty.MEDIUM; + } else if (selectedMode.startsWith("Hard")) { + difficulty = Difficulty.HARD; + } + } application.initGame(resolvedName, difficulty); } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Millions.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Millions.java index a9afdb3..8c6ede1 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Millions.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Millions.java @@ -56,6 +56,13 @@ public void switchToDesktopView() { stage.setScene(scene); } + @Override + public void stop() { + if (gameController != null) { + gameController.stopGame(); + } + } + public static void main(String[] args) { launch(args); } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Exchange.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Exchange.java index 682701b..8fd64f3 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Exchange.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Exchange.java @@ -108,14 +108,10 @@ public Transaction buy( final BigDecimal quantity, final Player player ) { - System.out.println("[DEBUG] Exchange" + " Buying " + quantity + " of " - + symbol + " for player " + player.getName()); Stock stock = getStock(symbol); BigDecimal purchasePrice = stock.getSalesPrice(); Share share = new Share(stock, quantity, purchasePrice); - System.out.println("[DEBUG] Purchase complete: " + quantity - + " shares of " + symbol + " at " + purchasePrice); return new Purchase(share, week); } @@ -132,14 +128,9 @@ public Transaction sell( final BigDecimal quantity, final Player player ) { - System.out.println("[DEBUG] Exchange " + " - Selling " + quantity - + " shares of " + share.getStock().getSymbol() - + " for player " + player.getName()); Share shareToSell = new Share( share.getStock(), quantity, share.getPurchasePrice() ); - System.out.println("[DEBUG] Sale complete: Prepared " + quantity - + " shares for transaction"); return new Sale(shareToSell, week); } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Player.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Player.java index f4c14c8..6fd4410 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Player.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Player.java @@ -33,6 +33,10 @@ public Player(final String name, final BigDecimal startingMoney) { this.transactionArchive = new TransactionArchive(); } + public BigDecimal getStartingMoney() { + return startingMoney; + } + public String getName() { return name; } @@ -55,10 +59,7 @@ public BigDecimal getMoney() { * @param amount the amount to add */ public void addMoney(final BigDecimal amount) { - BigDecimal oldMoney = money; money = money.add(amount); - System.out.println("[DEBUG] Player " + name + " money added: " - + oldMoney + " -> " + money + " (+" + amount + ")"); } /** @@ -69,14 +70,9 @@ public void addMoney(final BigDecimal amount) { */ public void withdrawMoney(final BigDecimal amount) { if (money.compareTo(amount) < 0) { - System.out.println("[DEBUG] Player " + name + " withdrawal FAILED: " - + "Insufficient funds (Needs: " + amount + ", Has: " + money + ")"); throw new InsufficientFunds("Not enough money to withdraw " + amount); } - BigDecimal oldMoney = money; money = money.subtract(amount); - System.out.println("[DEBUG] Player " + name + " money withdrawn: " - + oldMoney + " -> " + money + " (-" + amount + ")"); } /** diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Portfolio.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Portfolio.java index caca240..6cfd480 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Portfolio.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Portfolio.java @@ -81,8 +81,6 @@ public boolean removeShare(final Share newShare) { .findFirst(); if (match.isEmpty()) { - System.out.println("[DEBUG] Portfolio - Did not find share: " - + newShare); return false; } @@ -93,24 +91,18 @@ public boolean removeShare(final Share newShare) { if (cmp < 0) { // Trying to sell more than owned - System.out.println("[DEBUG] Portfolio - Cannot sell " - + newShare.getQuantity() - + " of " + newShare.getStock().getSymbol() - + ", only " + existing.getQuantity() + " owned"); return false; } else if (cmp == 0) { // Selling entire position — remove from list - System.out.println("[DEBUG] Portfolio - Fully closed position: " - + existing); return shares.remove(existing); } else { - // Partial sale — update quantity in place - existing.setQuantity(remainingQuantity); - System.out.println("[DEBUG] Portfolio - Partial sale: " - + newShare.getQuantity() - + " sold, " + remainingQuantity + " remaining of " - + existing.getStock().getSymbol()); - return true; + // Partial sale — replace with new instance to ensure UI update + shares.remove(existing); + return shares.add(new Share( + existing.getStock(), + remainingQuantity, + existing.getPurchasePrice() + )); } } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Share.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Share.java index 0582f7d..b64dc91 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Share.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Share.java @@ -1,6 +1,7 @@ package edu.ntnu.idi.idatt2003.gruppe42.Model; import java.math.BigDecimal; +import java.util.Objects; /** * Represents a share of a particular stock owned by a player. @@ -69,4 +70,23 @@ public BigDecimal getQuantity() { public BigDecimal getPurchasePrice() { return purchasePrice; } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Share share = (Share) o; + return Objects.equals(stock.getSymbol(), share.stock.getSymbol()) + && (quantity == null ? share.quantity == null : quantity.compareTo(share.quantity) == 0) + && (purchasePrice == null ? share.purchasePrice == null : purchasePrice.compareTo(share.purchasePrice) == 0); + } + + @Override + public int hashCode() { + return Objects.hash(stock.getSymbol(), quantity, purchasePrice); + } } 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 f162ed7..616ff9b 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 @@ -2,43 +2,97 @@ import edu.ntnu.idi.idatt2003.gruppe42.Model.App; import edu.ntnu.idi.idatt2003.gruppe42.Model.Share; -import edu.ntnu.idi.idatt2003.gruppe42.Model.Stock; import edu.ntnu.idi.idatt2003.gruppe42.View.Popup; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.function.Consumer; import javafx.application.Platform; +import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.control.Label; import javafx.scene.control.ListCell; 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. */ public class BankApp extends Popup { private final ListView portfolioList; - private final Label netWorthLabel = new Label("Net Worth: "); - private final Label unInvestedMoneyLabel = new Label("Cash: "); - private final Label investedMoneyLabel = new Label("Stock: "); - private final Label growthPercentageLabel = new Label("Growth: "); + 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 Consumer onShareSelected; /** * Constructs a new Bank popup. */ public BankApp() { - super(400, 300, 220, 120, App.BANK); + super(500, 550, 220, 100, App.BANK); this.portfolioList = new ListView<>(); - updateContent(); - } - - private void updateContent() { - GridPane statusPane = new GridPane(); - statusPane.add(netWorthLabel, 0, 0); - statusPane.add(growthPercentageLabel, 1, 0); - statusPane.add(unInvestedMoneyLabel, 0, 1); - statusPane.add(investedMoneyLabel, 1, 1); + 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); @@ -48,21 +102,103 @@ protected void updateItem(final Share share, final boolean empty) { return; } - GridPane sharePane = new GridPane(); - sharePane.setAlignment(Pos.CENTER_LEFT); - sharePane.add(new Label(share.getStock().getCompany()), 0, 0); - sharePane.add(new Label("Purchase"), 1, 0); - sharePane.add(new Label(share.getStock().getSymbol()), 0, 1); - sharePane.add(new Label(share.getPurchasePrice().toString()), 1, 1); - sharePane.add(new Label("Quantity"), 0, 2); - sharePane.add(new Label("Sale"), 1, 2); - sharePane.add(new Label(share.getQuantity().toString()), 0, 3); - sharePane.add(new Label(share.getStock().getSalesPrice().toString()), 1, 3); - setGraphic(sharePane); + 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); } }); - content.getChildren().setAll(statusPane, portfolioList); + content.setPadding(new Insets(20)); + content.setSpacing(20); + updateContent(); + } + + private void updateContent() { + content.getChildren().clear(); + + 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"); + + netWorthLabel.getStyleClass().add("bank-summary-label-bold"); + + VBox totalBalanceBox = new VBox(5, summaryTitle, netWorthLabel); + totalBalanceBox.setAlignment(Pos.CENTER_LEFT); + + GridPane grid = new GridPane(); + grid.setHgap(40); + grid.setVgap(15); + + 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); + + summaryBox.getChildren().addAll(totalBalanceBox, grid); + + 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); + + content.getChildren().addAll(summaryBox, portfolioBox); + } + + private VBox createSummaryItem(String title, Label valueLabel) { + VBox vbox = new VBox(5); + vbox.setAlignment(Pos.CENTER_LEFT); + Label titleLabel = new Label(title); + titleLabel.getStyleClass().add("bank-summary-item-title"); + vbox.getChildren().addAll(titleLabel, valueLabel); + return vbox; } /** @@ -71,26 +207,47 @@ protected void updateItem(final Share share, final boolean empty) { * @param netWorth total net worth * @param unInvestedMoney available cash * @param investedMoney value of shares - * @param growthPercentage growth since start + * @param startingMoney the starting money to calculate growth */ public void updateStatus( final double netWorth, final double unInvestedMoney, final double investedMoney, - final double growthPercentage + final double startingMoney ) { Platform.runLater(() -> { - netWorthLabel.setText("Net Worth: " + netWorth); - unInvestedMoneyLabel.setText("Cash: " + unInvestedMoney); - investedMoneyLabel.setText("Stock: " + investedMoney); - growthPercentageLabel.setText("Growth: " + growthPercentage + "%"); - updateContent(); + 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")); + } }); } 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; + } } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Apps/StockApp.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Apps/StockApp.java index f869efd..ae187ea 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Apps/StockApp.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Apps/StockApp.java @@ -7,10 +7,16 @@ import edu.ntnu.idi.idatt2003.gruppe42.View.Views.Components.StockGraph; import java.math.BigDecimal; import javafx.application.Platform; +import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.control.*; 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 Stock app. @@ -23,48 +29,97 @@ public class StockApp extends Popup { private final Spinner quantitySpinner; private final Button confirmButton; private final Receipt receipt; + private final edu.ntnu.idi.idatt2003.gruppe42.Model.Player player; /** * Constructs a new Stock popup. * + * @param player the player performing transactions */ - public StockApp() { - super(400, 300, 140, 140, App.STOCK); + public StockApp(final edu.ntnu.idi.idatt2003.gruppe42.Model.Player player) { + super(500, 450, 140, 140, App.STOCK); + this.player = player; this.searchField = new TextField(); + this.searchField.setPromptText("Search stocks..."); + this.searchField.getStyleClass().add("search-field"); + this.searchField.setMaxWidth(Double.MAX_VALUE); + this.stockList = new ListView<>(); + VBox.setVgrow(this.stockList, Priority.ALWAYS); + this.stockList.setSelectionModel(null); + this.stockList.getStyleClass().add("stock-list"); - final int minQuantity = -10; - final int maxQuantity = 10; + final int minQuantity = -100; + final int maxQuantity = 100; final int initialQuantity = 0; this.quantitySpinner = new Spinner<>( minQuantity, maxQuantity, initialQuantity ); + this.quantitySpinner.setEditable(true); + this.quantitySpinner.getStyleClass().add("stock-quantity-spinner"); + this.quantitySpinner.setPrefWidth(80); + + this.confirmButton = new Button("Trade"); + this.confirmButton.getStyleClass().add("primary-button"); + this.confirmButton.setPrefHeight(35); + this.quantitySpinner.setPrefHeight(35); - this.confirmButton = new Button("Confirm"); - this.receipt = new Receipt(null, BigDecimal.ZERO); + this.receipt = new Receipt(null, BigDecimal.ZERO, player); stockList.setCellFactory( lv -> new ListCell() { + private final HBox row = new HBox(15); + private final Label symbolLabel = new Label(); + private final Label companyLabel = new Label(); + private final Label price = new Label(); + private final Label changeLabel = new Label(); + + { + row.setPadding(new Insets(10)); + row.setAlignment(Pos.CENTER_LEFT); + row.getStyleClass().add("stock-list-item"); + + VBox names = new VBox(2, symbolLabel, companyLabel); + symbolLabel.getStyleClass().add("stock-symbol"); + companyLabel.getStyleClass().add("stock-company"); + + Region spacer = new Region(); + HBox.setHgrow(spacer, Priority.ALWAYS); + + VBox priceInfo = new VBox(2, price, changeLabel); + priceInfo.setAlignment(Pos.CENTER_RIGHT); + price.getStyleClass().add("stock-price"); + changeLabel.getStyleClass().add("stock-price-change"); + + row.getChildren().addAll(names, spacer, priceInfo); + } + @Override protected void updateItem(final Stock stock, final boolean empty) { super.updateItem(stock, empty); if (empty || stock == null) { setGraphic(null); } else { - final int spacing = 10; - HBox row = new HBox(spacing, new Label(stock.getCompany())); + symbolLabel.setText(stock.getSymbol()); + companyLabel.setText(stock.getCompany()); + price.setText("$" + String.format("%,.2f", stock.getSalesPrice())); + + BigDecimal change = stock.getLatestPriceChange(); + changeLabel.setText((change.compareTo(BigDecimal.ZERO) >= 0 ? "+" : "") + String.format("%,.2f", change)); + + changeLabel.getStyleClass().removeAll("stock-price-positive", "stock-price-negative"); + changeLabel.getStyleClass().add(change.compareTo(BigDecimal.ZERO) >= 0 ? "stock-price-positive" : "stock-price-negative"); + row.setOnMouseClicked(event -> { - System.out.println(stock.getCompany() + " is pressed!"); openStockPage(stock); }); - row.setAlignment(Pos.CENTER_LEFT); setGraphic(row); } } }); - content.getChildren().addAll(searchField, stockList); + openSearchPage(); } /** @@ -74,24 +129,51 @@ protected void updateItem(final Stock stock, final boolean empty) { */ public void openStockPage(final Stock stock) { currentStock = stock; - content.setAlignment(Pos.TOP_LEFT); + content.getChildren().clear(); - Button closeButton = new Button("Close"); - setCloseButtonAction(closeButton, stock); + Button backButton = new Button("← Back"); + backButton.getStyleClass().add("back-button"); + backButton.setOnAction(e -> openSearchPage()); - HBox header = new HBox(); - Label companyLabel = new Label(stock.getCompany()); + HBox topControls = new HBox(10); + topControls.setAlignment(Pos.CENTER_LEFT); + HBox.setHgrow(searchField, Priority.ALWAYS); + topControls.getChildren().addAll(backButton, searchField); - priceLabel = new Label(stock.getSalesPrice().toString()); + HBox headerBox = new HBox(15); + headerBox.setAlignment(Pos.CENTER_LEFT); + + VBox titleBox = new VBox(5); + Label companyTitle = new Label(stock.getCompany()); + companyTitle.getStyleClass().add("stock-title"); + Label symbolLabel = new Label(stock.getSymbol()); + symbolLabel.getStyleClass().add("stock-symbol-large"); + titleBox.getChildren().addAll(companyTitle, symbolLabel); - header.getChildren().addAll(companyLabel, priceLabel, quantitySpinner); + Region headerSpacer = new Region(); + HBox.setHgrow(headerSpacer, Priority.ALWAYS); + + HBox tradeControls = new HBox(10); + tradeControls.setAlignment(Pos.CENTER_RIGHT); + tradeControls.getChildren().addAll(quantitySpinner, confirmButton); + + headerBox.getChildren().addAll(titleBox, headerSpacer, tradeControls); + + priceLabel = new Label("$" + String.format("%,.2f", stock.getSalesPrice())); + priceLabel.getStyleClass().add("stock-price-large"); stock.getStockGraph().setVisibility(true); Platform.runLater(stock::updateStockGraph); StockGraph graph = stock.getStockGraph(); + VBox.setVgrow(graph, Priority.ALWAYS); + HBox receiptCentered = new HBox(receipt); + receiptCentered.setAlignment(Pos.CENTER); + + content.setSpacing(20); + content.setPadding(new Insets(20)); content.getChildren().setAll( - searchField, closeButton, header, graph, confirmButton, receipt + topControls, headerBox, priceLabel, graph, receiptCentered ); } @@ -103,7 +185,7 @@ public void openStockPage(final Stock stock) { public void updatePriceLabel(final Stock stock) { if (priceLabel != null && stock == currentStock) { Platform.runLater(() -> priceLabel.setText( - stock.getSalesPrice().toString()) + "$" + String.format("%,.2f", stock.getSalesPrice())) ); } } @@ -126,10 +208,16 @@ public void setCloseButtonAction( * Returns to the stock search page. */ public void openSearchPage() { + if (currentStock != null) { + currentStock.getStockGraph().setVisibility(false); + } currentStock = null; + content.setPadding(new Insets(20)); + content.setSpacing(20); content.getChildren().setAll(searchField, stockList); } + public Stock getCurrentStock() { return currentStock; } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Popup.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Popup.java index b1b0f3a..c945b8b 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Popup.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Popup.java @@ -1,6 +1,8 @@ package edu.ntnu.idi.idatt2003.gruppe42.View; import edu.ntnu.idi.idatt2003.gruppe42.Model.App; +import javafx.geometry.Insets; +import javafx.geometry.Pos; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.control.ScrollPane; @@ -8,7 +10,11 @@ import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Pane; +import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; import javafx.scene.layout.VBox; +import javafx.scene.shape.Circle; +import javafx.scene.shape.Rectangle; /** * An abstract class for all popups. @@ -49,36 +55,83 @@ protected Popup( this.type = type; root = new BorderPane(); + root.getStyleClass().add("popup-root"); + ScrollPane scrollPane = new ScrollPane(); - scrollPane.setStyle("-fx-hbar-policy: never; -fx-vbar-policy: AS_NEEDED; " - + "-fx-background-insets: 0; -fx-padding: 0;"); + scrollPane.getStyleClass().add("popup-scroll-pane"); + + // Load stylesheets + root.getStylesheets().add(getClass().getResource("/css/global.css").toExternalForm()); + root.getStylesheets().add(getClass().getResource("/css/popup.css").toExternalForm()); + root.getStylesheets().add(getClass().getResource("/css/apps.css").toExternalForm()); + + scrollPane.setFitToWidth(true); + scrollPane.setFitToHeight(true); header = new HBox(); - content = new VBox(); - - Label titleLabel = new Label(type.toString()); - closeButton = new Button("Exit"); - header.getChildren().addAll(titleLabel, closeButton); - header.setStyle("-fx-background-color: lightgray;"); + header.setAlignment(Pos.CENTER_LEFT); + header.setPadding(new Insets(5, 10, 5, 10)); + header.setSpacing(10); + header.getStyleClass().add("popup-header"); + + // Close Button (Mac-style red circle) + closeButton = new Button(); + closeButton.setShape(new Circle(6)); + closeButton.setPrefSize(12, 12); + closeButton.setMinSize(12, 12); + closeButton.setMaxSize(12, 12); + closeButton.getStyleClass().add("popup-close-button"); + + // Title Label (Centered) + Label titleLabel = new Label(type.toString().substring(0, 1).toUpperCase() + + type.toString().substring(1).toLowerCase()); + titleLabel.getStyleClass().add("popup-title"); + + // Container for title to center it + HBox titleContainer = new HBox(titleLabel); + titleContainer.setAlignment(Pos.CENTER); + HBox.setHgrow(titleContainer, Priority.ALWAYS); + + // Placeholder for symmetry on the right + Region rightSpacer = new Region(); + rightSpacer.setPrefSize(12, 12); + + header.getChildren().addAll(closeButton, titleContainer, rightSpacer); setCloseButtonAction(); + content = new VBox(); + content.getStyleClass().add("popup-content"); content.setPrefSize(width, height); scrollPane.setMinSize(width, height); root.setLayoutX(x); root.setLayoutY(y); + root.setManaged(false); scrollPane.setContent(content); root.setTop(header); root.setCenter(scrollPane); + // Initial sizing + root.autosize(); + + // Clip content to rounded corners + Rectangle clip = new Rectangle(); + clip.setArcWidth(20); + clip.setArcHeight(20); + clip.widthProperty().bind(root.widthProperty()); + clip.heightProperty().bind(root.heightProperty()); + root.setClip(clip); + setOnPressedAction(); makeDraggable(); root.setVisible(false); } private void setCloseButtonAction() { - closeButton.setOnAction(e -> root.setVisible(false)); + closeButton.setOnAction(e -> { + root.setVisible(false); + }); } private void setOnPressedAction() { diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Views/Components/Receipt.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Views/Components/Receipt.java index 4dfbe8f..df0a8bc 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Views/Components/Receipt.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Views/Components/Receipt.java @@ -1,24 +1,34 @@ package edu.ntnu.idi.idatt2003.gruppe42.View.Views.Components; +import edu.ntnu.idi.idatt2003.gruppe42.Model.Share; import edu.ntnu.idi.idatt2003.gruppe42.Model.Stock; import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.Optional; +import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.control.Label; -import javafx.scene.layout.GridPane; import javafx.scene.layout.VBox; /** * Component for displaying a transaction receipt. */ public final class Receipt extends VBox { + private final edu.ntnu.idi.idatt2003.gruppe42.Model.Player player; /** * Constructs a new Receipt. * * @param stock the stock * @param quantity the quantity + * @param player the player */ - public Receipt(final Stock stock, final BigDecimal quantity) { + public Receipt(final Stock stock, final BigDecimal quantity, final edu.ntnu.idi.idatt2003.gruppe42.Model.Player player) { + this.player = player; + this.getStyleClass().add("receipt-container"); + this.setAlignment(Pos.TOP_CENTER); + this.setSpacing(10); + this.setMaxWidth(300); update(stock, quantity); } @@ -29,25 +39,72 @@ public Receipt(final Stock stock, final BigDecimal quantity) { * @param quantity the quantity */ public void update(final Stock stock, final BigDecimal quantity) { + this.getChildren().clear(); + + Label header = new Label("TRANSACTION SLIP"); + header.getStyleClass().add("receipt-header"); + + Label line1 = new Label("=========================="); + line1.getStyleClass().add("receipt-text"); + Label line2 = new Label("--------------------------"); + line2.getStyleClass().add("receipt-text"); + Label line3 = new Label("=========================="); + line3.getStyleClass().add("receipt-text"); + if (stock == null) { - this.getChildren().clear(); + this.getChildren().addAll(header, line1, new Label("Select a stock"), line3); return; } - GridPane receiptPane = new GridPane(); - receiptPane.setAlignment(Pos.CENTER); + VBox details = new VBox(5); + details.setAlignment(Pos.TOP_LEFT); + + String type = quantity.compareTo(BigDecimal.ZERO) >= 0 ? "BUY" : "SELL"; + BigDecimal absQty = quantity.abs(); + + details.getChildren().add(createReceiptLabel("STOCK:", stock.getCompany())); + details.getChildren().add(createReceiptLabel("SYMBOL:", stock.getSymbol())); + details.getChildren().add(createReceiptLabel("TYPE:", type)); + details.getChildren().add(createReceiptLabel("QTY:", absQty.toString())); + details.getChildren().add(createReceiptLabel("PRICE:", "$" + String.format("%,.2f", stock.getSalesPrice()))); + + BigDecimal gross = stock.getSalesPrice().multiply(absQty); + BigDecimal commission = gross.multiply(new BigDecimal("0.01")).setScale(2, RoundingMode.HALF_UP); + + BigDecimal tax = BigDecimal.ZERO; + if (type.equals("SELL") && player != null) { + Optional match = player.getPortfolio().getShares().stream() + .filter(s -> s.getStock().getSymbol().equals(stock.getSymbol())) + .findFirst(); + + if (match.isPresent()) { + BigDecimal purchasePrice = match.get().getPurchasePrice(); + BigDecimal profit = stock.getSalesPrice().subtract(purchasePrice).multiply(absQty); + if (profit.compareTo(BigDecimal.ZERO) > 0) { + tax = profit.multiply(new BigDecimal("0.22")).setScale(2, RoundingMode.HALF_UP); + } + } + } - BigDecimal totalAmount = stock.getSalesPrice().multiply(quantity); + BigDecimal total = type.equals("BUY") ? gross.add(commission) : gross.subtract(commission).subtract(tax); - receiptPane.add(new Label(stock.getCompany()), 0, 0); - receiptPane.add(new Label("Quantity"), 1, 0); - receiptPane.add(new Label(stock.getSymbol()), 0, 1); - receiptPane.add(new Label(quantity.toString()), 1, 1); - receiptPane.add(new Label("Price"), 0, 2); - receiptPane.add(new Label("Amount"), 1, 2); - receiptPane.add(new Label(stock.getSalesPrice().toString()), 0, 3); - receiptPane.add(new Label(totalAmount.toString()), 1, 3); + VBox totals = new VBox(5); + totals.getStyleClass().add("receipt-total"); + totals.setPadding(new Insets(10, 0, 0, 0)); - this.getChildren().setAll(new Label("Receipt"), receiptPane); + totals.getChildren().add(createReceiptLabel("GROSS:", "$" + String.format("%,.2f", gross))); + totals.getChildren().add(createReceiptLabel("COMMISSION:", "$" + String.format("%,.2f", commission))); + if (type.equals("SELL")) { + totals.getChildren().add(createReceiptLabel("EST. TAX:", "$" + String.format("%,.2f", tax))); } + totals.getChildren().add(createReceiptLabel("TOTAL:", "$" + String.format("%,.2f", total))); + + this.getChildren().addAll(header, line1, details, line2, totals, line3); + } + + private Label createReceiptLabel(String label, String value) { + Label l = new Label(String.format("%-12s %s", label, value)); + l.getStyleClass().add("receipt-text"); + return l; + } } \ No newline at end of file diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Views/Components/StockGraph.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Views/Components/StockGraph.java index b7a15d1..c1398c5 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Views/Components/StockGraph.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Views/Components/StockGraph.java @@ -21,6 +21,7 @@ public final class StockGraph extends VBox { * Constructs a new StockGraph. */ public StockGraph() { + this.getStyleClass().add("stock-graph-container"); NumberAxis xAxis = new NumberAxis(); xAxis.setLabel("Time"); xAxis.setAutoRanging(true); @@ -32,7 +33,8 @@ public StockGraph() { yAxis.setAutoRanging(true); lineChart = new LineChart<>(xAxis, yAxis); - lineChart.setPrefSize(100, 100); + lineChart.getStyleClass().add("stock-line-chart"); + lineChart.setPrefSize(400, 250); lineChart.setCreateSymbols(false); lineChart.setLegendVisible(false); lineChart.setAnimated(false); 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 ddd43d1..7f167f8 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 @@ -23,19 +23,16 @@ import javafx.scene.layout.Region; import javafx.scene.layout.RowConstraints; import javafx.scene.layout.StackPane; -import javafx.scene.paint.Color; -import javafx.scene.text.Font; -import javafx.scene.text.FontWeight; /** * View for the desktop. - * Displays a 6x4 grid of apps. + * Displays a 10x6 grid of apps. */ public final class DesktopView { private final DesktopViewController desktopViewController; private final BorderPane root; - private static final int COLS = 6; - private static final int ROWS = 4; + private static final int COLS = 10; + private static final int ROWS = 6; /** * Constructs a new DesktopView. @@ -53,11 +50,19 @@ public DesktopView(final DesktopViewController desktopViewController) { * @return the root of the desktop view. */ public BorderPane getRoot() { - GridPane grid = createGrid(); - root.setCenter(grid); - root.setBottom(createBottomBar()); + if (root.getCenter() == null) { + GridPane grid = createGrid(); + root.setCenter(grid); + } + if (root.getBottom() == null) { + root.setBottom(createBottomBar()); + } + + // Load stylesheet + root.getStylesheets().add(getClass().getResource("/css/global.css").toExternalForm()); + root.getStylesheets().add(getClass().getResource("/css/desktop.css").toExternalForm()); + root.getStylesheets().add(getClass().getResource("/css/apps.css").toExternalForm()); - // Load background image from resources Image backgroundImage = new Image( Objects.requireNonNull(getClass().getResourceAsStream("/Images/mac_home_background.jpg")), 1920, 1080, @@ -149,17 +154,18 @@ private HBox createBottomBar() { HBox bottomBar = new HBox(15); bottomBar.setAlignment(Pos.CENTER_LEFT); bottomBar.setPadding(new Insets(5, 15, 5, 15)); - bottomBar.setStyle("-fx-background-color: rgba(0, 0, 0, 0.7);"); + bottomBar.getStyleClass().add("desktop-bottom-bar"); // Settings Button Button settingsButton = new Button("⚙"); - settingsButton.setStyle("-fx-background-color: transparent; -fx-text-fill: white; -fx-font-size: 18px;"); - settingsButton.setOnAction(e -> desktopViewController.handleSettings()); + settingsButton.getStyleClass().add("desktop-settings-button"); + settingsButton.setOnAction(e -> { + desktopViewController.handleSettings(); + }); // Player Name Label playerNameLabel = new Label("User: " + desktopViewController.getPlayer().getName()); - playerNameLabel.setTextFill(Color.WHITE); - playerNameLabel.setFont(Font.font("Arial", FontWeight.BOLD, 14)); + playerNameLabel.getStyleClass().add("desktop-label-bold"); playerNameLabel.setMinWidth(Region.USE_PREF_SIZE); // Spacer to push clock to the right @@ -171,20 +177,16 @@ private HBox createBottomBar() { TimeAndWeatherController twc = desktopViewController.getTimeAndWeatherController(); Label weatherLabel = new Label(); - weatherLabel.setTextFill(Color.WHITE); - weatherLabel.setFont(Font.font("Arial", 14)); + weatherLabel.getStyleClass().add("desktop-label"); Label tempLabel = new Label(); - tempLabel.setTextFill(Color.WHITE); - tempLabel.setFont(Font.font("Arial", 14)); + tempLabel.getStyleClass().add("desktop-label"); Label dayLabel = new Label(); - dayLabel.setTextFill(Color.WHITE); - dayLabel.setFont(Font.font("Arial", FontWeight.BOLD, 14)); + dayLabel.getStyleClass().add("desktop-label-bold"); Label clockLabel = new Label(); - clockLabel.setTextFill(Color.WHITE); - clockLabel.setFont(Font.font("Arial", FontWeight.BOLD, 14)); + clockLabel.getStyleClass().add("desktop-label-bold"); // Update labels when properties change twc.hourProperty().addListener((obs, oldVal, newVal) -> Platform.runLater(() -> { diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Views/StartView.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Views/StartView.java index 17aceb1..be45187 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Views/StartView.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Views/StartView.java @@ -13,6 +13,8 @@ import javafx.scene.layout.BackgroundPosition; import javafx.scene.layout.BackgroundRepeat; import javafx.scene.layout.BackgroundSize; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; @@ -21,7 +23,7 @@ public class StartView { private final TextField usernameField = new TextField(); - private final Button loginButton = new Button("Login"); + private final Button loginButton = new Button("→"); private final ToggleGroup startingMoneyGroup = new ToggleGroup(); @@ -39,40 +41,59 @@ public StackPane getRoot() { ); root.setBackground(new Background(background)); - // Main content - VBox content = new VBox(); - content.setAlignment(Pos.CENTER); - - // Logo + // User input content + VBox loginContainer = new VBox(20); + loginContainer.setAlignment(Pos.CENTER); + loginContainer.setMaxWidth(400); + loginContainer.getStyleClass().add("login-container"); + + // Avatar + VBox avatarContainer = new VBox(); + avatarContainer.getStyleClass().add("avatar-container"); Image logoImage = new Image(Objects.requireNonNull(getClass().getResourceAsStream("/images/logo.jpg"))); ImageView logoView = new ImageView(logoImage); logoView.setPreserveRatio(true); - logoView.setFitWidth(250); - - // User input content - VBox inputContent = new VBox(); - inputContent.setAlignment(Pos.CENTER); - inputContent.setMaxWidth(400); + logoView.setFitWidth(120); + avatarContainer.getChildren().add(logoView); + avatarContainer.setMaxWidth(130); Label usernameLabel = new Label("State your name, G"); + usernameLabel.getStyleClass().add("difficulty-label"); + usernameLabel.setStyle("-fx-font-size: 18px;"); + + HBox loginInputBox = new HBox(); + loginInputBox.setAlignment(Pos.CENTER_LEFT); + loginInputBox.getStyleClass().add("login-input-box"); + loginInputBox.setMaxWidth(250); usernameField.setPromptText("e.g. TateMindset99"); + usernameField.getStyleClass().add("login-field"); + HBox.setHgrow(usernameField, Priority.ALWAYS); + loginButton.getStyleClass().add("login-submit-button"); + loginInputBox.getChildren().addAll(usernameField, loginButton); + Label startingMoneyLabel = new Label("How much matrix money?"); - VBox startingMoneyOptions = new VBox(); + startingMoneyLabel.getStyleClass().add("difficulty-label"); + VBox startingMoneyOptions = new VBox(10); startingMoneyOptions.setAlignment(Pos.CENTER); - RadioButton easyCheck = new RadioButton("Easy (1K$ - Stone hard cash!)"); - RadioButton mediumCheck = new RadioButton("Medium (100$ - cold bucks)"); - RadioButton hardCheck = new RadioButton("Hard (0$ - im no nepo-baby)"); + RadioButton easyCheck = new RadioButton("Easy ($1000)"); + RadioButton mediumCheck = new RadioButton("Medium ($100)"); + RadioButton hardCheck = new RadioButton("Hard ($0)"); + + easyCheck.getStyleClass().add("difficulty-label"); + mediumCheck.getStyleClass().add("difficulty-label"); + hardCheck.getStyleClass().add("difficulty-label"); easyCheck.setToggleGroup(startingMoneyGroup); mediumCheck.setToggleGroup(startingMoneyGroup); hardCheck.setToggleGroup(startingMoneyGroup); startingMoneyOptions.getChildren().addAll(easyCheck, mediumCheck, hardCheck); - // Set children - inputContent.getChildren().addAll(usernameLabel, usernameField, startingMoneyLabel, startingMoneyOptions); - content.getChildren().addAll(logoView, inputContent, loginButton); - root.getChildren().add(content); + loginContainer.getChildren().addAll(avatarContainer, usernameLabel, loginInputBox, startingMoneyLabel, startingMoneyOptions); + + root.getChildren().add(loginContainer); + root.getStyleClass().add("login-root"); + root.getStylesheets().add(getClass().getResource("/css/login.css").toExternalForm()); return root; } diff --git a/src/main/resources/css/apps.css b/src/main/resources/css/apps.css new file mode 100644 index 0000000..53a2a3a --- /dev/null +++ b/src/main/resources/css/apps.css @@ -0,0 +1,226 @@ +/* App Specific Styles */ + +/* Stock App */ +.stock-list-item { + -fx-border-color: #f0f0f0; + -fx-border-width: 0 0 1 0; + -fx-cursor: hand; + -fx-background-color: transparent !important; +} + +.stock-symbol { + -fx-font-weight: bold; + -fx-font-size: 14px; +} + +.stock-company { + -fx-font-size: 12px; + -fx-text-fill: gray; +} + +.stock-price { + -fx-font-weight: bold; + -fx-font-size: 14px; +} + +.stock-price-change { + -fx-font-size: 12px; +} + +.stock-price-positive { + -fx-text-fill: green; +} + +.stock-price-negative { + -fx-text-fill: red; +} + +.stock-title { + -fx-font-weight: bold; + -fx-font-size: 20px; + -fx-text-fill: black; +} + +.stock-symbol-large { + -fx-text-fill: gray; +} + +.search-field { + -fx-background-color: white; + -fx-background-radius: 15; + -fx-border-color: #cccccc; + -fx-border-radius: 15; + -fx-padding: 5 15 5 15; +} + +.stock-price-large { + -fx-font-weight: bold; + -fx-font-size: 24px; +} + +.stock-quantity-spinner { + -fx-background-radius: 5; + -fx-background-color: white; + -fx-border-color: #cccccc; + -fx-border-radius: 5; +} + +.stock-quantity-spinner .text-field { + -fx-background-color: transparent; + -fx-alignment: center; +} + +.stock-price-change-large { + -fx-font-size: 18px; +} + +.trade-panel { + -fx-background-color: #f8f8f8; + -fx-background-radius: 10; +} + +.primary-button { + -fx-background-color: #007AFF; + -fx-text-fill: white; + -fx-font-weight: bold; + -fx-background-radius: 10; + -fx-padding: 8 20 8 20; +} + +.back-button { + -fx-background-color: transparent; + -fx-text-fill: #007AFF; + -fx-cursor: hand; +} + +/* Bank App */ +.bank-summary-card { + -fx-background-color: linear-gradient(to bottom right, #004e92, #000428); + -fx-background-radius: 15; + -fx-padding: 25; + -fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.3), 10, 0, 0, 5); +} + +.bank-summary-title { + -fx-font-weight: bold; + -fx-font-size: 14px; + -fx-text-fill: rgba(255, 255, 255, 0.8); + -fx-text-transform: uppercase; +} + +.bank-summary-label-bold { + -fx-font-weight: bold; + -fx-font-size: 32px; + -fx-text-fill: white; +} + +.bank-summary-label-normal { + -fx-font-size: 18px; + -fx-text-fill: white; + -fx-font-weight: bold; +} + +.bank-summary-item-title { + -fx-font-size: 12px; + -fx-text-fill: rgba(255, 255, 255, 0.7); +} + +.bank-portfolio-title { + -fx-font-weight: bold; + -fx-font-size: 18px; + -fx-padding: 10 0 5 0; +} + +.portfolio-list, .stock-list { + -fx-background-color: white; + -fx-background-radius: 10; + -fx-border-color: #e0e0e0; + -fx-border-radius: 10; + -fx-padding: 5; +} + +.portfolio-list:focused, .stock-list:focused { + -fx-focus-color: transparent; + -fx-faint-focus-color: transparent; +} + +.portfolio-list .list-cell, .stock-list .list-cell { + -fx-background-color: transparent !important; +} + +.portfolio-list .list-cell:hover, .stock-list .list-cell:hover { + -fx-cursor: hand; +} + +.bank-share-quantity { + -fx-font-weight: bold; + -fx-font-size: 14px; +} + +.bank-share-value { + -fx-font-weight: bold; + -fx-font-size: 14px; +} + +.bank-share-avg-price, .bank-share-current-price { + -fx-font-size: 12px; + -fx-text-fill: #666666; +} + +.bank-share-gain { + -fx-font-weight: bold; + -fx-font-size: 14px; +} + + +/* Stock Graph */ +.stock-graph-container { + -fx-padding: 10; + -fx-background-color: white; +} + +.stock-line-chart { + -fx-create-symbols: false; + -fx-horizontal-grid-lines-visible: false; + -fx-vertical-grid-lines-visible: false; +} + +.stock-line-chart .chart-series-line { + -fx-stroke: #007AFF; + -fx-stroke-width: 2px; +} + +.stock-line-chart .chart-plot-background { + -fx-background-color: transparent; +} + +.stock-line-chart .axis { + -fx-tick-label-fill: #888888; + -fx-tick-length: 5; +} + +/* Receipt */ +.receipt-container { + -fx-background-color: white; + -fx-border-color: #cccccc; + -fx-border-style: dashed; + -fx-padding: 20; + -fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.1), 5, 0, 0, 2); +} + +.receipt-text { + -fx-font-family: "Courier New", monospace; + -fx-font-size: 12px; +} + +.receipt-header { + -fx-font-weight: bold; + -fx-font-size: 14px; + -fx-alignment: center; +} + +.receipt-total { + -fx-font-weight: bold; + -fx-font-size: 14px; + -fx-border-color: black transparent transparent transparent; +} diff --git a/src/main/resources/css/desktop.css b/src/main/resources/css/desktop.css new file mode 100644 index 0000000..5a23d3e --- /dev/null +++ b/src/main/resources/css/desktop.css @@ -0,0 +1,35 @@ +.desktop-bottom-bar { + -fx-background-color: rgba(0, 0, 0, 0.7); + -fx-background-radius: 0; +} + +.desktop-settings-button { + -fx-background-color: transparent; + -fx-text-fill: white; + -fx-font-size: 18px; + -fx-cursor: hand; +} + +.desktop-label { + -fx-text-fill: white; + -fx-font-size: 14px; +} + +.desktop-label-bold { + -fx-text-fill: white; + -fx-font-weight: bold; + -fx-font-size: 14px; +} + +.app-button { + -fx-background-radius: 12; + -fx-background-color: #f0f0f0; + -fx-border-color: #d1d1d1; + -fx-border-radius: 12; + -fx-text-fill: #333333; + -fx-font-weight: bold; +} + +.app-button:hover { + -fx-background-color: #e0e0e0; +} diff --git a/src/main/resources/css/global.css b/src/main/resources/css/global.css new file mode 100644 index 0000000..46bd91a --- /dev/null +++ b/src/main/resources/css/global.css @@ -0,0 +1,65 @@ +/* Global Styles */ +.root { + -fx-font-family: "Segoe UI", system-ui, -apple-system, sans-serif; +} + +/* Custom ScrollBar Styling */ +.scroll-pane > .viewport { + -fx-background-color: transparent; +} + +.scroll-bar:vertical { + -fx-background-color: transparent; + -fx-pref-width: 8; +} + +.scroll-bar:horizontal { + -fx-opacity: 0; + -fx-pref-height: 0; +} + +.scroll-bar .thumb { + -fx-background-color: #cdcdcd; + -fx-background-radius: 4; +} + +.scroll-bar .thumb:hover { + -fx-background-color: #a6a6a6; +} + +.scroll-bar .track { + -fx-background-color: transparent; +} + +.scroll-bar .increment-button, .scroll-bar .decrement-button { + -fx-padding: 0; + -fx-background-color: transparent; + -fx-shape: ""; + -fx-opacity: 0; +} + +.scroll-bar .increment-arrow, .scroll-bar .decrement-arrow { + -fx-padding: 0; + -fx-shape: ""; + -fx-opacity: 0; +} + +.scroll-bar:vertical .increment-button, .scroll-bar:vertical .decrement-button { + -fx-pref-height: 0; + -fx-min-height: 0; + -fx-max-height: 0; +} + +.scroll-bar:horizontal .increment-button, .scroll-bar:horizontal .decrement-button { + -fx-pref-width: 0; + -fx-min-width: 0; + -fx-max-width: 0; +} + +.list-view { + -fx-background-color: transparent; +} + +.list-view .scroll-bar:vertical { + -fx-opacity: 1.0; +} diff --git a/src/main/resources/css/login.css b/src/main/resources/css/login.css new file mode 100644 index 0000000..b890f81 --- /dev/null +++ b/src/main/resources/css/login.css @@ -0,0 +1,52 @@ +.login-root { + -fx-font-family: "Segoe UI", system-ui, -apple-system, sans-serif; +} + +.login-container { + -fx-padding: 30; + -fx-alignment: center; +} + +.avatar-container { + -fx-padding: 5; + -fx-background-color: white; + -fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.5), 10, 0, 0, 0); + -fx-border-color: #ffffff; + -fx-border-width: 2; + -fx-background-radius: 4; + -fx-border-radius: 4; +} + +.login-input-box { + -fx-background-color: white; + -fx-background-radius: 4; + -fx-border-color: #abadb3; + -fx-border-radius: 4; +} + +.login-field { + -fx-background-color: transparent; + -fx-padding: 5 10 5 10; +} + +.login-submit-button { + -fx-background-color: linear-gradient(to bottom, #ffffff, #e1e1e1); + -fx-background-radius: 4; + -fx-border-color: #707070; + -fx-border-radius: 4; + -fx-padding: 2 10 2 10; + -fx-font-weight: bold; + -fx-font-size: 16px; + -fx-cursor: hand; +} + +.login-submit-button:hover { + -fx-background-color: linear-gradient(to bottom, #e5f1fb, #c6e0f7); + -fx-border-color: #3c7fb1; +} + +.difficulty-label { + -fx-text-fill: white; + -fx-font-size: 14px; + -fx-effect: dropshadow(one-pass-box, rgba(0,0,0,0.8), 2, 0, 0, 1); +} diff --git a/src/main/resources/css/popup.css b/src/main/resources/css/popup.css new file mode 100644 index 0000000..d3c1e25 --- /dev/null +++ b/src/main/resources/css/popup.css @@ -0,0 +1,39 @@ +.popup-root { + -fx-background-color: white; + -fx-background-radius: 10; + -fx-border-color: #d1d1d1; + -fx-border-radius: 10; + -fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.2), 10, 0, 0, 5); +} + +.popup-header { + -fx-background-color: linear-gradient(to bottom, #f0f0f0, #e0e0e0); + -fx-border-color: transparent transparent #d1d1d1 transparent; + -fx-border-width: 0 0 1 0; +} + +.popup-close-button { + -fx-background-color: #ff5f57; + -fx-background-insets: 0; + -fx-padding: 0; + -fx-cursor: hand; +} + +.popup-close-button:hover { + -fx-background-color: #ff4b42; +} + +.popup-close-button:pressed { + -fx-background-color: #BF3630; +} + +.popup-title { + -fx-font-weight: bold; + -fx-text-fill: #333333; + -fx-font-size: 16px; +} + +.popup-scroll-pane { + -fx-background-color: white; + -fx-viewport-background-color: white; +} diff --git a/src/test/java/edu/ntnu/idi/idatt2003/gruppe42/Model/PortfolioTest.java b/src/test/java/edu/ntnu/idi/idatt2003/gruppe42/Model/PortfolioTest.java index 6089bed..dc47d57 100644 --- a/src/test/java/edu/ntnu/idi/idatt2003/gruppe42/Model/PortfolioTest.java +++ b/src/test/java/edu/ntnu/idi/idatt2003/gruppe42/Model/PortfolioTest.java @@ -1,5 +1,6 @@ package edu.ntnu.idi.idatt2003.gruppe42.Model; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -26,6 +27,16 @@ void addShareTest() { assertFalse(portfolio.addShare(null)); } + @Test + void testAddShareMergesCorrectly() { + portfolio.addShare(share); + Share share2 = new Share(stock, new BigDecimal("5"), new BigDecimal("20000")); + portfolio.addShare(share2); + + assertEquals(1, portfolio.getShares().size()); + assertEquals(0, new BigDecimal("15").compareTo(portfolio.getShares().get(0).getQuantity())); + } + @Test void removeShareTest() { portfolio.addShare(share);