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 c012783..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,38 +2,35 @@ 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}. * Handles updating the player's financial status and portfolio view. */ -public final class BankAppController implements AppController { - private final Player player; - private final BankApp bankApp; - +public record BankAppController(BankApp bankApp, Player player) implements AppController { /** * Constructs a new BankAppController. * * @param bankApp the bank app view * @param player the player model */ - public BankAppController(final BankApp bankApp, final Player player) { - this.player = player; - this.bankApp = bankApp; + public BankAppController { } @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 4a252eb..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,14 +98,9 @@ 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 e) { - System.out.println("[DEBUG] Buy failed with exception: " - + e.getMessage()); - e.printStackTrace(); + } catch (Exception exception) { + 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 66622b2..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 @@ -12,11 +12,8 @@ * Controller for managing the game state and timing. */ public final class GameController { - /** The player model. */ private final Player player; - /** List of application controllers. */ private final List appControllers = new ArrayList<>(); - /** Timer for game ticks. */ private Timer timer; /** @@ -41,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; @@ -49,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 68b9f9c..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 @@ -4,10 +4,9 @@ import edu.ntnu.idi.idatt2003.gruppe42.Model.Stock; import edu.ntnu.idi.idatt2003.gruppe42.Model.StockFileHandler; import edu.ntnu.idi.idatt2003.gruppe42.View.Apps.StockApp; -import javafx.application.Platform; - import java.nio.file.Path; import java.util.List; +import java.util.Objects; /** * Controller for the stock market. @@ -25,14 +24,14 @@ public MarketController() { this.stockResolution = 50; try { - Path path = Path.of(getClass().getClassLoader() - .getResource("stocks.csv").toURI()); + Path path = Path.of(Objects.requireNonNull(getClass().getClassLoader() + .getResource("stocks.csv")).toURI()); exchange = new Exchange(StockFileHandler.readFromFile(path)); startMarket(); System.out.println("Market loaded"); - } catch (Exception e) { + } catch (Exception exception) { System.out.println("File not found"); } } @@ -55,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/TimeAndWeatherController.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/TimeAndWeatherController.java new file mode 100644 index 0000000..c73f6b9 --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Controller/TimeAndWeatherController.java @@ -0,0 +1,87 @@ +package edu.ntnu.idi.idatt2003.gruppe42.Controller; + +import edu.ntnu.idi.idatt2003.gruppe42.Controller.AppControllers.AppController; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.Random; +import javafx.beans.property.IntegerProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; + +/** + * Controller for managing game time and weather. + * Advances 1 hour per tick. + */ +public class TimeAndWeatherController implements AppController { + private final IntegerProperty hour = new SimpleIntegerProperty(0); + private final IntegerProperty dayIndex = new SimpleIntegerProperty(0); // 0 = Sunday, 1 = Monday, etc. + private final IntegerProperty temperature = new SimpleIntegerProperty(15); + private final StringProperty weather = new SimpleStringProperty("Sunny"); + private final Random random = new Random(); + + private static final String[] DAYS = {"SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"}; + + public TimeAndWeatherController() { + updateWeather(); + } + + @Override + public void nextTick() { + int nextHour = hour.get() + 1; + if (nextHour >= 24) { + hour.set(0); + dayIndex.set((dayIndex.get() + 1) % 7); + updateWeather(); + } else { + hour.set(nextHour); + } + } + + private void updateWeather() { + // Basic seasonal/random temperature logic + // Range from -10 to 30 + int change = random.nextInt(11) - 5; // -5 to +5 + int newTemp = temperature.get() + change; + if (newTemp < -10) newTemp = -10; + if (newTemp > 30) newTemp = 30; + temperature.set(newTemp); + + if (newTemp <= 0) { + weather.set("Snow"); + } else { + // If warm, could be sunny or rain + if (random.nextBoolean()) { + weather.set("Sunny"); + } else { + weather.set("Rain"); + } + } + } + + public IntegerProperty hourProperty() { + return hour; + } + + public IntegerProperty dayIndexProperty() { + return dayIndex; + } + + public IntegerProperty temperatureProperty() { + return temperature; + } + + public StringProperty weatherProperty() { + return weather; + } + + public String getTimeString() { + return String.format("%02d:00", hour.get()); + } + + public String getDayOfWeekString() { + return DAYS[dayIndex.get()]; + } +} 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 6c5278e..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 @@ -6,6 +6,7 @@ import edu.ntnu.idi.idatt2003.gruppe42.Controller.GameController; import edu.ntnu.idi.idatt2003.gruppe42.Controller.MarketController; import edu.ntnu.idi.idatt2003.gruppe42.Controller.PopupController; +import edu.ntnu.idi.idatt2003.gruppe42.Controller.TimeAndWeatherController; import edu.ntnu.idi.idatt2003.gruppe42.Model.App; import edu.ntnu.idi.idatt2003.gruppe42.Model.Player; import edu.ntnu.idi.idatt2003.gruppe42.View.Apps.AppStoreApp; @@ -32,10 +33,10 @@ * 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; + private final Player player; + private final TimeAndWeatherController timeAndWeatherController; /** * Constructs a new desktop view controller. @@ -47,25 +48,26 @@ public DesktopViewController( final Player player, final GameController gameController ) { - List popups = new ArrayList<>(); + this.player = player; + this.timeAndWeatherController = new TimeAndWeatherController(); + gameController.addAppController(timeAndWeatherController); - final int appWidth = 400; - final int appHeight = 300; + List popups = new ArrayList<>(); - AppStoreApp appStoreApp = new AppStoreApp(appWidth, appHeight, 100, 100); + AppStoreApp appStoreApp = new AppStoreApp(); gameController.addAppController(new AppStoreController(appStoreApp)); - HustlersApp hustlersApp = new HustlersApp(appWidth, appHeight, 140, 140); - - StockApp stockApp = new StockApp(appWidth, appHeight, 120, 120); + HustlersApp hustlersApp = new HustlersApp(); + + StockApp stockApp = new StockApp(player); MarketController marketController = new MarketController(); gameController.addAppController( new StockAppController(stockApp, marketController, player) ); - MailApp mailApp = new MailApp(appWidth, appHeight, 160, 160); + MailApp mailApp = new MailApp(); - NewsApp newsApp = new NewsApp(appWidth, appHeight, 180, 180); + NewsApp newsApp = new NewsApp(); BankApp bankApp = new BankApp(); gameController.addAppController(new BankAppController(bankApp, player)); @@ -78,6 +80,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( @@ -86,6 +94,24 @@ public DesktopViewController( ); } + /** + * Returns the player model. + * + * @return the player model + */ + public Player getPlayer() { + return player; + } + + /** + * Returns the time and weather controller. + * + * @return the time and weather controller + */ + public TimeAndWeatherController getTimeAndWeatherController() { + return timeAndWeatherController; + } + /** * Returns the desktop view. * @@ -102,7 +128,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; @@ -123,7 +152,6 @@ public Button createAppButton(final App type) { // Print when clicked and open popup button.setOnAction(event -> { - System.out.println("button pressed: " + type); openPopup(type); }); @@ -142,6 +170,13 @@ public Button createAppButton(final App type) { return button; } + /** + * Handles the settings button action. + */ + public void handleSettings() { + // Placeholder for settings logic + } + /** * Opens the correct popup based on the app type. * 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 9a3eb7d..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,14 +45,22 @@ 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); } private String resolveUsername(String input) { - if (username == null || username.trim().isEmpty() || !isValidName(username)) { - username = TATE_NAMES.get(new Random().nextInt(TATE_NAMES.size())); + if (input == null || input.trim().isEmpty() || !isValidName(input)) { + return TATE_NAMES.get(new Random().nextInt(TATE_NAMES.size())); } return input; } 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/Calculator/PurchaseCalculator.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Calculator/PurchaseCalculator.java index cfc8c11..ca65050 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Calculator/PurchaseCalculator.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Calculator/PurchaseCalculator.java @@ -7,9 +7,7 @@ * Calculator for stock purchase transactions. */ public final class PurchaseCalculator implements TransactionCalculator { - /** The price per share at purchase. */ private final BigDecimal purchasePrice; - /** The number of shares bought. */ private final BigDecimal quantity; /** diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Calculator/SaleCalculator.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Calculator/SaleCalculator.java index 69ac0b5..1925e88 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Calculator/SaleCalculator.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Calculator/SaleCalculator.java @@ -7,11 +7,8 @@ * Calculator for stock sale transactions. */ public final class SaleCalculator implements TransactionCalculator { - /** The price per share at purchase. */ private final BigDecimal purchasePrice; - /** The current price per share at sale. */ private final BigDecimal salesPrice; - /** The number of shares sold. */ private final BigDecimal quantity; /** 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 f1706a1..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 @@ -23,8 +23,7 @@ public class Exchange { private int week; /** Map of stock symbols to Stock objects. */ private final Map stockMap; - /** Random number generator for simulation events. */ - private final Random random; + /** * Constructs a new {@code Exchange} with a list of stocks. @@ -34,7 +33,6 @@ public class Exchange { public Exchange(final List stocks) { this.week = 0; this.stockMap = new HashMap(); - this.random = new Random(); for (Stock stock : stocks) { stockMap.put(stock.getSymbol(), stock); } @@ -110,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); } @@ -134,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/Model/Transaction/Transaction.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Model/Transaction/Transaction.java index f3d8c09..9a95857 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 @@ -8,13 +8,9 @@ * Abstract base class for financial transactions. */ public abstract class Transaction { - /** The share position involved in the transaction. */ private final Share share; - /** The week the transaction occurred. */ private final int week; - /** The calculator used for this transaction. */ private final TransactionCalculator calculator; - /** Whether the transaction has been committed. */ private boolean committed = false; /** diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Apps/AppStoreApp.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Apps/AppStoreApp.java index c37bcd8..9eeeaad 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Apps/AppStoreApp.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Apps/AppStoreApp.java @@ -4,21 +4,16 @@ import edu.ntnu.idi.idatt2003.gruppe42.View.Popup; /** - * A popup for the AppStore app. + * A popup for the app store app. */ public class AppStoreApp extends Popup { /** - * Constructs a new AppStore popup. - * - * @param width width of the popup - * @param height height of the popup - * @param x x-position of the popup - * @param y y-position of the popup + * Constructs a new app store popup. */ - public AppStoreApp(int width, int height, int x, int y) { - super(width, height, x, y, App.APPSTORE); - // Add AppStore specific content here + public AppStoreApp() { + super(400, 300, 100, 100, App.APPSTORE); + // Add App store specific content here } } 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 4ffea33..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,114 +1,257 @@ 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.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.VBox; +import javafx.scene.paint.Color; /** - * 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("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 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(400, 300, 120, 120, App.BANK); - this.portfolioList = new ListView<>(); - updateContent(); + super(500, 550, 220, 100, App.BANK); + + portfolioList = new ListView<>(); + portfolioList.setSelectionModel(null); + portfolioList.getStyleClass().add("portfolio-list"); + portfolioList.setCellFactory(lv -> new ShareCell()); + + content.setPadding(new Insets(20)); + content.setSpacing(20); + buildLayout(); + } + + private void buildLayout() { + content.getChildren().clear(); + content.getChildren().addAll(buildSummaryCard(), buildPortfolioSection()); + } + + private VBox buildSummaryCard() { + Label title = new Label("Total Balance"); + title.getStyleClass().add("bank-summary-title"); + netWorthLabel.getStyleClass().add("bank-summary-label-bold"); + + 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); + + VBox card = new VBox(20, balanceBox, grid); + card.getStyleClass().add("bank-summary-card"); + return card; } - private void updateContent() { - GridPane statusPane = new GridPane(); - - final int col0 = 0; - final int col1 = 1; - final int row0 = 0; - final int row1 = 1; - - statusPane.add(netWorthLabel, col0, row0); - statusPane.add(unInvestedMoneyLabel, col0, row1); - statusPane.add(investedMoneyLabel, col1, row1); - statusPane.add(growthPercentageLabel, col1, row0); - - portfolioList.setCellFactory( - lv -> - new ListCell() { - @Override - protected void updateItem(final Share share, final boolean empty) { - super.updateItem(share, empty); - if (empty || share == null) { - setGraphic(null); - } else { - GridPane sharePane = new GridPane(); - final int col0 = 0; - final int col1 = 1; - final int row0 = 0; - final int row1 = 1; - final int row2 = 2; - final int row3 = 3; - - sharePane.add(new Label(share.getStock().getCompany()), - col0, row0); - sharePane.add(new Label("Purchase"), col1, row0); - sharePane.add(new Label(share.getStock().getSymbol()), - col0, row1); - sharePane.add(new Label(share.getPurchasePrice().toString()), - col1, row1); - sharePane.add(new Label("Quantity"), col0, row2); - sharePane.add(new Label("Sale"), col1, row2); - sharePane.add(new Label(share.getQuantity().toString()), - col0, row3); - sharePane.add(new Label(share.getStock().getSalesPrice() - .toString()), col1, row3); - sharePane.setAlignment(Pos.CENTER_LEFT); - setGraphic(sharePane); - } - } - }); - - content.getChildren().setAll(statusPane, portfolioList); + private VBox buildPortfolioSection() { + Label title = new Label("Investment Portfolio"); + title.getStyleClass().add("bank-portfolio-title"); + + portfolioList.setPrefHeight(300); + portfolioList.setMinHeight(200); + VBox.setVgrow(portfolioList, Priority.ALWAYS); + + VBox section = new VBox(10, title, portfolioList); + section.setPadding(new Insets(10, 0, 0, 0)); + VBox.setVgrow(section, Priority.ALWAYS); + return section; + } + + private VBox summaryItem(String title, Label valueLabel) { + Label titleLabel = new Label(title); + titleLabel.getStyleClass().add("bank-summary-item-title"); + + 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 growthPercentage growth since start + * @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 growthPercentage - ) { + public void updateStatus(double netWorth, double cash, double invested, double startingMoney) { Platform.runLater(() -> { - netWorthLabel.setText("Net Worth: " + netWorth); - unInvestedMoneyLabel.setText("Cash: " + unInvestedMoney); - investedMoneyLabel.setText("Stock: " + investedMoney); - growthPercentageLabel.setText("Growth: " + growthPercentage + "%"); - updateContent(); + 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")); }); } public ListView getPortfolioList() { return portfolioList; } -} + 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/Apps/HustlersApp.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Apps/HustlersApp.java index 7467c1d..ec8981f 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Apps/HustlersApp.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Apps/HustlersApp.java @@ -10,14 +10,9 @@ public class HustlersApp extends Popup { /** * Constructs a new Hustlers popup. - * - * @param width width of the popup - * @param height height of the popup - * @param x x-position of the popup - * @param y y-position of the popup */ - public HustlersApp(int width, int height, int x, int y) { - super(width, height, x, y, App.HUSTLERS); + public HustlersApp() { + super(400, 300, 140, 140, App.HUSTLERS); // Add Hustlers specific content here } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Apps/MailApp.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Apps/MailApp.java index 3b31ac3..74c44bc 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Apps/MailApp.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Apps/MailApp.java @@ -11,13 +11,9 @@ public class MailApp extends Popup { /** * Constructs a new Mail popup. * - * @param width width of the popup - * @param height height of the popup - * @param x x-position of the popup - * @param y y-position of the popup */ - public MailApp(int width, int height, int x, int y) { - super(width, height, x, y, App.MAIL); + public MailApp() { + super(400, 300, 140, 140, App.MAIL); // Add Mail specific content here } diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Apps/NewsApp.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Apps/NewsApp.java index ac57fe8..e8235d3 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Apps/NewsApp.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/View/Apps/NewsApp.java @@ -10,14 +10,9 @@ public class NewsApp extends Popup { /** * Constructs a new News popup. - * - * @param width width of the popup - * @param height height of the popup - * @param x x-position of the popup - * @param y y-position of the popup */ - public NewsApp(int width, int height, int x, int y) { - super(width, height, x, y, App.NEWS); - // Add News specific content here + public NewsApp() { + super(400, 300, 180, 180, App.NEWS); + // Add news-specific content here } } 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 680c0f7..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,52 +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 width width of the popup - * @param height height of the popup - * @param x x-position of the popup - * @param y y-position of the popup + * @param player the player performing transactions */ - public StockApp(final int width, final int height, final int x, final int y) { - super(width, height, x, y, 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 = -1000; - final int maxQuantity = 1000; + 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(); } /** @@ -78,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 ); } @@ -107,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())) ); } } @@ -130,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 3553490..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,34 +10,28 @@ 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. * Handles dragging, layout, and common components. */ public abstract class Popup { - /** Width of the popup. */ private final int width; - /** Height of the popup. */ private final int height; - /** X-offset for dragging. */ private double dx; - /** Y-offset for dragging. */ private double dy; - /** The app type of the popup. */ private final App type; /** Root layout node. */ private final BorderPane root; - /** Scrollable container for content. */ - private final ScrollPane scrollPane; - /** Header container. */ private final HBox header; - /** Close button. */ private final Button closeButton; - /** Content container. */ protected final VBox content; /** @@ -59,51 +55,98 @@ protected Popup( this.type = type; root = new BorderPane(); - scrollPane = new ScrollPane(); - scrollPane.setStyle("-fx-hbar-policy: never; -fx-vbar-policy: AS_NEEDED; " - + "-fx-background-insets: 0; -fx-padding: 0;"); + root.getStyleClass().add("popup-root"); + + ScrollPane scrollPane = new ScrollPane(); + 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() { - root.addEventFilter(MouseEvent.MOUSE_PRESSED, e -> root.toFront()); + root.addEventFilter(MouseEvent.MOUSE_PRESSED, mouseEvent -> root.toFront()); } private void makeDraggable() { - header.setOnMousePressed(e -> { - dx = e.getSceneX() - root.getLayoutX(); - dy = e.getSceneY() - root.getLayoutY(); + header.setOnMousePressed(mouseEvent -> { + dx = mouseEvent.getSceneX() - root.getLayoutX(); + dy = mouseEvent.getSceneY() - root.getLayoutY(); }); - header.setOnMouseDragged(e -> { - root.setLayoutX(e.getSceneX() - dx); - root.setLayoutY(e.getSceneY() - dy); + header.setOnMouseDragged(mouseEvent -> { + root.setLayoutX(mouseEvent.getSceneX() - dx); + root.setLayoutY(mouseEvent.getSceneY() - dy); keepInBounds(); }); } 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 08578ee..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,26 +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 Stock stock; - private BigDecimal quantity; + 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); } @@ -31,31 +39,72 @@ public Receipt(final Stock stock, final BigDecimal quantity) { * @param quantity the quantity */ public void update(final Stock stock, final BigDecimal quantity) { - this.stock = stock; - this.quantity = 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(); - - final int col0 = 0; - final int col1 = 1; - final int row0 = 0; - final int row1 = 1; - final int row2 = 2; - final int row3 = 3; - - receiptPane.add(new Label(stock.getCompany()), col0, row0); - receiptPane.add(new Label("Quantity"), col1, row0); - receiptPane.add(new Label(stock.getSymbol()), col0, row1); - receiptPane.add(new Label(quantity.toString()), col1, row1); - receiptPane.add(new Label("Price"), col0, row2); - receiptPane.add(new Label("Amount"), col1, row2); - receiptPane.add(new Label(stock.getSalesPrice().toString()), col0, row3); - receiptPane.add(new Label((stock.getSalesPrice().multiply(quantity)) - .toString()), col1, row3); - receiptPane.setAlignment(Pos.CENTER); - this.getChildren().setAll(new Label("Receipt"), receiptPane); + + 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 total = type.equals("BUY") ? gross.add(commission) : gross.subtract(commission).subtract(tax); + + VBox totals = new VBox(5); + totals.getStyleClass().add("receipt-total"); + totals.setPadding(new Insets(10, 0, 0, 0)); + + 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 40b88a8..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 @@ -16,12 +16,12 @@ public final class StockGraph extends VBox { private final LineChart lineChart; private boolean isVisible = false; - private final int stockResolution = 50; /** * Constructs a new StockGraph. */ public StockGraph() { + this.getStyleClass().add("stock-graph-container"); NumberAxis xAxis = new NumberAxis(); xAxis.setLabel("Time"); xAxis.setAutoRanging(true); @@ -33,8 +33,8 @@ public StockGraph() { yAxis.setAutoRanging(true); lineChart = new LineChart<>(xAxis, yAxis); - final int prefSize = 100; - lineChart.setPrefSize(prefSize, prefSize); + lineChart.getStyleClass().add("stock-line-chart"); + lineChart.setPrefSize(400, 250); lineChart.setCreateSymbols(false); lineChart.setLegendVisible(false); lineChart.setAnimated(false); @@ -59,6 +59,7 @@ public boolean update(final Stock stock) { return false; } + int stockResolution = 50; List latestHistory = history.subList( Math.max(history.size() - stockResolution, 0), history.size() 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 7a01c33..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 @@ -1,28 +1,38 @@ package edu.ntnu.idi.idatt2003.gruppe42.View.Views; +import edu.ntnu.idi.idatt2003.gruppe42.Controller.TimeAndWeatherController; import edu.ntnu.idi.idatt2003.gruppe42.Controller.ViewControllers.DesktopViewController; import edu.ntnu.idi.idatt2003.gruppe42.Model.App; +import java.util.Objects; +import javafx.application.Platform; +import javafx.geometry.Insets; import javafx.geometry.Pos; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.image.Image; +import javafx.scene.layout.Background; +import javafx.scene.layout.BackgroundImage; +import javafx.scene.layout.BackgroundPosition; +import javafx.scene.layout.BackgroundRepeat; +import javafx.scene.layout.BackgroundSize; import javafx.scene.layout.BorderPane; 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.RowConstraints; import javafx.scene.layout.StackPane; /** * View for the desktop. - * Displays a 6x4 grid of apps. + * Displays a 10x6 grid of apps. */ public final class DesktopView { - /** Controller for the desktop. */ private final DesktopViewController desktopViewController; - /** Root layout node. */ private final BorderPane root; - /** Number of columns in the grid. */ - private static final int COLS = 6; - /** Number of rows in the grid. */ - private static final int ROWS = 4; + private static final int COLS = 10; + private static final int ROWS = 6; /** * Constructs a new DesktopView. @@ -40,13 +50,50 @@ public DesktopView(final DesktopViewController desktopViewController) { * @return the root of the desktop view. */ public BorderPane getRoot() { - GridPane grid = createGrid(); - root.setCenter(grid); + 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()); + + Image backgroundImage = new Image( + Objects.requireNonNull(getClass().getResourceAsStream("/Images/mac_home_background.jpg")), + 1920, 1080, + true, // preserve aspect ratio + true // smooth scaling + ); + + // Configure background to cover the full container without tiling + BackgroundSize backgroundSize = new BackgroundSize( + BackgroundSize.AUTO, BackgroundSize.AUTO, + false, + false, + false, + true + ); + + BackgroundImage background = new BackgroundImage( + backgroundImage, + BackgroundRepeat.NO_REPEAT, + BackgroundRepeat.NO_REPEAT, + BackgroundPosition.CENTER, + backgroundSize + ); + + root.setBackground(new Background(background)); + return root; } /** - * Creates the 6x4 grid with app buttons. + * Creates the 10x6 grid with app buttons. * * @return the grid pane. */ @@ -94,9 +141,83 @@ private GridPane createGrid() { */ private StackPane createCell() { StackPane cell = new StackPane(); - cell.setStyle("-fx-border-color: black; -fx-border-width: 1;"); desktopViewController.configureCellAsDropTarget(cell); return cell; } + /** + * Creates the bottom bar with settings, player name, and clock. + * + * @return the bottom bar HBox. + */ + private HBox createBottomBar() { + HBox bottomBar = new HBox(15); + bottomBar.setAlignment(Pos.CENTER_LEFT); + bottomBar.setPadding(new Insets(5, 15, 5, 15)); + bottomBar.getStyleClass().add("desktop-bottom-bar"); + + // Settings Button + Button settingsButton = new Button("⚙"); + settingsButton.getStyleClass().add("desktop-settings-button"); + settingsButton.setOnAction(e -> { + desktopViewController.handleSettings(); + }); + + // Player Name + Label playerNameLabel = new Label("User: " + desktopViewController.getPlayer().getName()); + playerNameLabel.getStyleClass().add("desktop-label-bold"); + playerNameLabel.setMinWidth(Region.USE_PREF_SIZE); + + // Spacer to push clock to the right + Region spacer = new Region(); + HBox.setHgrow(spacer, Priority.ALWAYS); + spacer.setMinWidth(20); + + // Time and Weather elements + TimeAndWeatherController twc = desktopViewController.getTimeAndWeatherController(); + + Label weatherLabel = new Label(); + weatherLabel.getStyleClass().add("desktop-label"); + + Label tempLabel = new Label(); + tempLabel.getStyleClass().add("desktop-label"); + + Label dayLabel = new Label(); + dayLabel.getStyleClass().add("desktop-label-bold"); + + Label clockLabel = new Label(); + clockLabel.getStyleClass().add("desktop-label-bold"); + + // Update labels when properties change + twc.hourProperty().addListener((obs, oldVal, newVal) -> Platform.runLater(() -> { + clockLabel.setText(twc.getTimeString()); + })); + + twc.dayIndexProperty().addListener((obs, oldVal, newVal) -> Platform.runLater(() -> { + dayLabel.setText(twc.getDayOfWeekString()); + })); + + twc.weatherProperty().addListener((obs, oldVal, newVal) -> Platform.runLater(() -> { + weatherLabel.setText(newVal); + })); + + twc.temperatureProperty().addListener((obs, oldVal, newVal) -> Platform.runLater(() -> { + tempLabel.setText(newVal + "°C"); + })); + + // Initial values + clockLabel.setText(twc.getTimeString()); + dayLabel.setText(twc.getDayOfWeekString()); + weatherLabel.setText(twc.weatherProperty().get()); + tempLabel.setText(twc.temperatureProperty().get() + "°C"); + + HBox rightInfo = new HBox(10); + rightInfo.setAlignment(Pos.CENTER_RIGHT); + rightInfo.setMinWidth(Region.USE_PREF_SIZE); + rightInfo.getChildren().addAll(weatherLabel, tempLabel, dayLabel, clockLabel); + + bottomBar.getChildren().addAll(settingsButton, playerNameLabel, spacer, rightInfo); + + return bottomBar; + } } 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 94924e6..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 @@ -8,6 +8,13 @@ import javafx.scene.control.ToggleGroup; import javafx.scene.image.Image; import javafx.scene.image.ImageView; +import javafx.scene.layout.Background; +import javafx.scene.layout.BackgroundImage; +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; @@ -16,48 +23,77 @@ 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(); public StackPane getRoot() { - StackPane root = new StackPane(); - // Main content - VBox content = new VBox(); - content.setAlignment(Pos.CENTER); + // Background image + Image backgroundImage = new Image(Objects.requireNonNull(getClass().getResourceAsStream("/Images/windows7_login_background.jpg")), 1920, 1080, true, true); + BackgroundImage background = new BackgroundImage( + backgroundImage, + BackgroundRepeat.NO_REPEAT, + BackgroundRepeat.NO_REPEAT, + BackgroundPosition.CENTER, + new BackgroundSize(BackgroundSize.AUTO, BackgroundSize.AUTO, false, false, true, true) + ); + root.setBackground(new Background(background)); - // 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/Images/mac_home_background.jpg b/src/main/resources/Images/mac_home_background.jpg new file mode 100644 index 0000000..73d60c5 Binary files /dev/null and b/src/main/resources/Images/mac_home_background.jpg differ diff --git a/src/main/resources/Images/windows7_login_background.jpg b/src/main/resources/Images/windows7_login_background.jpg new file mode 100644 index 0000000..258f2df Binary files /dev/null and b/src/main/resources/Images/windows7_login_background.jpg differ 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);