diff --git a/pom.xml b/pom.xml index 12869a8..f850370 100644 --- a/pom.xml +++ b/pom.xml @@ -44,6 +44,11 @@ + + + src/main/resources + + org.apache.maven.plugins @@ -60,7 +65,7 @@ javafx-maven-plugin 0.0.8 - edu.ntnu.idi.idatt2003.gruppe42.Millions + edu.ntnu.idi.idatt2003.gruppe42.Launcher @@ -68,6 +73,57 @@ maven-javadoc-plugin 3.12.0 + + + + org.apache.maven.plugins + maven-shade-plugin + 3.5.0 + + + package + shade + + + + edu.ntnu.idi.idatt2003.gruppe42.Launcher + + + + + *:* + + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + + + + + + + + + + + org.panteleyev + jpackage-maven-plugin + 1.6.0 + + Millions + 1.0.0 + edu.ntnu.idi.idatt2003.gruppe42 + target + target/dist + Millions-1.0-SNAPSHOT.jar + edu.ntnu.idi.idatt2003.gruppe42.Launcher + DMG + src/main/resources/images/app_icon.icns + + + + + diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Launcher.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Launcher.java new file mode 100644 index 0000000..828f455 --- /dev/null +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Launcher.java @@ -0,0 +1,7 @@ +package edu.ntnu.idi.idatt2003.gruppe42; + +public class Launcher { + public static void main(String[] args) { + Millions.launch(Millions.class, args); + } +} 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 1144111..d0f7b25 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Millions.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/Millions.java @@ -9,7 +9,8 @@ import edu.ntnu.idi.idatt2003.gruppe42.model.Player; import edu.ntnu.idi.idatt2003.gruppe42.view.views.DesktopView; import edu.ntnu.idi.idatt2003.gruppe42.view.views.StartView; -import java.nio.file.Path; + +import java.io.InputStream; import javafx.application.Application; import javafx.scene.Scene; import javafx.stage.Stage; @@ -63,17 +64,17 @@ public void start(final Stage stage) { * * @param username the player name * @param difficulty selected game difficulty - * @param userSelectedPath path to stock data file + * @param stocksStream path to stock data file * @param popupsController controller managing popups */ public void initGame(final String username, final Difficulty difficulty, - Path userSelectedPath, PopupsController popupsController) { + InputStream stocksStream, PopupsController popupsController) { player = GameFactory.createPlayer(username, difficulty); gameController = new GameController(); gameController.startGame(); desktopViewController = new DesktopViewController(player, gameController, - userSelectedPath, popupsController); + stocksStream, popupsController); desktopViewController.setOnGameOver(this::resetGame); desktopView = desktopViewController.getDesktopView(); scene.setRoot(desktopView.getRoot()); diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/audio/Audio.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/audio/Audio.java index c093d2b..61ec2e1 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/audio/Audio.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/audio/Audio.java @@ -6,13 +6,13 @@ public enum Audio { // Sound effects - OPEN_APP("/Audio/open_app.wav", true), - CLOSE_APP("/Audio/close_app.wav", true), - CLOCK("/Audio/clock.wav", true), + OPEN_APP("/audio/open_app.wav", true), + CLOSE_APP("/audio/close_app.wav", true), + CLOCK("/audio/clock.wav", true), // Background music - WORK_THEME("/Audio/work_theme.mp3", false), - WEEKEND_THEME("/Audio/weekend_theme.mp3", false); + WORK_THEME("/audio/work_theme.mp3", false), + WEEKEND_THEME("/audio/weekend_theme.mp3", false); private final String path; private final boolean isSfx; diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/audio/AudioManager.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/audio/AudioManager.java index 5e06cd8..0293dad 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/audio/AudioManager.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/audio/AudioManager.java @@ -42,7 +42,7 @@ private AudioManager() { } } - // Keep background music volume in sync with master × 0.5 (bg is quieter) + // Keep background music volume in sync with master × 0.5 (bg should be quieter for UX) masterVolume.addListener((obs, ov, nv) -> { if (bgPlayer != null) { bgPlayer.setVolume(nv.doubleValue() * 0.5); @@ -80,7 +80,6 @@ public void playSfx(final Audio audio) { Clip clip = AudioSystem.getClip(); clip.open(stream); - // Apply volume via MASTER_GAIN if (clip.isControlSupported(FloatControl.Type.MASTER_GAIN)) { FloatControl gain = (FloatControl) clip.getControl(FloatControl.Type.MASTER_GAIN); float db = volumeToDb(volume); 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 5159d33..c10afcd 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 @@ -5,7 +5,7 @@ import edu.ntnu.idi.idatt2003.gruppe42.model.StockFileHandler; import edu.ntnu.idi.idatt2003.gruppe42.model.exceptions.StockFileParseException; import java.io.IOException; -import java.nio.file.Path; +import java.io.InputStream; import java.util.List; /** @@ -18,12 +18,12 @@ public final class MarketController { /** * Creates a new market controller and loads stock data from a file. * - * @param path the path to the stock data file + * @param stocksStream the path to the stock data file */ - public MarketController(Path path) { + public MarketController(InputStream stocksStream) { this.stockResolution = 120; try { - exchange = new Exchange(StockFileHandler.readFromFile(path)); + exchange = new Exchange(StockFileHandler.readFromFile(stocksStream)); startMarket(); System.out.println("Market loaded"); } catch (IOException exception) { diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/controller/appcontrollers/SettingsAppController.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/controller/appcontrollers/SettingsAppController.java index 8a00114..b216313 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/controller/appcontrollers/SettingsAppController.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/controller/appcontrollers/SettingsAppController.java @@ -3,79 +3,90 @@ import edu.ntnu.idi.idatt2003.gruppe42.audio.AudioManager; import edu.ntnu.idi.idatt2003.gruppe42.model.Exchange; import edu.ntnu.idi.idatt2003.gruppe42.model.StockFileHandler; -import edu.ntnu.idi.idatt2003.gruppe42.view.apps.FilePickerApp; import edu.ntnu.idi.idatt2003.gruppe42.view.apps.SettingsApp; import edu.ntnu.idi.idatt2003.gruppe42.view.apps.SettingsApp.GradientConfig; + +import java.io.InputStream; +import java.nio.file.Files; import java.nio.file.Path; import java.util.Objects; import java.util.function.Consumer; -import javafx.scene.layout.Pane; +import javafx.stage.FileChooser; +import javafx.stage.Window; /** * Controller for the Settings application. - * - *

This controller manages user-configurable settings such as: - *

- * - *

It also validates selected stock files and falls back to a default file - * if validation fails. */ public class SettingsAppController implements AppController { private Path userSelectedPath; - private Runnable onUserSelection; private Runnable onSelectedFileFailed; private Runnable onLogout; private Runnable onPowerOff; private Consumer onPlayerNameChanged; private Consumer onGradientChanged; + private String errorMessage; private final SettingsApp settingsApp; - private final FilePickerApp filePickerApp; - - /** - * Constructs the Settings controller and initializes UI bindings, - * file validation, and event wiring. - * - * @param settingsApp the settings UI view - * @param filePickerApp the file picker UI view - */ + public SettingsAppController( - final SettingsApp settingsApp, - final FilePickerApp filePickerApp) { + final SettingsApp settingsApp) { + this.settingsApp = settingsApp; - this.filePickerApp = filePickerApp; - userSelectedPath = loadDefaultPath(); + this.userSelectedPath = loadDefaultStockPath(); + refreshView(); wireControls(); + } + + private void openFilePicker() { + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle("Select Stock CSV File"); + + fileChooser.getExtensionFilters().add( + new FileChooser.ExtensionFilter("CSV files", "*.csv") + ); + + Window window = settingsApp.getRoot().getScene().getWindow(); + + java.io.File file = fileChooser.showOpenDialog(window); + + if (file == null) { + return; + } - // Confirm: validate the picked file then propagate or revert - filePickerApp.setOnFileConfirmed(path -> { - if (isValidStockFile(path)) { - userSelectedPath = path; - if (onUserSelection != null) { - onUserSelection.run(); - } - } else { - if (onSelectedFileFailed != null) { - onSelectedFileFailed.run(); - } - userSelectedPath = loadDefaultPath(); + Path selected = file.toPath(); + + if (isValidStockFile(selected)) { + userSelectedPath = selected; + } else { + if (onSelectedFileFailed != null) { + onSelectedFileFailed.run(); } - refreshView(); - }); + userSelectedPath = null; + } + + refreshView(); + } + + private Path loadDefaultStockPath() { + try { + return Path.of( + Objects.requireNonNull( + getClass().getClassLoader().getResource("stockFiles/stocks.csv") + ).toURI() + ); + } catch (Exception e) { + System.err.println("Failed to load default stocks.csv: " + e.getMessage()); + return null; + } } private void wireControls() { wireVolumeControls(); + if (!settingsApp.isLoggedIn()) { wireLoggedOutControls(); } else { @@ -85,17 +96,23 @@ private void wireControls() { private void wireVolumeControls() { settingsApp.getMasterVolumeSlider().valueProperty().addListener( - (obs, ov, nv) -> AudioManager.get().setMasterVolume(nv.doubleValue() / 100.0)); + (obs, ov, nv) -> AudioManager.get() + .setMasterVolume(nv.doubleValue() / 100.0)); + settingsApp.getSfxVolumeSlider().valueProperty().addListener( - (obs, ov, nv) -> AudioManager.get().setSfxVolume(nv.doubleValue() / 100.0)); + (obs, ov, nv) -> AudioManager.get() + .setSfxVolume(nv.doubleValue() / 100.0)); + settingsApp.getMasterVolumeSlider() - .setValue(AudioManager.get().getMasterVolume() * 100.0); + .setValue(AudioManager.get().getMasterVolume() * 100.0); + settingsApp.getSfxVolumeSlider() - .setValue(AudioManager.get().getSfxVolume() * 100.0); + .setValue(AudioManager.get().getSfxVolume() * 100.0); } private void wireLoggedOutControls() { - settingsApp.getOpenFileChooserButton().setOnAction(event -> openFilePicker()); + settingsApp.getOpenFileChooserButton() + .setOnAction(event -> openFilePicker()); } private void wireLoggedInControls() { @@ -106,7 +123,8 @@ private void wireLoggedInControls() { }); settingsApp.getApplyNameButton().setOnAction(event -> { - final String name = settingsApp.getPlayerNameField().getText().trim(); + String name = settingsApp.getPlayerNameField().getText().trim(); + if (!name.isEmpty() && onPlayerNameChanged != null) { onPlayerNameChanged.accept(name); settingsApp.getPlayerNameField().clear(); @@ -126,30 +144,14 @@ private void wireLoggedInControls() { }); } - /** - * Enables or disables logged-in mode and rewires UI controls accordingly. - * - * @param status true if user is logged in, false otherwise - */ - public void setLoggedIn(final boolean status) { + public void setLoggedIn(boolean status) { settingsApp.setLoggedIn(status); wireControls(); } - private void openFilePicker() { - if (filePickerApp.getRoot().getParent() == null) { - final var parent = settingsApp.getRoot().getParent(); - if (parent instanceof Pane pane) { - pane.getChildren().add(filePickerApp.getRoot()); - filePickerApp.centerInParent(); - } - } - filePickerApp.show(); - } - - private boolean isValidStockFile(final Path path) { - try { - new Exchange(StockFileHandler.readFromFile(path)); + private boolean isValidStockFile(Path path) { + try (InputStream in = Files.newInputStream(path)) { + new Exchange(StockFileHandler.readFromFile(in)); return true; } catch (Exception e) { errorMessage = e.getMessage(); @@ -157,12 +159,14 @@ private boolean isValidStockFile(final Path path) { } } - private Path loadDefaultPath() { + public InputStream getSelectedStockStream() { + if (userSelectedPath == null) { + return null; + } + try { - return Path.of(Objects.requireNonNull( - getClass().getClassLoader().getResource("stocks.csv")).toURI()); + return Files.newInputStream(userSelectedPath); } catch (Exception e) { - System.err.println("Failed to load default stocks.csv: " + e.getMessage()); return null; } } @@ -172,83 +176,32 @@ private void refreshView() { settingsApp.updateContent(); } - /** - * Sets the callback invoked when the user successfully selects a valid stock file path. - * - * @param callback the runnable to invoke after a successful path selection - */ - public void setOnUserPathSelected(final Runnable callback) { - onUserSelection = callback; - } - - /** - * Sets the callback invoked when the selected stock file fails validation. - * - * @param callback the runnable to invoke when file validation fails - */ - public void setOnSelectedFileFailed(final Runnable callback) { - onSelectedFileFailed = callback; - } - - /** - * Sets the callback invoked when the user triggers a logout action. - * - * @param callback the runnable to invoke on logout - */ - public void setOnLogout(final Runnable callback) { - onLogout = callback; - } - - /** - * Sets the callback invoked when the user triggers the power-off action. - * - * @param callback the runnable to invoke on power-off - */ - public void setOnPowerOff(final Runnable callback) { - onPowerOff = callback; - } - - /** - * Sets the callback invoked when the user applies a new player name. - * - * @param callback the consumer to invoke with the newly entered player name - */ - public void setOnPlayerNameChanged(final Consumer callback) { - onPlayerNameChanged = callback; - } - - /** - * Sets the callback invoked when the user applies a new gradient configuration. - * - * @param callback the consumer to invoke with the selected gradient config - */ - public void setOnGradientChanged(final Consumer callback) { - onGradientChanged = callback; - } - - /** - * Returns the currently selected stock file path. - * - * @return selected file path - */ - public Path getUserSelectedPath() { - return userSelectedPath; - } - - /** - * Returns the last error message produced during file validation. - * - * @return error message, or null if no error occurred - */ + public void setOnSelectedFileFailed(Runnable callback) { + this.onSelectedFileFailed = callback; + } + + public void setOnLogout(Runnable callback) { + this.onLogout = callback; + } + + public void setOnPowerOff(Runnable callback) { + this.onPowerOff = callback; + } + + public void setOnPlayerNameChanged(Consumer callback) { + this.onPlayerNameChanged = callback; + } + + public void setOnGradientChanged(Consumer callback) { + this.onGradientChanged = callback; + } + public String getErrorMessage() { return errorMessage; } - /** - * Processes the next game tick. - * - *

This controller does not currently perform any tick-based logic. - */ @Override - public void nextTick() {} + public void nextTick() { + // no-op + } } \ No newline at end of file diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/controller/viewcontrollers/DesktopComponentFactory.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/controller/viewcontrollers/DesktopComponentFactory.java index 631ccca..98d9631 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/controller/viewcontrollers/DesktopComponentFactory.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/controller/viewcontrollers/DesktopComponentFactory.java @@ -79,10 +79,10 @@ public Node createAppButton(final App type) { private String iconPath(final App type) { return switch (type) { - case MAIL -> "/Images/mailApp_icon.png"; - case BANK -> "/Images/bankApp_icon.png"; - case STOCK -> "/Images/stockApp_icon.png"; - case HUSTLERS -> "/Images/hustlersApp_icon.png"; + case MAIL -> "/images/mail_app_icon.png"; + case BANK -> "/images/bank_app_icon.png"; + case STOCK -> "/images/stock_app_icon.png"; + case HUSTLERS -> "/images/hustlers_app_icon.png"; default -> null; }; } 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 1de0926..9feae79 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 @@ -17,7 +17,6 @@ import edu.ntnu.idi.idatt2003.gruppe42.view.Popup; import edu.ntnu.idi.idatt2003.gruppe42.view.apps.AppStoreApp; import edu.ntnu.idi.idatt2003.gruppe42.view.apps.BankApp; -import edu.ntnu.idi.idatt2003.gruppe42.view.apps.FilePickerApp; import edu.ntnu.idi.idatt2003.gruppe42.view.apps.GameOverApp; import edu.ntnu.idi.idatt2003.gruppe42.view.apps.HustlersApp; import edu.ntnu.idi.idatt2003.gruppe42.view.apps.MailApp; @@ -28,7 +27,8 @@ import edu.ntnu.idi.idatt2003.gruppe42.view.apps.WeekendReportApp; import edu.ntnu.idi.idatt2003.gruppe42.view.views.DesktopView; import edu.ntnu.idi.idatt2003.gruppe42.view.views.DesktopView.ModalLayer; -import java.nio.file.Path; + +import java.io.InputStream; import java.util.ArrayList; import java.util.List; import javafx.application.Platform; @@ -76,20 +76,20 @@ public final class DesktopViewController { * * @param player the player model * @param gameController the main game controller - * @param userSelectedPath path to stock data file selected by user + * @param stocksStream path to stock data file selected by user * @param popupsController controller responsible for popup management */ public DesktopViewController( final Player player, final GameController gameController, - final Path userSelectedPath, + final InputStream stocksStream, final PopupsController popupsController) { this.player = player; this.timeAndWeatherController = new TimeAndWeatherController(gameController); gameController.addAppController(timeAndWeatherController); - this.marketController = new MarketController(userSelectedPath); + this.marketController = new MarketController(stocksStream); this.mailController = new MailController(timeAndWeatherController); gameController.addAppController(mailController); @@ -142,9 +142,8 @@ private List initializeApps(GameController gameController) { gameController.addAppController(new BankAppController(bankApp, player)); SettingsApp settingsApp = (SettingsApp) popupsController.getPopup(App.SETTINGS); - FilePickerApp filePickerApp = (FilePickerApp) popupsController.getPopup(App.FILEPICKER); SettingsAppController settingsAppController = - new SettingsAppController(settingsApp, filePickerApp); + new SettingsAppController(settingsApp); settingsAppController.setLoggedIn(true); settingsAppController.setOnGradientChanged(cfg -> desktopView.getDesktopPane().setStyle( 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 fa23133..eccac6a 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 @@ -3,33 +3,18 @@ import edu.ntnu.idi.idatt2003.gruppe42.Millions; import edu.ntnu.idi.idatt2003.gruppe42.controller.PopupsController; import edu.ntnu.idi.idatt2003.gruppe42.controller.appcontrollers.SettingsAppController; -import edu.ntnu.idi.idatt2003.gruppe42.model.App; import edu.ntnu.idi.idatt2003.gruppe42.model.Difficulty; +import edu.ntnu.idi.idatt2003.gruppe42.model.App; import edu.ntnu.idi.idatt2003.gruppe42.view.Popup; -import edu.ntnu.idi.idatt2003.gruppe42.view.apps.FilePickerApp; import edu.ntnu.idi.idatt2003.gruppe42.view.apps.SettingsApp; import edu.ntnu.idi.idatt2003.gruppe42.view.apps.WarningApp; import edu.ntnu.idi.idatt2003.gruppe42.view.views.StartView; -import java.nio.file.Path; + +import java.io.InputStream; import java.util.ArrayList; import java.util.List; -import java.util.Objects; import java.util.Random; -/** - * Controller for the start screen of the application. - * - *

This controller manages: - *

- * - *

It acts as the bridge between the StartView UI and the main application logic. - */ public class StartViewController { private String username = ""; @@ -37,149 +22,136 @@ public class StartViewController { private final StartView startView; private final Millions application; private final PopupsController popupsController; - private Path userSelectedPath; + private final SettingsAppController settingsAppController; private final WarningApp warningApp; private static final List TATE_NAMES = List.of( - "Top G", - "Cobra Tate", - "Tristan's Brother", - "Emory's Son", - "Matrix Escaper", - "Bugatti Owner", - "Hustler G", - "Chin Checker", - "Sparkling Water Enthusiast", - "The Talisman", - "Baby Oil Hoarder", - "Roofie Supplier" + "Top G", + "Cobra Tate", + "Tristan's Brother", + "Emory's Son", + "Matrix Escaper", + "Bugatti Owner", + "Hustler G", + "Chin Checker", + "Sparkling Water Enthusiast", + "The Talisman", + "Baby Oil Hoarder", + "Roofie Supplier" ); - /** - * Constructs the start view controller and initializes UI components, - * popups, and event handlers for the start screen. - * - * @param application the main application instance - * @param startView the start screen view - */ public StartViewController(final Millions application, final StartView startView) { this.application = application; this.startView = startView; - final List popups = new ArrayList<>(); + List popups = new ArrayList<>(); - FilePickerApp filePickerApp = new FilePickerApp(); SettingsApp settingsApp = new SettingsApp(false); - settingsAppController = new SettingsAppController(settingsApp, filePickerApp); + + settingsAppController = new SettingsAppController(settingsApp); warningApp = new WarningApp(); warningApp.centerInParent(); startView.getRoot().getChildren().addAll( - settingsApp.getRoot(), - warningApp.getRoot(), - filePickerApp.getRoot() + settingsApp.getRoot(), + warningApp.getRoot() ); - settingsAppController.setOnUserPathSelected(this::updateUserSelectedPath); settingsAppController.setOnSelectedFileFailed(this::showFileWarning); popups.add(settingsApp); popups.add(warningApp); - popups.add(filePickerApp); + popupsController = new PopupsController(popups); startView.getUsernameField().textProperty().addListener( - (obs, old, newValue) -> username = newValue + (obs, old, newValue) -> username = newValue ); startView.getLoginButton().setOnAction(event -> { if (startView.getSelectedMode() == null) { warningApp.configure( - "OBS", - "Difficulty Required", - "Got it" + "OBS", + "Difficulty Required", + "Got it" ); - warningApp.getPrimaryButton().setOnAction(e -> popupsController.hide(App.WARNING)); + warningApp.getPrimaryButton() + .setOnAction(e -> popupsController.hide(App.WARNING)); popupsController.show(App.WARNING); return; } + handleStart(); }); - startView.getSettingsButton().setOnAction(actionEvent -> popupsController.show(App.SETTINGS)); + startView.getSettingsButton() + .setOnAction(e -> popupsController.show(App.SETTINGS)); } - /** - * Starts the game by resolving the username, selecting difficulty, - * loading stock data, and initializing the main game. - */ private void handleStart() { + String resolvedName = resolveUsername(username); + Difficulty difficulty = resolveDifficulty(); + + InputStream stocksStream = resolveStockStream(); + + application.initGame( + resolvedName, + difficulty, + stocksStream, + popupsController + ); + } + + private InputStream resolveStockStream() { + InputStream stream = settingsAppController.getSelectedStockStream(); + + if (stream != null) { + return stream; + } + + return getClass() + .getClassLoader() + .getResourceAsStream("stockFiles/stocks.csv"); + } + + private Difficulty resolveDifficulty() { 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; - } + if (selectedMode == null) { + return Difficulty.EASY; } - if (Objects.isNull(userSelectedPath)) { - try { - userSelectedPath = Path.of(Objects.requireNonNull( - getClass().getClassLoader().getResource("stocks.csv")).toURI()); - } catch (Exception e) { - System.err.println("Failed to load default stocks.csv: " + e.getMessage()); - } + if (selectedMode.startsWith("Medium")) { + return Difficulty.MEDIUM; + } + + if (selectedMode.startsWith("Hard")) { + return Difficulty.HARD; } - application.initGame(resolvedName, difficulty, userSelectedPath, popupsController); + return Difficulty.EASY; } - /** - * Resolves a valid username. - * - *

If the input is invalid or empty, a random fallback name is selected. - * - * @param input the raw username input - * @return a valid username - */ - private String resolveUsername(final String input) { - if (input == null || input.trim().isEmpty() || !isValidName(input)) { + private String resolveUsername(String input) { + if (input == null || input.trim().isEmpty() || !input.matches("[a-zA-Z0-9]+")) { return TATE_NAMES.get(new Random().nextInt(TATE_NAMES.size())); } return input; } - /** - * Validates whether a username contains only alphanumeric characters. - * - * @param value the username to validate - * @return true if the name is valid, false otherwise - */ - private boolean isValidName(final String value) { - return value.matches("[a-zA-Z0-9]+"); - } - - /** - * Displays a warning when the selected stock file is invalid - * and prompts the user to revert to the default file. - */ private void showFileWarning() { warningApp.configure( - "Invalid File", - settingsAppController.getErrorMessage(), - "Revert to default file" + "Invalid File", + settingsAppController.getErrorMessage(), + "Revert" ); - warningApp.getPrimaryButton().setOnAction(e -> popupsController.hide(App.WARNING)); - popupsController.show(App.WARNING); - } - private void updateUserSelectedPath() { - userSelectedPath = settingsAppController.getUserSelectedPath(); + warningApp.getPrimaryButton() + .setOnAction(e -> popupsController.hide(App.WARNING)); + + popupsController.show(App.WARNING); } -} +} \ No newline at end of file diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/News.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/News.java index 1f60af7..b92e457 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/News.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/News.java @@ -33,6 +33,7 @@ public News(String company, int trend) { */ public String generateAuthor() { List authors = new ArrayList<>(); + // Saved as List in-case new authors are added authors.add("market@news.com"); return authors.get(random.nextInt(authors.size())); } @@ -58,8 +59,8 @@ public String generateNews() { private List getStringsGood() { List goodNews = new ArrayList<>(); goodNews.add("Turns out the product of " + company + " is good for the environment."); - goodNews.add("Beaver population increased due to the efforts of " + company + "."); - goodNews.add("Eminem dropped new hit single, namedropping " + company + "."); + goodNews.add("Beaver population increased due to the efforts of " + company); + goodNews.add("Eminem dropped new hit single, namedropping " + company); goodNews.add("Founder of " + company + " seen feeding the homeless."); goodNews.add(company + " got frontpage in new forbes magazine, " + "on top companies to invest in."); diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/StockFileHandler.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/StockFileHandler.java index 17859fd..1f497a5 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/StockFileHandler.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/model/StockFileHandler.java @@ -1,10 +1,8 @@ package edu.ntnu.idi.idatt2003.gruppe42.model; import edu.ntnu.idi.idatt2003.gruppe42.model.exceptions.StockFileParseException; -import java.io.BufferedReader; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStreamReader; + +import java.io.*; import java.math.BigDecimal; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -36,30 +34,29 @@ private StockFileHandler() {} *

Each valid line must follow the format: {@code SYMBOL,Company Name,price} * (e.g. {@code AAPL,Apple Inc,191.27}). * - * @param path the path to the stock data file. + * @param inputStream the path to the stock data file. * @return a list of parsed {@link Stock} objects. * @throws IOException if the file does not exist or cannot be read. * @throws StockFileParseException if any line contains invalid or malformed data. */ - public static List readFromFile(final Path path) - throws IOException, StockFileParseException { + public static List readFromFile(final InputStream inputStream) + throws IOException, StockFileParseException { - if (!Files.exists(path)) { - throw new IOException("File not found: " + path); - } - if (!Files.isReadable(path)) { - throw new IOException("File is not readable: " + path); + if (inputStream == null) { + throw new IOException("Input stream is null"); } List stocks = new ArrayList<>(); int lineNumber = 0; try (BufferedReader reader = new BufferedReader( - new InputStreamReader(new FileInputStream(path.toFile()), StandardCharsets.UTF_8))) { + new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { String line; + while ((line = reader.readLine()) != null) { lineNumber++; + String trimmed = line.trim(); if (trimmed.isEmpty() || trimmed.startsWith("#")) { @@ -67,48 +64,88 @@ public static List readFromFile(final Path path) } String[] parts = trimmed.split(","); + if (parts.length != EXPECTED_FIELDS) { - throw new StockFileParseException(lineNumber, trimmed, - String.format("Expected %d comma-separated fields but found %d. " - + "Hint: decimal prices must use '.' not ','", - EXPECTED_FIELDS, parts.length)); + throw new StockFileParseException( + lineNumber, + trimmed, + String.format( + "Expected %d comma-separated fields but found %d. " + + "Hint: decimal prices must use '.' not ','", + EXPECTED_FIELDS, + parts.length + ) + ); } String symbol = parts[0].trim(); + if (symbol.isEmpty()) { - throw new StockFileParseException(lineNumber, trimmed, - "Ticker symbol is empty"); + throw new StockFileParseException( + lineNumber, + trimmed, + "Ticker symbol is empty" + ); } + if (!SYMBOL_PATTERN.matcher(symbol).matches()) { - throw new StockFileParseException(lineNumber, trimmed, - String.format("Ticker symbol \"%s\" is invalid — " - + "must be exactly 3 uppercase letters (A-Z)", symbol)); + throw new StockFileParseException( + lineNumber, + trimmed, + String.format( + "Ticker symbol \"%s\" is invalid", + symbol + ) + ); } String company = parts[1].trim(); + if (company.isEmpty()) { - throw new StockFileParseException(lineNumber, trimmed, - "Company name is empty"); + throw new StockFileParseException( + lineNumber, + trimmed, + "Company name is empty" + ); } String rawPrice = parts[2].trim(); + if (!PRICE_PATTERN.matcher(rawPrice).matches()) { - throw new StockFileParseException(lineNumber, trimmed, - String.format("Price \"%s\" is invalid — must be a positive number " - + "with exactly 2 decimal places using '.' (e.g. 191.27)", rawPrice)); + throw new StockFileParseException( + lineNumber, + trimmed, + String.format( + "Price \"%s\" is invalid", + rawPrice + ) + ); } BigDecimal price; + try { price = new BigDecimal(rawPrice); } catch (NumberFormatException e) { - throw new StockFileParseException(lineNumber, trimmed, - String.format("Price \"%s\" could not be parsed as a number", rawPrice)); + throw new StockFileParseException( + lineNumber, + trimmed, + String.format( + "Price \"%s\" could not be parsed", + rawPrice + ) + ); } if (price.compareTo(BigDecimal.ZERO) <= 0) { - throw new StockFileParseException(lineNumber, trimmed, - String.format("Price \"%s\" must be greater than zero", rawPrice)); + throw new StockFileParseException( + lineNumber, + trimmed, + String.format( + "Price \"%s\" must be greater than zero", + rawPrice + ) + ); } stocks.add(new Stock(symbol, company, price)); @@ -116,8 +153,11 @@ public static List readFromFile(final Path path) } if (stocks.isEmpty()) { - throw new StockFileParseException(lineNumber, "", - "File contained no valid stock entries"); + throw new StockFileParseException( + lineNumber, + "", + "File contained no valid stock entries" + ); } return stocks; diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/view/apps/FilePickerApp.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/view/apps/FilePickerApp.java deleted file mode 100644 index bff9896..0000000 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/view/apps/FilePickerApp.java +++ /dev/null @@ -1,353 +0,0 @@ -package edu.ntnu.idi.idatt2003.gruppe42.view.apps; - -import edu.ntnu.idi.idatt2003.gruppe42.model.App; -import edu.ntnu.idi.idatt2003.gruppe42.view.Popup; -import java.io.File; -import java.nio.file.Path; -import java.util.Arrays; -import java.util.Comparator; -import java.util.function.Consumer; -import javafx.geometry.Insets; -import javafx.geometry.Pos; -import javafx.scene.control.Button; -import javafx.scene.control.Label; -import javafx.scene.control.ScrollPane; -import javafx.scene.control.Separator; -import javafx.scene.control.TextField; -import javafx.scene.layout.HBox; -import javafx.scene.layout.Priority; -import javafx.scene.layout.VBox; - -/** - * File picker popup used to browse the local file system and select CSV files. - * - *

The picker provides navigation (back, forward, up), a quick access sidebar, - * and a selectable file list filtered to CSV files.

- */ -public class FilePickerApp extends Popup { - private static final String FILTER = ".csv"; - private final Button backButton = new Button("←"); - private final Button forwardButton = new Button("→"); - private final Button upButton = new Button("↑"); - private final TextField addressBar = new TextField(); - - private static final File[] QUICK_ACCESS = { - new File(System.getProperty("user.home")), - new File(System.getProperty("user.home") + "/Desktop"), - new File(System.getProperty("user.home") + "/Documents"), - new File(System.getProperty("user.home") + "/Downloads"), - }; - - private final VBox fileList = new VBox(1); - - private final TextField selectedField = new TextField(); - private final Button openButton = new Button("Open"); - private final Button cancelButton = new Button("Cancel"); - private final Label filterLabel = new Label("CSV Files (*.csv)"); - - private File currentDir = new File(System.getProperty("user.home")); - private File prevDir = null; - private File selectedFile; - private Consumer onFileConfirmed; - - /** - * Constructs a new file picker popup. - * Initializes UI and loads the initial directory view. - */ - public FilePickerApp() { - super(580, 420, 80, 60, App.FILEPICKER); - buildLayout(); - wireToolbar(); - populateDir(currentDir); - } - - /** - * Builds the full layout of the file picker including sidebar, - * toolbar, file list, and bottom action bar. - */ - private void buildLayout() { - backButton.getStyleClass().add("fp-toolbar-button"); - forwardButton.getStyleClass().add("fp-toolbar-button"); - upButton.getStyleClass().add("fp-toolbar-button"); - backButton.setDisable(true); - forwardButton.setDisable(true); - - addressBar.getStyleClass().add("fp-address-bar"); - addressBar.setEditable(false); - HBox.setHgrow(addressBar, Priority.ALWAYS); - - HBox toolbar = new HBox(4, backButton, forwardButton, upButton, addressBar); - toolbar.setAlignment(Pos.CENTER_LEFT); - toolbar.setPadding(new Insets(6, 8, 6, 8)); - toolbar.getStyleClass().add("fp-toolbar"); - - VBox sidebar = new VBox(2); - sidebar.getStyleClass().add("fp-sidebar"); - sidebar.setPrefWidth(130); - sidebar.setPadding(new Insets(8, 6, 8, 6)); - - Label quickLabel = new Label("QUICK ACCESS"); - quickLabel.getStyleClass().add("fp-sidebar-section"); - sidebar.getChildren().add(quickLabel); - - String[] names = { "Home", "Desktop", "Documents", "Downloads" }; - for (int i = 0; i < QUICK_ACCESS.length; i++) { - File dir = QUICK_ACCESS[i]; - - if (!dir.exists()) { - continue; - } - - Button btn = new Button(names[i]); - btn.getStyleClass().add("fp-sidebar-item"); - btn.setMaxWidth(Double.MAX_VALUE); - btn.setAlignment(Pos.CENTER_LEFT); - int idx = i; - btn.setOnAction(e -> { - prevDir = currentDir; - backButton.setDisable(false); - populateDir(QUICK_ACCESS[idx]); - }); - sidebar.getChildren().add(btn); - } - - ScrollPane scroll = new ScrollPane(fileList); - scroll.setFitToWidth(true); - scroll.getStyleClass().add("fp-scroll"); - VBox.setVgrow(scroll, Priority.ALWAYS); - - // Column header - HBox header = buildColumnHeader(); - - VBox mainArea = new VBox(0, header, scroll); - mainArea.getStyleClass().add("fp-main"); - HBox.setHgrow(mainArea, Priority.ALWAYS); - - HBox body = new HBox(0, sidebar, new Separator(), mainArea); - VBox.setVgrow(body, Priority.ALWAYS); - - selectedField.getStyleClass().add("settings-text-field"); - selectedField.setEditable(false); - selectedField.setPromptText("File name"); - HBox.setHgrow(selectedField, Priority.ALWAYS); - - filterLabel.getStyleClass().add("fp-filter-label"); - filterLabel.setPrefWidth(140); - - openButton.getStyleClass().add("primary-button"); - cancelButton.getStyleClass().add("secondary-button"); - openButton.setDisable(true); - - openButton.setOnAction(e -> confirm()); - cancelButton.setOnAction(e -> hide()); - - HBox nameRow = new HBox(8, new Label("File name:"), selectedField, filterLabel); - nameRow.setAlignment(Pos.CENTER_LEFT); - (nameRow.getChildren().get(0)).getStyleClass().add("fp-bottom-label"); - - HBox buttonRow = new HBox(6, openButton, cancelButton); - buttonRow.setAlignment(Pos.CENTER_RIGHT); - - VBox bottomBar = new VBox(6, nameRow, buttonRow); - bottomBar.getStyleClass().add("fp-bottom-bar"); - bottomBar.setPadding(new Insets(8, 10, 8, 10)); - - VBox root = new VBox(0, toolbar, body, new Separator(), bottomBar); - VBox.setVgrow(body, Priority.ALWAYS); - content.getChildren().setAll(root); - content.setPrefSize(580, 420); - } - - /** - * Builds the column header row for the file list. - * - * @return header HBox containing column labels - */ - private HBox buildColumnHeader() { - Label nameCol = new Label("Name"); - Label typeCol = new Label("Type"); - Label sizeCol = new Label("Size"); - nameCol.getStyleClass().add("fp-col-header"); - typeCol.getStyleClass().add("fp-col-header"); - sizeCol.getStyleClass().add("fp-col-header"); - HBox.setHgrow(nameCol, Priority.ALWAYS); - typeCol.setPrefWidth(80); - sizeCol.setPrefWidth(70); - - HBox header = new HBox(0, nameCol, typeCol, sizeCol); - header.getStyleClass().add("fp-col-header-row"); - header.setPadding(new Insets(3, 8, 3, 36)); - return header; - } - - /** - * Wires toolbar navigation actions (back, up, address bar input). - */ - private void wireToolbar() { - backButton.setOnAction(e -> { - if (prevDir != null) { - populateDir(prevDir); - prevDir = null; - backButton.setDisable(true); - } - }); - upButton.setOnAction(e -> { - if (currentDir.getParentFile() != null) { - prevDir = currentDir; - backButton.setDisable(false); - populateDir(currentDir.getParentFile()); - } - }); - addressBar.setOnAction(e -> { - File typed = new File(addressBar.getText()); - if (typed.isDirectory()) { - populateDir(typed); - } - }); - } - - /** - * Populates the file list with entries from the given directory. - * - * @param dir directory to display - */ - private void populateDir(final File dir) { - currentDir = dir; - addressBar.setText(dir.getAbsolutePath()); - upButton.setDisable(dir.getParentFile() == null); - fileList.getChildren().clear(); - selectedFile = null; - selectedField.clear(); - openButton.setDisable(true); - - File[] entries = dir.listFiles(); - if (entries == null) { - return; - } - - Arrays.stream(entries) - .filter(f -> f.isDirectory() || f.getName().endsWith(FILTER)) - .sorted(Comparator - .comparing(File::isDirectory).reversed() - .thenComparing(f -> f.getName().toLowerCase())) - .forEach(f -> fileList.getChildren().add(buildRow(f))); - } - - /** - * Builds a UI row representing a file or directory. - * - * @param file file or directory to render - * @return HBox representing the row - */ - private HBox buildRow(final File file) { - String icon = file.isDirectory() ? "📁" : "📄"; - final String type = file.isDirectory() ? "Folder" : "CSV File"; - final String size = file.isDirectory() ? "" - : formatSize(file.length()); - - Label iconLabel = new Label(icon); - iconLabel.getStyleClass().add("fp-row-icon"); - iconLabel.setMinWidth(28); - - Label nameLabel = new Label(file.getName()); - nameLabel.getStyleClass().add( - file.isDirectory() ? "fp-row-dir" : "fp-row-file"); - nameLabel.setMaxWidth(Double.MAX_VALUE); - HBox.setHgrow(nameLabel, Priority.ALWAYS); - - Label typeLabel = new Label(type); - typeLabel.getStyleClass().add("fp-row-meta"); - typeLabel.setPrefWidth(80); - - Label sizeLabel = new Label(size); - sizeLabel.getStyleClass().add("fp-row-meta"); - sizeLabel.setPrefWidth(70); - - HBox row = new HBox(0, iconLabel, nameLabel, typeLabel, sizeLabel); - row.setAlignment(Pos.CENTER_LEFT); - row.setPadding(new Insets(4, 8, 4, 8)); - row.getStyleClass().add("fp-row"); - - row.setOnMouseClicked(e -> { - if (e.getClickCount() == 1) { - if (file.isDirectory()) { - prevDir = currentDir; - backButton.setDisable(false); - populateDir(file); - } else { - clearSelection(); - row.getStyleClass().add("fp-row-selected"); - selectedFile = file; - selectedField.setText(file.getName()); - openButton.setDisable(false); - } - } else if (e.getClickCount() == 2 && !file.isDirectory()) { - selectedFile = file; - confirm(); - } - }); - - return row; - } - - /** - * Confirms the currently selected file and triggers callback. - */ - private void confirm() { - if (selectedFile != null && onFileConfirmed != null) { - onFileConfirmed.accept(selectedFile.toPath()); - hide(); - } - } - - /** - * Clears the current selection highlight from the file list. - */ - private void clearSelection() { - fileList.getChildren() - .forEach(n -> n.getStyleClass().remove("fp-row-selected")); - } - - /** - * Formats file size into human-readable units. - * - * @param bytes file size in bytes - * @return formatted string (B, KB, MB) - */ - private static String formatSize(final long bytes) { - if (bytes < 1024) { - return bytes + " B"; - } - if (bytes < 1024 * 1024) { - return (bytes / 1024) + " KB"; - } - return (bytes / (1024 * 1024)) + " MB"; - } - - /** - * Sets callback executed when a file is confirmed. - * - * @param callback consumer receiving selected file path - */ - public void setOnFileConfirmed(final Consumer callback) { - this.onFileConfirmed = callback; - } - - /** - * Shows the file picker and refreshes the directory view. - */ - @Override - public void show() { - selectedFile = null; - selectedField.clear(); - openButton.setDisable(true); - populateDir(currentDir); - super.show(); - } - - /** Hides the file picker. */ - @Override - public void hide() { - super.hide(); - } -} \ No newline at end of file diff --git a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/view/apps/SettingsApp.java b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/view/apps/SettingsApp.java index dd79a08..5042f42 100644 --- a/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/view/apps/SettingsApp.java +++ b/src/main/java/edu/ntnu/idi/idatt2003/gruppe42/view/apps/SettingsApp.java @@ -223,7 +223,7 @@ public void updateContent() { layout.setPadding(new Insets(24, 28, 24, 28)); layout.getStyleClass().add("settings-layout"); - layout.getChildren().add(sectionTitle("Audio")); + layout.getChildren().add(sectionTitle("audio")); layout.getChildren().add(sliderRow("Master volume", masterVolumeSlider)); layout.getChildren().add(sliderRow("SFX volume", sfxVolumeSlider)); 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 03d20e8..527cc78 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 @@ -62,7 +62,7 @@ public StockApp(final Player player) { this.alphaButton = new Button("A-Z"); this.alphaButton.getStyleClass().add("sort-button"); - this.gainerButton = new Button("Gainer"); + this.gainerButton = new Button("Gainers"); this.gainerButton.getStyleClass().add("sort-button"); this.priceButton = new Button("Price"); this.priceButton.getStyleClass().add("sort-button"); 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 3c9f4fc..139894d 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 @@ -46,45 +46,50 @@ public BorderPane getRoot() { 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"))); + + Image logoImage = new Image(Objects.requireNonNull( + getClass().getResourceAsStream("/images/logo.jpg") + )); + ImageView logoView = new ImageView(logoImage); logoView.setPreserveRatio(true); logoView.setFitWidth(120); + avatarContainer.getChildren().add(logoView); avatarContainer.setMaxWidth(130); - // Username Label usernameLabel = new Label("State your name, G"); usernameLabel.getStyleClass().add("difficulty-label"); - // Login Input 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); - // Starting Money Label Label startingMoneyLabel = new Label("How much matrix money?"); startingMoneyLabel.getStyleClass().add("difficulty-label"); + VBox startingMoneyOptions = new VBox(10); startingMoneyOptions.setAlignment(Pos.CENTER); - // Starting Money Options RadioButton easyCheck = new RadioButton("Easy ($10,000)"); RadioButton mediumCheck = new RadioButton("Medium ($1000)"); RadioButton hardCheck = new RadioButton("Hard ($100)"); + easyCheck.getStyleClass().add("difficulty-label"); mediumCheck.getStyleClass().add("difficulty-label"); hardCheck.getStyleClass().add("difficulty-label"); + easyCheck.setToggleGroup(startingMoneyGroup); mediumCheck.setToggleGroup(startingMoneyGroup); hardCheck.setToggleGroup(startingMoneyGroup); @@ -92,7 +97,11 @@ public BorderPane getRoot() { startingMoneyOptions.getChildren().addAll(easyCheck, mediumCheck, hardCheck); loginContainer.getChildren().addAll( - avatarContainer, usernameLabel, loginInputBox, startingMoneyLabel, startingMoneyOptions + avatarContainer, + usernameLabel, + loginInputBox, + startingMoneyLabel, + startingMoneyOptions ); settingsButton = new Button("⚙"); @@ -100,15 +109,15 @@ public BorderPane getRoot() { HBox bottomBar = new HBox(settingsButton); bottomBar.getStyleClass().add("login-bottom-bar"); - root.setBottom(bottomBar); content.getChildren().add(loginContainer); content.getStyleClass().add("login-root"); loadStylesheets(); - + root.setBottom(bottomBar); root.setCenter(content); + return root; } diff --git a/src/main/resources/Audio/.work_theme.mp3.icloud b/src/main/resources/Audio/.work_theme.mp3.icloud new file mode 100644 index 0000000..d50b58b Binary files /dev/null and b/src/main/resources/Audio/.work_theme.mp3.icloud differ diff --git a/src/main/resources/Audio/work_theme.mp3 b/src/main/resources/Audio/work_theme.mp3 deleted file mode 100644 index 5d94de6..0000000 Binary files a/src/main/resources/Audio/work_theme.mp3 and /dev/null differ diff --git a/src/main/resources/Images/bankApp_icon.png b/src/main/resources/Images/bankApp_icon.png deleted file mode 100644 index 642b9a4..0000000 Binary files a/src/main/resources/Images/bankApp_icon.png and /dev/null differ diff --git a/src/main/resources/Audio/clock.wav b/src/main/resources/audio/clock.wav similarity index 100% rename from src/main/resources/Audio/clock.wav rename to src/main/resources/audio/clock.wav diff --git a/src/main/resources/Audio/close_app.wav b/src/main/resources/audio/close_app.wav similarity index 100% rename from src/main/resources/Audio/close_app.wav rename to src/main/resources/audio/close_app.wav diff --git a/src/main/resources/Audio/open_app.wav b/src/main/resources/audio/open_app.wav similarity index 100% rename from src/main/resources/Audio/open_app.wav rename to src/main/resources/audio/open_app.wav diff --git a/src/main/resources/Audio/weekend_theme.mp3 b/src/main/resources/audio/weekend_theme.mp3 similarity index 100% rename from src/main/resources/Audio/weekend_theme.mp3 rename to src/main/resources/audio/weekend_theme.mp3 diff --git a/src/main/resources/images/app_icon.icns b/src/main/resources/images/app_icon.icns new file mode 100644 index 0000000..cfc94b6 Binary files /dev/null and b/src/main/resources/images/app_icon.icns differ diff --git a/src/main/resources/images/app_icon.png b/src/main/resources/images/app_icon.png new file mode 100644 index 0000000..e54b7d8 Binary files /dev/null and b/src/main/resources/images/app_icon.png differ diff --git a/src/main/resources/images/bank_app_icon.png b/src/main/resources/images/bank_app_icon.png new file mode 100644 index 0000000..a149843 Binary files /dev/null and b/src/main/resources/images/bank_app_icon.png differ diff --git a/src/main/resources/Images/hustlersApp_icon.png b/src/main/resources/images/hustlers_app_icon.png similarity index 100% rename from src/main/resources/Images/hustlersApp_icon.png rename to src/main/resources/images/hustlers_app_icon.png diff --git a/src/main/resources/Images/logo.jpg b/src/main/resources/images/logo.jpg similarity index 100% rename from src/main/resources/Images/logo.jpg rename to src/main/resources/images/logo.jpg diff --git a/src/main/resources/Images/mailApp_icon.png b/src/main/resources/images/mail_app_icon.png similarity index 100% rename from src/main/resources/Images/mailApp_icon.png rename to src/main/resources/images/mail_app_icon.png diff --git a/src/main/resources/Images/stockApp_icon.png b/src/main/resources/images/stock_app_icon.png similarity index 100% rename from src/main/resources/Images/stockApp_icon.png rename to src/main/resources/images/stock_app_icon.png diff --git a/src/main/resources/empty.csv b/src/main/resources/stockFiles/empty.csv similarity index 100% rename from src/main/resources/empty.csv rename to src/main/resources/stockFiles/empty.csv diff --git a/src/main/resources/scramble.csv b/src/main/resources/stockFiles/scramble.csv similarity index 100% rename from src/main/resources/scramble.csv rename to src/main/resources/stockFiles/scramble.csv diff --git a/src/main/resources/sp500.csv b/src/main/resources/stockFiles/sp500.csv similarity index 100% rename from src/main/resources/sp500.csv rename to src/main/resources/stockFiles/sp500.csv diff --git a/src/main/resources/stocks.csv b/src/main/resources/stockFiles/stocks.csv similarity index 100% rename from src/main/resources/stocks.csv rename to src/main/resources/stockFiles/stocks.csv diff --git a/src/test/java/edu/ntnu/idi/idatt2003/gruppe42/model/StockFileHandlerTest.java b/src/test/java/edu/ntnu/idi/idatt2003/gruppe42/model/StockFileHandlerTest.java index d61bcb4..ff9c44a 100644 --- a/src/test/java/edu/ntnu/idi/idatt2003/gruppe42/model/StockFileHandlerTest.java +++ b/src/test/java/edu/ntnu/idi/idatt2003/gruppe42/model/StockFileHandlerTest.java @@ -2,7 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; -import java.io.IOException; +import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; @@ -11,34 +11,45 @@ /** * Unit tests for the {@link StockFileHandler} class. * - *

Verifies correct parsing of stock data from CSV files, + *

Verifies correct parsing of stock data from CSV input streams, * including handling of valid entries, comments, and empty lines. */ public class StockFileHandlerTest { + /** + * Tests that a valid CSV file is correctly parsed into Stock objects. + * + * @throws Exception if file creation or reading fails + */ @Test - void readFromFileTest() throws IOException { - Path testFile = Files.createTempFile("stocks", "csv"); - Files.writeString(testFile, "AAPL,Apple Inc.,276.43\nMSFT,Microsoft,404.68"); - try { - List stocks = StockFileHandler.readFromFile(testFile); + void readFromFileTest() throws Exception { + Path testFile = Files.createTempFile("stocks", ".csv"); + Files.writeString(testFile, + "AAPL,Apple Inc.,276.43\nMSFT,Microsoft,404.68"); + + try (InputStream in = Files.newInputStream(testFile)) { + List stocks = StockFileHandler.readFromFile(in); + assertEquals(2, stocks.size()); assertEquals("AAPL", stocks.get(0).getSymbol()); - } catch (Exception e) { - throw new RuntimeException(e); } } + /** + * Tests that comments and empty lines are ignored when parsing. + * + * @throws Exception if file creation or reading fails + */ @Test - void readFromFileWithCommentsAndEmptyLines() throws IOException { - Path testFile = Files.createTempFile("stocks", "csv"); - Files.writeString(testFile, "#Comment\n\nAAPL,Apple Inc.,276.43"); + void readFromFileWithCommentsAndEmptyLines() throws Exception { + Path testFile = Files.createTempFile("stocks", ".csv"); + Files.writeString(testFile, + "#Comment\n\nAAPL,Apple Inc.,276.43"); + + try (InputStream in = Files.newInputStream(testFile)) { + List stocks = StockFileHandler.readFromFile(in); - try { - List stocks = StockFileHandler.readFromFile(testFile); assertEquals(1, stocks.size()); - } catch (Exception e) { - throw new RuntimeException(e); } } -} +} \ No newline at end of file