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-plugin0.0.8
- edu.ntnu.idi.idatt2003.gruppe42.Millions
+ edu.ntnu.idi.idatt2003.gruppe42.Launcher
@@ -68,6 +73,57 @@
maven-javadoc-plugin3.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:
- *
- *
Audio volume (master and SFX)
- *
Player name changes
- *
Theme gradient customization
- *
File selection for stock data
- *
Session actions such as logout and power off
- *
- *
- *
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:
- *
- *
User login and username validation
- *
Game initialization with selected difficulty
- *
File selection for stock data
- *
Start screen popups and warnings
- *
Settings interactions
- *
- *
- *
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