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 50880df..2a1603e 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 @@ -37,6 +37,8 @@ public final class StockAppController implements AppController { /** Called when the player clicks Trade during a weekend. */ private Runnable onMarketClosed; + private Runnable onInsufficientFunds; + /** * Constructs a new StockAppController. * @@ -199,6 +201,15 @@ public void setOnMarketClosed(final Runnable callback) { this.onMarketClosed = callback; } + /** + * Registers the callback invoked when the player has insufficient funds. + * + * @param callback the action to run (typically shows an insufficient-funds warning) + */ + public void setOnInsufficientFunds(final Runnable callback) { + this.onInsufficientFunds = callback; + } + /** * Handles the trade button action based on the spinner value. */ @@ -218,7 +229,11 @@ private void handleTransaction() { } BigDecimal quantity = new BigDecimal(Math.abs(value)); if (value > 0) { - handleBuy(currentStock, quantity); + try{ + handleBuy(currentStock, quantity); + } catch (Exception e) { + onInsufficientFunds.run(); + } } else { handleSell(currentStock, quantity); } @@ -230,16 +245,12 @@ private void handleTransaction() { * @param stock the stock to buy * @param quantity the quantity to buy */ - private void handleBuy(final Stock stock, final BigDecimal quantity) { - try { - Transaction transaction = marketController.getExchange() - .buy(stock.getSymbol(), quantity, player); - if (transaction != null) { - transaction.commit(player); - player.getPortfolio().addShare(transaction.getShare()); - } - } catch (Exception exception) { - exception.printStackTrace(); + private void handleBuy(final Stock stock, final BigDecimal quantity) throws Exception { + Transaction transaction = marketController.getExchange() + .buy(stock.getSymbol(), quantity); + if (transaction != null) { + transaction.commit(player); + player.getPortfolio().addShare(transaction.getShare()); } } @@ -253,17 +264,22 @@ private void handleSell(final Stock stock, final BigDecimal quantity) { player.getPortfolio().getShares().stream() .filter(share -> share.getStock().getSymbol().equals(stock.getSymbol())) .findFirst() - .ifPresent(existingShare -> { - if (existingShare.getQuantity().compareTo(quantity) < 0) { - return; - } - Transaction transaction = marketController.getExchange() - .sell(existingShare, quantity, player); - if (transaction != null) { - transaction.commit(player); - player.getPortfolio().removeShare(transaction.getShare()); - } - }); + .ifPresent( + existingShare -> { + if (existingShare.getQuantity().compareTo(quantity) < 0) { + return; + } + Transaction transaction = + marketController.getExchange().sell(existingShare, quantity, player); + if (transaction != null) { + try { + transaction.commit(player); + } catch (Exception e) { + System.out.println("Transaction failed"); + } + player.getPortfolio().removeShare(transaction.getShare()); + } + }); } /** 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 cb4b47a..201c4f3 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 @@ -52,7 +52,7 @@ public void setGameState(GameState gameState) { public void startGame() { timer = new Timer(true); final int delay = 0; - final int period = 10; //TODO: This has to be 1000 (1 second) on release + final int period = 1000; //TODO: This has to be 1000 (1 second) on release timer.scheduleAtFixedRate( new TimerTask() { 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 0e776d9..0e7091c 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 @@ -101,6 +101,7 @@ public DesktopViewController(final Player player, final GameController gameContr }); stockAppController.setOnMarketClosed(this::showMarketClosedWarning); + stockAppController.setOnInsufficientFunds(this::showInsufficientFundsWarning); this.desktopView = new DesktopView(this); BorderPane root = desktopView.getRoot(); @@ -208,6 +209,16 @@ private void showMarketClosedWarning() { showWarning(); } + private void showInsufficientFundsWarning() { + warningApp.configure( + "\uD83E\uDD7A", + "Insufficient Funds", + "You don't have enough cash to complete this purchase.", + "I'll be back"); + warningApp.getPrimaryButton().setOnAction(event -> dismissWarning()); + showWarning(); + } + private void showWarning() { warningApp.show(); desktopView.enterLayer(ModalLayer.WARNING); 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 8ff47df..f703b3d 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 @@ -99,13 +99,11 @@ public List getAllStocks() { * * @param symbol the symbol of the stock to buy * @param quantity the amount of stock to buy - * @param player the player performing the purchase * @return a {@code Purchase} transaction */ public Transaction buy( final String symbol, - final BigDecimal quantity, - final Player player + final BigDecimal quantity ) { Stock stock = getStock(symbol); BigDecimal purchasePrice = stock.getSalesPrice(); 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 71e71b7..61923ff 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 @@ -104,7 +104,7 @@ public void addMoney(final BigDecimal amount) { * @param amount the amount to withdraw * @throws InsufficientFunds if the player does not have enough money */ - public void withdrawMoney(final BigDecimal amount) { + public void withdrawMoney(final BigDecimal amount) throws InsufficientFunds { if (money.compareTo(amount) < 0) { throw new InsufficientFunds("Not enough money to withdraw " + amount); } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Transaction/Purchase.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Transaction/Purchase.java index 5aaffcb..e30b711 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Transaction/Purchase.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Transaction/Purchase.java @@ -22,7 +22,7 @@ public Purchase(final Share share, final int week) { } @Override - public void commit(final Player player) { + public void commit(final Player player) throws Exception { setCommitted(true); player.withdrawMoney(getCalculator().calculateTotal()); player.getTransactionArchive().add(this); diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Transaction/Transaction.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Transaction/Transaction.java index 9a95857..c843781 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Transaction/Transaction.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Transaction/Transaction.java @@ -72,7 +72,7 @@ public boolean isCommitted() { * * @param player the player performing the transaction */ - public abstract void commit(Player player); + public abstract void commit(Player player) throws Exception; /** * Sets the committed status of the transaction. 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 4890724..8b8ae9f 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 @@ -15,6 +15,7 @@ import javafx.scene.layout.ColumnConstraints; import javafx.scene.layout.GridPane; import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; import javafx.scene.layout.VBox; import javafx.scene.paint.Color; @@ -34,6 +35,9 @@ public class BankApp extends Popup { private Consumer onShareSelected; + /** + * Constructs the Bank app popup. + */ public BankApp() { super(500, 550, 220, 100, App.BANK); @@ -55,18 +59,40 @@ private void buildLayout() { private VBox buildSummaryCard() { Label title = new Label("Total Balance"); title.getStyleClass().add("bank-summary-title"); + title.setMinWidth(Region.USE_PREF_SIZE); + netWorthLabel.getStyleClass().add("bank-summary-label-bold"); + netWorthLabel.setMinWidth(Region.USE_PREF_SIZE); VBox balanceBox = new VBox(5, title, netWorthLabel); balanceBox.setAlignment(Pos.CENTER_LEFT); cashLabel.getStyleClass().add("bank-summary-label-normal"); + cashLabel.setMinWidth(Region.USE_PREF_SIZE); + investedLabel.getStyleClass().add("bank-summary-label-normal"); + investedLabel.setMinWidth(Region.USE_PREF_SIZE); + totalReturnLabel.getStyleClass().add("bank-summary-label-normal"); + totalReturnLabel.setMinWidth(Region.USE_PREF_SIZE); GridPane grid = new GridPane(); grid.setHgap(40); grid.setVgap(15); + + ColumnConstraints c0 = new ColumnConstraints(); + c0.setMinWidth(Region.USE_PREF_SIZE); + c0.setHgrow(Priority.ALWAYS); + + ColumnConstraints c1 = new ColumnConstraints(); + c1.setMinWidth(Region.USE_PREF_SIZE); + c1.setHgrow(Priority.ALWAYS); + + ColumnConstraints c2 = new ColumnConstraints(); + c2.setMinWidth(Region.USE_PREF_SIZE); + c2.setHgrow(Priority.ALWAYS); + + grid.getColumnConstraints().addAll(c0, c1, c2); grid.add(summaryItem("Cash Available", cashLabel), 0, 0); grid.add(summaryItem("Invested", investedLabel), 1, 0); grid.add(summaryItem("Total Return", totalReturnLabel), 2, 0); @@ -79,6 +105,7 @@ private VBox buildSummaryCard() { private VBox buildPortfolioSection() { Label title = new Label("Investment Portfolio"); title.getStyleClass().add("bank-portfolio-title"); + title.setMinWidth(Region.USE_PREF_SIZE); portfolioList.setPrefHeight(300); portfolioList.setMinHeight(200); @@ -93,6 +120,7 @@ private VBox buildPortfolioSection() { private VBox summaryItem(String title, Label valueLabel) { Label titleLabel = new Label(title); titleLabel.getStyleClass().add("bank-summary-item-title"); + titleLabel.setMinWidth(Region.USE_PREF_SIZE); VBox box = new VBox(5, titleLabel, valueLabel); box.setAlignment(Pos.CENTER_LEFT); @@ -105,7 +133,7 @@ private VBox summaryItem(String title, Label valueLabel) { * @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 + * @param startingMoney initial balance used as the growth baseline */ public void updateStatus(double netWorth, double cash, double invested, double startingMoney) { Platform.runLater(() -> { @@ -120,10 +148,20 @@ public void updateStatus(double netWorth, double cash, double invested, double s }); } + /** + * Returns the portfolio list view. + * + * @return the portfolio list + */ public ListView getPortfolioList() { return portfolioList; } + /** + * Sets the callback invoked when the user clicks a share row. + * + * @param onShareSelected the callback to invoke with the selected share + */ public void setOnShareSelected(Consumer onShareSelected) { this.onShareSelected = onShareSelected; } @@ -147,22 +185,19 @@ private static String formatUsd(double amount) { */ private class ShareCell extends ListCell { - private final GridPane grid = new GridPane(); + private static final double COL_SYMBOL_MIN = 120; + private static final double COL_QUANTITY_MIN = 100; + private static final double COL_PRICE_MIN = 90; + private static final double COL_GAIN_MIN = 90; - // Column 0 - private final Label symbolLabel = new Label(); + private final GridPane grid = new GridPane(); + private final Label symbolLabel = new Label(); private final Label companyLabel = new Label(); - - // Column 1 private final Label quantityLabel = new Label(); private final Label avgPriceLabel = new Label(); - - // Column 2 private final Label currentPriceLabel = new Label(); - private final Label marketValueLabel = new Label(); - - // Column 3 - private final Label gainLabel = new Label(); + private final Label marketValueLabel = new Label(); + private final Label gainLabel = new Label(); private final Label gainPctLabel = new Label(); ShareCell() { @@ -180,30 +215,58 @@ private class ShareCell extends ListCell { } private void configureColumns() { - for (int pct : new int[]{30, 20, 25, 25}) { - ColumnConstraints col = new ColumnConstraints(); - col.setPercentWidth(pct); - grid.getColumnConstraints().add(col); - } + ColumnConstraints colSymbol = new ColumnConstraints(); + colSymbol.setMinWidth(COL_SYMBOL_MIN); + colSymbol.setHgrow(Priority.ALWAYS); + + ColumnConstraints colQuantity = new ColumnConstraints(); + colQuantity.setMinWidth(COL_QUANTITY_MIN); + colQuantity.setHgrow(Priority.ALWAYS); + + ColumnConstraints colPrice = new ColumnConstraints(); + colPrice.setMinWidth(COL_PRICE_MIN); + colPrice.setHgrow(Priority.NEVER); + + ColumnConstraints colGain = new ColumnConstraints(); + colGain.setMinWidth(COL_GAIN_MIN); + colGain.setHgrow(Priority.NEVER); + + grid.getColumnConstraints().addAll(colSymbol, colQuantity, colPrice, colGain); } private void addCellContent() { symbolLabel.getStyleClass().add("stock-symbol"); + symbolLabel.setMinWidth(Region.USE_PREF_SIZE); + companyLabel.getStyleClass().add("stock-company"); + companyLabel.setMinWidth(Region.USE_PREF_SIZE); + grid.add(new VBox(2, symbolLabel, companyLabel), 0, 0); quantityLabel.getStyleClass().add("bank-share-quantity"); + quantityLabel.setMinWidth(Region.USE_PREF_SIZE); + avgPriceLabel.getStyleClass().add("bank-share-avg-price"); + avgPriceLabel.setMinWidth(Region.USE_PREF_SIZE); + grid.add(new VBox(2, quantityLabel, avgPriceLabel), 1, 0); currentPriceLabel.getStyleClass().add("bank-share-current-price"); + currentPriceLabel.setMinWidth(Region.USE_PREF_SIZE); + marketValueLabel.getStyleClass().add("bank-share-value"); + marketValueLabel.setMinWidth(Region.USE_PREF_SIZE); + VBox valBox = new VBox(2, currentPriceLabel, marketValueLabel); valBox.setAlignment(Pos.CENTER_RIGHT); grid.add(valBox, 2, 0); gainLabel.getStyleClass().add("bank-share-gain"); + gainLabel.setMinWidth(Region.USE_PREF_SIZE); + gainPctLabel.getStyleClass().add("bank-share-gain"); + gainPctLabel.setMinWidth(Region.USE_PREF_SIZE); + VBox gainBox = new VBox(2, gainLabel, gainPctLabel); gainBox.setAlignment(Pos.CENTER_RIGHT); grid.add(gainBox, 3, 0); @@ -238,10 +301,10 @@ private void populateLabels(Share share) { avgPriceLabel.setText("Avg: $" + String.format("%,.2f", purchasePrice)); currentPriceLabel.setText("$" + String.format("%,.2f", currentPrice)); - marketValueLabel.setText("$" + String.format("%,.2f", marketValue)); + marketValueLabel.setText("$" + String.format("%,.2f", marketValue)); String sign = gain.compareTo(BigDecimal.ZERO) >= 0 ? "+" : ""; - gainLabel.setText(sign + "$" + String.format("%,.2f", gain)); + gainLabel.setText(sign + "$" + String.format("%,.2f", gain)); gainPctLabel.setText(sign + String.format("%.2f", gainPercent) + "%"); applyGainStyle(gain.compareTo(BigDecimal.ZERO) >= 0); 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 69df799..6bc423f 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 @@ -6,6 +6,7 @@ import edu.ntnu.idi.idatt2003.gruppe42.View.Popup; import edu.ntnu.idi.idatt2003.gruppe42.View.Views.Components.Receipt; 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; @@ -116,12 +117,13 @@ public void showStockPage(final Stock stock) { StockGraph graph = stock.getStockGraph(); VBox.setVgrow(graph, Priority.ALWAYS); + receipt.update(stock, new BigDecimal(quantitySpinner.getValue())); HBox receiptCentered = new HBox(receipt); receiptCentered.setAlignment(Pos.CENTER); content.setSpacing(20); - content.setPadding(new Insets(20)); content.getChildren().setAll(topControls, headerBox, priceLabel, graph, receiptCentered); + } /** 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 cc9a3b5..e2a19b3 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 @@ -197,11 +197,4 @@ public Button getCloseButton() { return closeButton; } - public int getWidth() { - return width; - } - - public int getHeight() { - return height; - } } \ No newline at end of file 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 930b618..6fe0029 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 @@ -30,7 +30,6 @@ public Receipt(final Player player) { setAlignment(Pos.TOP_CENTER); setSpacing(8); setMaxWidth(320); - renderEmpty(); } /** @@ -61,12 +60,10 @@ public static Receipt forTransaction(final Transaction tx) { /** * Re-renders as a live preview for the given stock and signed quantity. - * A positive quantity is a BUY; negative is a SELL. Zero or null clears - * the receipt back to its empty placeholder. + * A positive quantity is a BUY; negative is a SELL. */ public void update(final Stock stock, final BigDecimal quantity) { - if (stock == null || quantity == null || quantity.signum() == 0) { - renderEmpty(); + if (stock == null || quantity == null) { return; } boolean isBuy = quantity.signum() > 0; @@ -106,16 +103,6 @@ private BigDecimal estimateSaleTax(final Stock stock, final BigDecimal qty) { return profit.multiply(TAX_RATE).setScale(2, RoundingMode.HALF_UP); } - /** Empty placeholder receipt shown before the user picks a stock. */ - private void renderEmpty() { - getChildren().clear(); - getChildren().addAll( - headerLabel("RECEIPT"), - ruleStrong(), - receiptLine("Select a stock"), - ruleStrong() - ); - } /** * Renders the canonical receipt layout with the given values. 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 c324094..44b0d3f 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 @@ -50,7 +50,6 @@ public enum ModalLayer { RAPPORT, WARNING, GAME_OVER } private static final int ROWS = 6; private static final int HEART_COUNT = 3; private static final double BACKDROP_BLUR = 8.0; - private static final String FREEDAY = "Freeday"; private final DesktopViewController controller; private final BorderPane root;