diff --git a/delete-storage.bat b/delete-storage.bat new file mode 100755 index 0000000..b2ed3f1 --- /dev/null +++ b/delete-storage.bat @@ -0,0 +1,26 @@ +@echo off +REM delete-storage.bat +REM Deletes the Millions JSON storage for resetting app state +REM The storage file location is chosen by StorageFile.java + +SET "STORAGE_FOLDER_NAME=Millions" +SET "STORAGE_FILE=storage.json" + +REM Use %APPDATA% if set, otherwise fallback to %USERPROFILE% +IF DEFINED APPDATA ( + SET "STORAGE_DIR=%APPDATA%\%STORAGE_FOLDER_NAME%" +) ELSE ( + SET "STORAGE_DIR=%USERPROFILE%\%STORAGE_FOLDER_NAME%" +) + +SET "STORAGE_PATH=%STORAGE_DIR%\%STORAGE_FILE%" + +IF EXIST "%STORAGE_PATH%" ( + ECHO Deleting storage at %STORAGE_PATH% ... + DEL /F "%STORAGE_PATH%" + ECHO Storage deleted. +) ELSE ( + ECHO Storage not found at %STORAGE_PATH%. +) + +PAUSE diff --git a/delete-storage.sh b/delete-storage.sh new file mode 100755 index 0000000..c387f85 --- /dev/null +++ b/delete-storage.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +# delete-storage.sh +# Deletes the Millions JSON storage for resetting app state +# The storage file location is chosen by StorageFile.java + +set -e + +STORAGE_FOLDER_NAME="Millions" +STORAGE_FILE="storage.json" + +OS=$(uname | tr '[:upper:]' '[:lower:]') +HOME_DIR="$HOME" + +# Determine database folder path based on OS +if [[ "$OS" == *"darwin"* ]]; then + # macOS + STORAGE_DIR="$HOME_DIR/Library/Application Support/$STORAGE_FOLDER_NAME" +elif [[ "$OS" == *"linux"* ]]; then + # Linux / Unix + STORAGE_DIR="$HOME_DIR/.local/share/$STORAGE_FOLDER_NAME" +elif [[ "$OS" == *"mingw"* || "$OS" == *"cygwin"* || "$OS" == *"msys"* ]]; then + # Windows (Git Bash, Cygwin, MSYS) + if [[ -n "$APPDATA" ]]; then + STORAGE_DIR="$APPDATA/$STORAGE_FOLDER_NAME" + else + STORAGE_DIR="$HOME_DIR/$STORAGE_FOLDER_NAME" + fi +else + echo "Unsupported OS: $OS" + exit 1 +fi + +STORAGE_PATH="$STORAGE_DIR/$STORAGE_FILE" + +if [[ -f "$STORAGE_PATH" ]]; then + echo "Deleting storage at $STORAGE_PATH ..." + rm "$STORAGE_PATH" + echo "Storage deleted." +else + echo "Storage not found at $STORAGE_PATH" +fi + diff --git a/src/main/java/edu/ntnu/idi/idatt/model/Exchange.java b/src/main/java/edu/ntnu/idi/idatt/model/Exchange.java index e41a2c0..db005da 100644 --- a/src/main/java/edu/ntnu/idi/idatt/model/Exchange.java +++ b/src/main/java/edu/ntnu/idi/idatt/model/Exchange.java @@ -1,7 +1,6 @@ package edu.ntnu.idi.idatt.model; import java.math.BigDecimal; -import java.math.RoundingMode; import java.util.*; import edu.ntnu.idi.idatt.model.market.Stock; @@ -195,11 +194,10 @@ public Transaction sell(Share share, Player player) { * @see Stock */ public void advance() { - this.week += 1; - Random random = new Random(); stockMap.values() - .forEach(s -> s.addNewSalesPrice(s.getSalesPrice() - .multiply(BigDecimal.valueOf(random.nextDouble(0.8, 1.4))).setScale(2, RoundingMode.HALF_UP))); + .forEach(stock -> stock.advancePrice()); + + this.week += 1; } } diff --git a/src/main/java/edu/ntnu/idi/idatt/model/Newspaper.java b/src/main/java/edu/ntnu/idi/idatt/model/Newspaper.java new file mode 100644 index 0000000..4651b30 --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt/model/Newspaper.java @@ -0,0 +1,61 @@ +package edu.ntnu.idi.idatt.model; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import edu.ntnu.idi.idatt.model.enums.NewspaperEnum; + +public class Newspaper { + + ArrayList news = new ArrayList<>(List.of(NewspaperEnum.NONE_EVENT)); + private static double chance = 0.05; // In percent + + public NewspaperEnum makeNews() { + Random r = new Random(); + double roll = r.nextDouble(); + + if (roll >= chance) { + news.add(NewspaperEnum.NONE_EVENT); + return NewspaperEnum.NONE_EVENT; + } + + int message = r.nextInt(NewspaperEnum.values().length - 1); // Do not include last + NewspaperEnum event = NewspaperEnum.values()[message]; + + news.add(event); + return event; + } + + public boolean hasNewNews() { + return news.getLast().equals(NewspaperEnum.NONE_EVENT) ? false : true; + } + + public ArrayList getNews() { + return news; + } + + public List getNewsStrings() { + + ArrayList strings = new ArrayList<>(); + + int i = 1; // First possible new happens at week 1 + for (NewspaperEnum newsEnum : news) { + + if (newsEnum == NewspaperEnum.NONE_EVENT) { + i++; + continue; + } + + strings.add(String.format("Week: %d - %s [%s]", + i, + newsEnum.getTitle(), + newsEnum.getDescription())); + i++; + + } + return strings; + + } + +} diff --git a/src/main/java/edu/ntnu/idi/idatt/model/enums/NewspaperEnum.java b/src/main/java/edu/ntnu/idi/idatt/model/enums/NewspaperEnum.java new file mode 100644 index 0000000..bc48f3e --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt/model/enums/NewspaperEnum.java @@ -0,0 +1,109 @@ +package edu.ntnu.idi.idatt.model.enums; + +public enum NewspaperEnum { + NEW_PRODUCT( + "New product announced!", + "The company revealed plans for a major new product launch next week!", + 0.08, + 0.02), + + QUARTER( + "Quarterly earnings released!", + "The company's quarterly earnings report exceeded analyst expectations!", + 0.05, + 0.03), + + DISASTER( + "Natural disaster struck!", + "A natural disaster disrupted several of the company's factories!", + -0.08, + 0.08), + + RESEARCH_FUNDS( + "Government research funding!", + "The government approved additional funding for the company's research division!", + 0.04, + 0.04), + + FRAUD( + "Fraud investigation!", + "The company's CEO is under investigation for potential fraud!", + -0.05, + 0.12), + + NEW_PARTNERSHIP( + "Major partnership formed!", + "The company announced a strategic partnership with another corporation!", + 0.07, + 0.03), + + PRODUCT_RECALL( + "Product recall issued!", + "The company recalled one of its products over safety concerns!", + -0.08, + 0.07), + + SUPPLY_SHORTAGE( + "Supply shortage reported!", + "Global supply shortages are slowing the company's production!", + -0.06, + 0.05), + + FACTORY_EXPANSION( + "Factory expansion planned!", + "The company announced plans to expand manufacturing capacity!", + 0.06, + 0.02), + + PATENT_APPROVED( + "Patent approved!", + "The company secured a major technology patent approval!", + 0.09, + 0.03), + + PATENT_DENIED( + "Patent denied!", + "Regulators denied one of the company's patent applications!", + -0.08, + 0.05), + + MARKET_MANIPULATION_INVESTIGATION( + "Market investigation launched!", + "Regulators opened a market manipulation investigation into the company!", + -0.03, + 0.15), + NONE_EVENT( + "", + "", + 0, + 0); + + private final String title; + private final String description; + private final double trend; + private final double volatility; // How violently the price moves factor + + NewspaperEnum(String title, String description, double trend, double volatility) { + this.title = title; + this.description = description; + this.trend = trend; + this.volatility = volatility; + } + + public String getTitle() { + return title; + } + + public String getDescription() { + return description; + } + + public double getTrend() { + return trend; + } + + public double getVolatility() { + return volatility; + } + +} diff --git a/src/main/java/edu/ntnu/idi/idatt/model/market/Stock.java b/src/main/java/edu/ntnu/idi/idatt/model/market/Stock.java index cff8ad3..fc64df5 100644 --- a/src/main/java/edu/ntnu/idi/idatt/model/market/Stock.java +++ b/src/main/java/edu/ntnu/idi/idatt/model/market/Stock.java @@ -5,6 +5,10 @@ import java.util.ArrayList; import java.util.Comparator; import java.util.List; +import java.util.Random; + +import edu.ntnu.idi.idatt.model.Newspaper; +import edu.ntnu.idi.idatt.model.enums.NewspaperEnum; /** * Stock class @@ -19,6 +23,11 @@ public class Stock { private final String symbol; private final String company; private final ArrayList prices = new ArrayList<>(); + private final Newspaper newspaper = new Newspaper(); + + private double trend; // Directional price movement + private double volatility; // How violent the price behaves + private double momentum; // Price continuation factor /** * Constructor for a Stock. @@ -33,6 +42,12 @@ public Stock(String symbol, String company, List prices) { this.symbol = symbol; this.company = company; this.prices.addAll(prices); + + // Initial values for price movement + Random r = new Random(); + trend = r.nextDouble(-0.01, 0.01); + volatility = r.nextDouble(0.02, 0.11); + momentum = 0; } /** @@ -119,6 +134,44 @@ public void addNewSalesPrice(BigDecimal price) { prices.add(price); } + // TODO: JavaDocs ETC + public Newspaper getNewspaper() { + return newspaper; + } + + private double calculatePriceFactor() { + double noice = (new Random().nextGaussian() + 0.2) * volatility; + + return 0.01 /* positive trend */ + trend + momentum + noice; + } + + private void calibrateNextPriceFactor() { + + momentum = calculatePriceFactor() * 0.35; + trend *= 0.9 + new Random().nextGaussian(); + + // Reduce accumulation if constant effects applied + trend = Math.clamp(trend, -0.06, 0.08); + + volatility *= 0.95; + + NewspaperEnum type = newspaper.makeNews(); + trend += type.getTrend(); + volatility += type.getVolatility(); + + } + + public void advancePrice() { + BigDecimal currentPrice = this.getSalesPrice(); + BigDecimal priceFactor = currentPrice.multiply(BigDecimal.valueOf(this.calculatePriceFactor())); + BigDecimal newPrice = currentPrice.add(priceFactor); + newPrice = newPrice.setScale(2, RoundingMode.HALF_UP); + + this.addNewSalesPrice(newPrice); + + this.calibrateNextPriceFactor(); + } + // TODO: JavaDocs @Override public String toString() { diff --git a/src/main/java/edu/ntnu/idi/idatt/session/UserSession.java b/src/main/java/edu/ntnu/idi/idatt/session/UserSession.java index 2fef3e5..8531f80 100644 --- a/src/main/java/edu/ntnu/idi/idatt/session/UserSession.java +++ b/src/main/java/edu/ntnu/idi/idatt/session/UserSession.java @@ -5,6 +5,7 @@ import edu.ntnu.idi.idatt.model.Exchange; import edu.ntnu.idi.idatt.model.player.Player; import edu.ntnu.idi.idatt.storage.SessionManager; +import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleObjectProperty; public class UserSession { @@ -41,10 +42,12 @@ public Exchange getExchange() { public void setExchange(Exchange exchange) { this.exchange = exchange; + updateGameState(); // Startup hook } private final SimpleObjectProperty moneyProperty = new SimpleObjectProperty<>(BigDecimal.ZERO); private final SimpleObjectProperty netWorthProperty = new SimpleObjectProperty<>(BigDecimal.ZERO); + private final SimpleIntegerProperty weekProperty = new SimpleIntegerProperty(); public SimpleObjectProperty moneyProperty() { return moneyProperty; @@ -54,9 +57,19 @@ public SimpleObjectProperty netWorthProperty() { return netWorthProperty; } + public SimpleIntegerProperty weekProperty() { + return weekProperty; + } + public void updateGameState() { + + if (player == null || exchange == null) { + return; + } + moneyProperty.set(player.getMoney()); netWorthProperty.set(player.getNetWorth()); + weekProperty.set(exchange.getWeek()); SessionManager.saveSession(); } diff --git a/src/main/java/edu/ntnu/idi/idatt/storage/StockParser.java b/src/main/java/edu/ntnu/idi/idatt/storage/StockParser.java index a4936dc..74c2739 100644 --- a/src/main/java/edu/ntnu/idi/idatt/storage/StockParser.java +++ b/src/main/java/edu/ntnu/idi/idatt/storage/StockParser.java @@ -2,8 +2,11 @@ import java.io.BufferedReader; import java.io.File; +import java.io.FileInputStream; import java.io.FileReader; import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; import java.math.BigDecimal; import java.nio.file.Files; import java.nio.file.Paths; @@ -28,26 +31,49 @@ private StockParser() { } /** - * Method for loading from stocks from file - * - * @param path - The path to the .csv file. - * + * Wrapper for loading stocks from file. + * + * @param filePath - Path to the .csv file. + * * @return a list of loaded stocks. - * @throws IOException on BufferedReader error + * @throws IOException on errors along the way. */ - public static List load(String path) throws IOException { - File file = new File(path.toString()); + public static List loadFromFile(String filePath) throws IOException { + File file = new File(filePath); if (!file.exists()) { throw new IOException("File at this path doesn't exist!"); } - if (!Files.probeContentType(Paths.get(file.getPath())).equals("text/csv")) { + if (!file.getName().toLowerCase().endsWith(".csv")) { throw new IOException("Please choose a .csv file!"); } + return load(new FileInputStream(file)); + } + /** + * Wrapper for loading stocks from resources. + * + * @param is - The inputstream of the resource. (GetResourceAsStream) + * + * @return a list of loaded stocks. + * @throws IOException on errors along the way. + */ + public static List loadFromResource(InputStream is) throws IOException { + return load(is); + } + + /** + * Method for loading from stocks from file + * + * @param is - The inputstream of a file. + * + * @return a list of loaded stocks. + * @throws IOException on errors along the way. + */ + private static List load(InputStream is) throws IOException { ArrayList stocks = new ArrayList<>(); - try (BufferedReader reader = new BufferedReader(new FileReader(file))) { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(is))) { List stockStringList = new ArrayList<>(reader.readAllLines()); // Remove comments and inproper syntax @@ -58,7 +84,7 @@ public static List load(String path) throws IOException { for (String stockString : stockStringList) { String[] stockValues = stockString.split(","); if (stockValues.length != 3) { - throw new IOException("Invalid CSV format!"); + throw new IOException("Invalid .CSV format!"); } Stock stock = new Stock(stockValues[0], stockValues[1], List.of(new BigDecimal(stockValues[2]))); @@ -66,7 +92,7 @@ public static List load(String path) throws IOException { } } catch (IOException e) { - throw new IOException("File loading failed!"); + throw new IOException("File loading failed!", e); } return stocks; diff --git a/src/main/java/edu/ntnu/idi/idatt/view/SceneFactory.java b/src/main/java/edu/ntnu/idi/idatt/view/SceneFactory.java index 165182a..5eb1390 100644 --- a/src/main/java/edu/ntnu/idi/idatt/view/SceneFactory.java +++ b/src/main/java/edu/ntnu/idi/idatt/view/SceneFactory.java @@ -132,13 +132,15 @@ public static Parent createTransactionView() { return view.getInstance(); } - public static Parent createNewspaperView() { - mark(() -> createNewspaperView()); + public static Parent createNewspaperView(String symbol) { + + mark(() -> createNewspaperView(symbol)); + Stock stock = UserSession.getInstance().getExchange().getStock(symbol); NewspaperModel model = new NewspaperModel(); - NewspaperView view = new NewspaperView(); - NewspaperController controller = new NewspaperController(model); + NewspaperView view = new NewspaperView(); + NewspaperController controller = new NewspaperController(model, stock); view.setModel(model); view.setController(controller); diff --git a/src/main/java/edu/ntnu/idi/idatt/view/components/elements/SearchBarComponent.java b/src/main/java/edu/ntnu/idi/idatt/view/components/elements/SearchBarComponent.java index 0e56051..c089f87 100644 --- a/src/main/java/edu/ntnu/idi/idatt/view/components/elements/SearchBarComponent.java +++ b/src/main/java/edu/ntnu/idi/idatt/view/components/elements/SearchBarComponent.java @@ -26,7 +26,7 @@ public SearchBarComponent(String placeholder) { searchBar.textProperty().bindBidirectional(query); searchBar.setFocusTraversable(false); - Image image = new Image(this.getClass().getResource("/icons/search.png/").toExternalForm()); + Image image = new Image(this.getClass().getResource("/icons/search.png").toExternalForm()); searchIcon = new IconComponent(image, null, 32); wrapper.getChildren().addAll(searchBar, searchIcon); diff --git a/src/main/java/edu/ntnu/idi/idatt/view/components/ui/UIFactory.java b/src/main/java/edu/ntnu/idi/idatt/view/components/ui/UIFactory.java index 2be519b..365580f 100644 --- a/src/main/java/edu/ntnu/idi/idatt/view/components/ui/UIFactory.java +++ b/src/main/java/edu/ntnu/idi/idatt/view/components/ui/UIFactory.java @@ -160,32 +160,38 @@ public static Parent createToolbar(ActionEventHandler menuHandle, ActionEventHan Label balance = new Label(); balance.textProperty().bind( UserSession.getInstance().moneyProperty().asString("Balance: %.2f $")); + Label week = new Label(); + week.textProperty().bind( + UserSession.getInstance().weekProperty().asString("Week: %d")); Label playerStatus = new Label("Status:" + UserSession.getInstance().getPlayer().getStatus()); Label playerNetWorth = new Label(); playerNetWorth.textProperty().bind( UserSession.getInstance().netWorthProperty().asString("Net Worth: %.2f $")); CssUtils.apply(balance, CssUtils.MED_TEXT_16); + CssUtils.apply(week, CssUtils.MED_TEXT_16); CssUtils.apply(playerStatus, CssUtils.MED_TEXT_16); CssUtils.apply(playerNetWorth, CssUtils.MED_TEXT_16); - VBox infoWrapper = new VBox(); - infoWrapper.setAlignment(Pos.CENTER); - infoWrapper.getChildren().addAll(playerStatus, playerNetWorth); - IconComponent userIcon = new IconComponent(ResourceUtils.MENU_ICON, null, 44); IconComponent quitIcon = new IconComponent(ResourceUtils.QUIT_ICON, null, 44); - HBox iconWrapper = new HBox(); - iconWrapper.setAlignment(Pos.CENTER); - iconWrapper.getChildren().addAll(userIcon, quitIcon); - UICompositor toolbar = new UICompositor.Builder() .parent(new HBox()) .growWithAlignment(Pos.CENTER) - .addContent(balance) + .wrap(new VBox()) + .properties(wrapper -> ((VBox) wrapper).setAlignment(Pos.CENTER)) + .addAllContent(balance, week) + .unwrap() .filler() - .addAllContent(infoWrapper, iconWrapper) + .wrap(new VBox()) + .properties(wrapper -> ((VBox) wrapper).setAlignment(Pos.CENTER)) + .addAllContent(playerStatus, playerNetWorth) + .unwrap() + .wrap(new HBox()) + .properties(wrapper -> ((HBox) wrapper).setAlignment(Pos.CENTER)) + .addAllContent(userIcon, quitIcon) + .unwrap() .build(); userIcon.onIconClick(() -> menuHandle.handle()); diff --git a/src/main/java/edu/ntnu/idi/idatt/view/entry/StartController.java b/src/main/java/edu/ntnu/idi/idatt/view/entry/StartController.java index 3767920..595a1c2 100644 --- a/src/main/java/edu/ntnu/idi/idatt/view/entry/StartController.java +++ b/src/main/java/edu/ntnu/idi/idatt/view/entry/StartController.java @@ -2,6 +2,7 @@ import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.math.BigDecimal; import java.util.List; @@ -69,16 +70,34 @@ public void initializeGame() { return; } - if (csv == null) { - model.getError().set("Specify the stocks .csv file!"); - return; + List stocks; + if (model.isPredefinedCSV().get()) { + InputStream ISpredefinedCSV = this.getClass() + .getResourceAsStream("/stocks.csv"); + + try { + stocks = StockParser.loadFromResource(ISpredefinedCSV); + } catch (IOException e) { + model.getError().set(e.getMessage()); + return; + } + + } else { + if (csv == null) { + model.getError().set("Specify the stocks .csv file!"); + return; + } + + try { + stocks = StockParser.loadFromFile(csv.getPath()); + } catch (IOException e) { + model.getError().set(e.getMessage()); + return; + } } - List stocks; - try { - stocks = StockParser.load(csv.getPath()); - } catch (IOException e) { - model.getError().set(e.getMessage()); + if (stocks == null || stocks.isEmpty()) { + model.getError().set("Failed to load stocks!"); return; } diff --git a/src/main/java/edu/ntnu/idi/idatt/view/entry/StartModel.java b/src/main/java/edu/ntnu/idi/idatt/view/entry/StartModel.java index 3e4c4ed..089262c 100644 --- a/src/main/java/edu/ntnu/idi/idatt/view/entry/StartModel.java +++ b/src/main/java/edu/ntnu/idi/idatt/view/entry/StartModel.java @@ -14,6 +14,7 @@ public class StartModel implements Model { private final StringProperty fileName = new SimpleStringProperty(); private final BooleanProperty newGame = new SimpleBooleanProperty(); + private final BooleanProperty predefinedCSV = new SimpleBooleanProperty(true); public StringProperty getName() { return name; @@ -35,4 +36,8 @@ public BooleanProperty isNewGame() { return newGame; } + public BooleanProperty isPredefinedCSV() { + return predefinedCSV; + } + } diff --git a/src/main/java/edu/ntnu/idi/idatt/view/entry/StartView.java b/src/main/java/edu/ntnu/idi/idatt/view/entry/StartView.java index f32cc87..0c7bc3c 100644 --- a/src/main/java/edu/ntnu/idi/idatt/view/entry/StartView.java +++ b/src/main/java/edu/ntnu/idi/idatt/view/entry/StartView.java @@ -7,6 +7,7 @@ import javafx.geometry.Pos; import javafx.scene.Parent; import javafx.scene.control.Button; +import javafx.scene.control.CheckBox; import javafx.scene.control.Label; import javafx.scene.control.TextField; import javafx.scene.layout.HBox; @@ -25,6 +26,8 @@ public class StartView extends AbstractView { // Global buttons for controller implementation private VBox newGameWrapper; + private HBox importCsvWrapper; + private CheckBox csvPredefinedCheckBox; private Button csvButton; private Button startButton; @@ -73,8 +76,17 @@ public Parent createContent() { // CSV import button and label - HBox csvWrapper = new HBox(); - csvWrapper.setAlignment(Pos.CENTER_LEFT); + HBox csvPredefinedWrapper = new HBox(); + csvPredefinedWrapper.setAlignment(Pos.CENTER_LEFT); + VBox.setMargin(csvPredefinedWrapper, new Insets(0.0, 0.0, 0.0, 50.0)); + csvPredefinedCheckBox = new CheckBox(); + csvPredefinedCheckBox.setSelected(true); + Label predefinedLabel = new Label("Use predefined stock CSV: "); + predefinedLabel.getStyleClass().add("med-text-16"); + csvPredefinedWrapper.getChildren().addAll(predefinedLabel, csvPredefinedCheckBox); + + importCsvWrapper = new HBox(); + importCsvWrapper.setAlignment(Pos.CENTER_LEFT); csvButton = new Button("Import CSV"); csvButton.getStyleClass().add("button"); @@ -82,8 +94,8 @@ public Parent createContent() { fileLabel = new Label(); fileLabel.getStyleClass().add("med-text-16"); - csvWrapper.getChildren().addAll(csvButton, fileLabel); - VBox.setMargin(csvWrapper, new Insets(0.0, 0.0, 0.0, 50.0)); + importCsvWrapper.getChildren().addAll(csvButton, fileLabel); + VBox.setMargin(importCsvWrapper, new Insets(0.0, 0.0, 0.0, 50.0)); // Start game button and error log startButton = new Button("Start Game"); @@ -100,7 +112,7 @@ public Parent createContent() { VBox.setVgrow(filler, Priority.ALWAYS); // Wrap components together and adjust positioning - newGameWrapper.getChildren().addAll(balanceWrapper, csvWrapper); + newGameWrapper.getChildren().addAll(balanceWrapper, csvPredefinedWrapper, importCsvWrapper); wrapper.getChildren().addAll(playerWrapper, newGameWrapper); wrapper.setAlignment(Pos.BASELINE_LEFT); @@ -115,6 +127,10 @@ public void setModel(StartModel model) { this.errorLabel.textProperty().bind(model.getError()); this.fileLabel.textProperty().bind(model.getFileName()); this.newGameWrapper.visibleProperty().bind(model.isNewGame()); + this.csvPredefinedCheckBox.selectedProperty().bindBidirectional(model.isPredefinedCSV()); + + // Binding importCsvWrapper visibleProperty here for convenience + this.importCsvWrapper.visibleProperty().bind(csvPredefinedCheckBox.selectedProperty().not()); } public void setController(StartController controller) { diff --git a/src/main/java/edu/ntnu/idi/idatt/view/primary/exchange/ExchangeController.java b/src/main/java/edu/ntnu/idi/idatt/view/primary/exchange/ExchangeController.java index 3c8b8dd..dd8f981 100644 --- a/src/main/java/edu/ntnu/idi/idatt/view/primary/exchange/ExchangeController.java +++ b/src/main/java/edu/ntnu/idi/idatt/view/primary/exchange/ExchangeController.java @@ -9,6 +9,13 @@ import edu.ntnu.idi.idatt.view.SceneManager; import edu.ntnu.idi.idatt.view.components.AbstractController; import edu.ntnu.idi.idatt.view.components.elements.StockComponent; +import javafx.animation.Animation; +import javafx.animation.KeyFrame; +import javafx.animation.KeyValue; +import javafx.animation.Timeline; +import javafx.scene.effect.DropShadow; +import javafx.scene.paint.Color; +import javafx.util.Duration; public class ExchangeController extends AbstractController { @@ -32,11 +39,32 @@ public void setStocksModel(List stockList) { model.getStockList().clear(); for (Stock stock : stockList) { StockComponent stockComponent = new StockComponent(stock); + + if (stock.getNewspaper().hasNewNews()) { + applyNewspaperGlow(stockComponent); + } + stockComponent.onStockClick((symbol) -> redirectView(symbol)); model.getStockList().add(stockComponent); } } + public void applyNewspaperGlow(StockComponent component) { + DropShadow glow = new DropShadow(7, Color.WHITE); + + Timeline timeline = new Timeline( + new KeyFrame(Duration.ZERO, + new KeyValue(glow.radiusProperty(), 10)), + new KeyFrame(Duration.seconds(0.5), + new KeyValue(glow.radiusProperty(), 50))); + + timeline.setAutoReverse(true); + timeline.setCycleCount(Animation.INDEFINITE); + + component.setEffect(glow); + timeline.play(); + } + public void handleSearchQuery(String query) { if (query == null || query.isBlank()) { setStocksModel(stocksSorted); // Get back to "no search" diff --git a/src/main/java/edu/ntnu/idi/idatt/view/primary/exchange/ExchangeView.java b/src/main/java/edu/ntnu/idi/idatt/view/primary/exchange/ExchangeView.java index e69a94e..8476959 100644 --- a/src/main/java/edu/ntnu/idi/idatt/view/primary/exchange/ExchangeView.java +++ b/src/main/java/edu/ntnu/idi/idatt/view/primary/exchange/ExchangeView.java @@ -44,9 +44,9 @@ public Parent createContent() { @Override public Parent createNavigation() { - return UIFactory.createNavigation(UserSession.getInstance().getExchange().getName(), - List.of(" • Newspaper"), - () -> SceneManager.switchTo(SceneFactory.createNewspaperView())); + return UIFactory.createNavigation( + UserSession.getInstance().getExchange().getName(), + List.of()); } @Override diff --git a/src/main/java/edu/ntnu/idi/idatt/view/primary/newspaper/NewspaperController.java b/src/main/java/edu/ntnu/idi/idatt/view/primary/newspaper/NewspaperController.java index 674758a..23904da 100644 --- a/src/main/java/edu/ntnu/idi/idatt/view/primary/newspaper/NewspaperController.java +++ b/src/main/java/edu/ntnu/idi/idatt/view/primary/newspaper/NewspaperController.java @@ -1,24 +1,28 @@ package edu.ntnu.idi.idatt.view.primary.newspaper; -import edu.ntnu.idi.idatt.model.portfolio.Share; -import edu.ntnu.idi.idatt.session.UserSession; +import edu.ntnu.idi.idatt.model.market.Stock; import edu.ntnu.idi.idatt.view.components.AbstractController; -import edu.ntnu.idi.idatt.view.components.elements.NewspaperComponent; +import edu.ntnu.idi.idatt.view.util.CssUtils; +import javafx.scene.control.Label; import java.util.ArrayList; public class NewspaperController extends AbstractController { - private final ArrayList loadedNewspaperComponents = new ArrayList<>(); - private UserSession session = UserSession.getInstance(); - - public NewspaperController(NewspaperModel model) { - super(model); - for (Share share : session.getPlayer().getPortfolio().getShares()) { - NewspaperComponent newspaperComponent = new NewspaperComponent(share); - loadedNewspaperComponents.add(newspaperComponent); - } - model.getNewspaperComponentsList().setAll(loadedNewspaperComponents); - } + private final ArrayList