diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index e45147d..960ac00 100644 --- a/pom.xml +++ b/pom.xml @@ -11,21 +11,31 @@ UTF-8 -5.10.2 + 5.10.2 + + 21 + 21.0.4 + org.openjfx javafx-controls - 21 + ${javafx.version} org.openjfx javafx-fxml - 21 + ${javafx.version} + + + org.openjfx + javafx-media + ${javafx.version} - + + org.junit.jupiter junit-jupiter-api ${junit.version} @@ -36,39 +46,30 @@ junit-jupiter-engine ${junit.version} test - + + + org.apache.maven.plugins maven-compiler-plugin 3.13.0 - 23 - 23 + ${maven.compiler.release} + org.openjfx javafx-maven-plugin 0.0.8 - - - - default-cli - - sbu.javafx/sbu.javafx.MusicApp - app - app - app - true - true - true - - - + + sbu.javafx/sbu.javafx.MusicApp + + \ No newline at end of file diff --git a/src/main/java/sbu/javafx/Launcher.java b/src/main/java/sbu/javafx/Launcher.java index c288d10..6351bdc 100644 --- a/src/main/java/sbu/javafx/Launcher.java +++ b/src/main/java/sbu/javafx/Launcher.java @@ -2,7 +2,10 @@ package sbu.javafx; import javafx.application.Application; -public class Launcher{ - public static void main(String[] args) { - // TODO: Launch the JavaFX application by calling Application.launch(Main.class) } -} +public class Launcher +{ + public static void main(String[] args) + { + Application.launch(MusicApp.class, args); + } +} \ No newline at end of file diff --git a/src/main/java/sbu/javafx/MusicApp.java b/src/main/java/sbu/javafx/MusicApp.java index a50badc..9810d80 100644 --- a/src/main/java/sbu/javafx/MusicApp.java +++ b/src/main/java/sbu/javafx/MusicApp.java @@ -7,8 +7,8 @@ import javafx.stage.Stage; import java.io.IOException; -public class MusicApp extends Application { - +public class MusicApp extends Application +{ @Override public void start(Stage stage) throws IOException { @@ -16,8 +16,7 @@ public class MusicApp extends Application { Scene scene = new Scene(fxmlLoader.load()); stage.setTitle("Music Player"); stage.setScene(scene); - //TODO: add icon to the stage + stage.setResizable(false); stage.show(); } - -} +} \ No newline at end of file diff --git a/src/main/java/sbu/javafx/MusicController.java b/src/main/java/sbu/javafx/MusicController.java index 5da27a9..9dac1a9 100644 --- a/src/main/java/sbu/javafx/MusicController.java +++ b/src/main/java/sbu/javafx/MusicController.java @@ -1,31 +1,384 @@ package sbu.javafx; import javafx.fxml.FXML; -import javafx.event.ActionEvent; +import javafx.scene.Scene; +import javafx.scene.control.Button; import javafx.scene.control.Label; -import javafx.scene.input.MouseEvent; +import javafx.scene.control.ListView; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; import javafx.scene.layout.VBox; +import javafx.scene.media.Media; +import javafx.scene.media.MediaPlayer; +import javafx.stage.Stage; -public class MusicController { +import java.net.URL; +import java.util.ArrayList; +import java.util.List; - // TODO: Define your UI components using the @FXML annotation. +public class MusicController +{ + @FXML private VBox rootPane; + @FXML private Label titleLabel; + @FXML private Label nowPlayingLabel; - //TODO: Implement the logic to play a song. This method should update the UI to show which song is currently playing. + @FXML private Button playBtn1; + @FXML private Button playBtn2; + @FXML private Button playBtn3; + + @FXML private ImageView playIcon1; + @FXML private ImageView playIcon2; + @FXML private ImageView playIcon3; + + @FXML private Button stopBtn1; + @FXML private Button stopBtn2; + @FXML private Button stopBtn3; + + private final List playlist = new ArrayList<>(); + private MediaPlayer mediaPlayer; + + private boolean darkTheme = false; + + private String currentPlayingSongFile = null; + private boolean isPlaying = false; + + private boolean isSong1Playing = false; + private boolean isSong2Playing = false; + private boolean isSong3Playing = false; + + + private Image playImage; + private Image pauseImage; + private Image stopImage; + + private final String song1 = "Careless Whisper"; + private final String song2 = "Adore You"; + private final String song3 = "Wildflower"; + + private final String song1File = "/sbu/javafx/audio/careless_whisper.mp3"; + private final String song2File = "/sbu/javafx/audio/adore_you.mp3"; + private final String song3File = "/sbu/javafx/audio/wildflower.mp3"; @FXML - public void handlePlayAction() { - //Update labels, change button icons, etc. + private void initialize() + { + playImage = loadImage("/sbu/javafx/images/play.png"); + pauseImage = loadImage("/sbu/javafx/images/pause.png"); + stopImage = loadImage("/sbu/javafx/images/stop.png"); + + updateAllButtonIcons(); + updateNowPlaying("None"); + applyLightTheme(); } - //TODO: Logic to add a specific song to the user's playlist. + @FXML public void handlePlay1() { togglePlay(song1, song1File, playIcon1, 1); } + @FXML public void handlePlay2() { togglePlay(song2, song2File, playIcon2, 2); } + @FXML public void handlePlay3() { togglePlay(song3, song3File, playIcon3, 3); } - public void addToPlaylist(String songName) { - //Add the song to a list or update a ListView. + @FXML public void handleStop1() { stopSong(1); } + @FXML public void handleStop2() { stopSong(2); } + @FXML public void handleStop3() { stopSong(3); } + + @FXML public void handleAdd1() { addToPlaylist(song1); } + @FXML public void handleAdd2() { addToPlaylist(song2); } + @FXML public void handleAdd3() { addToPlaylist(song3); } + + @FXML + public void handleViewPlaylist() + { + Stage stage = new Stage(); + VBox root = new VBox(10); + root.setStyle("-fx-padding: 16; -fx-background-color: white;"); + Label title = new Label("My Playlist"); + title.setStyle("-fx-font-size: 18px; -fx-font-weight: bold;"); + ListView listView = new ListView<>(); + listView.getItems().addAll(playlist); + root.getChildren().addAll(title, listView); + Scene scene = new Scene(root, 300, 400); + stage.setScene(scene); + stage.setTitle("Playlist"); + stage.show(); } - // TODO:This method should open a new window or change the current scene to display the "Playlist" page. - - public void openPlaylistWindow() { - //Create a new stage and show the playlist UI. + @FXML + public void handleToggleTheme() + { + darkTheme = !darkTheme; + if (darkTheme) applyDarkTheme(); else applyLightTheme(); } -} + + private void togglePlay(String songName, String resourcePath, ImageView icon, int songIndex) + { + boolean shouldPlay = false; + + switch(songIndex) + { + case 1: shouldPlay = !isSong1Playing; break; + case 2: shouldPlay = !isSong2Playing; break; + case 3: shouldPlay = !isSong3Playing; break; + } + + if (shouldPlay) + { + playOrResumeSong(songName, resourcePath, icon, songIndex); + } + else + { + pauseSong(songIndex); + } + updateAllButtonIcons(); + } + + private void playOrResumeSong(String songName, String resourcePath, ImageView icon, int songIndex) + { + try + { + if (mediaPlayer != null && isPlaying && !resourcePath.equals(currentPlayingSongFile)) + { + stopPlayback(); + updateAllButtonIcons(); + } + + if (mediaPlayer != null && !isPlaying && resourcePath.equals(currentPlayingSongFile)) + { + mediaPlayer.play(); + isPlaying = true; + } + else + { + if (mediaPlayer != null) + { + mediaPlayer.stop(); + mediaPlayer.dispose(); + } + + URL url = getClass().getResource(resourcePath); + if (url == null) + { + updateNowPlaying(songName + " (file not found)"); + setSongStatus(songIndex, false); + return; + } + + Media media = new Media(url.toExternalForm()); + mediaPlayer = new MediaPlayer(media); + + currentPlayingSongFile = resourcePath; + isPlaying = true; + + mediaPlayer.setOnEndOfMedia(() -> + { + stopPlayback(); + updateNowPlaying("None"); + setSongStatus(songIndex, false); + updateAllButtonIcons(); + }); + + mediaPlayer.setOnError(() -> + { + stopPlayback(); + updateNowPlaying(songName + " (error)"); + setSongStatus(songIndex, false); + updateAllButtonIcons(); + }); + + mediaPlayer.play(); + } + + setSongStatus(songIndex, true); + updateNowPlaying(songName); + + } + catch (Exception e) + { + stopPlayback(); + updateNowPlaying(songName + " (error)"); + setSongStatus(songIndex, false); + e.printStackTrace(); + } + } + + private void pauseSong(int songIndex) + { + if (mediaPlayer != null && isPlaying && currentPlayingSongFile != null) + { + String targetFile = ""; + switch(songIndex) + { + case 1: targetFile = song1File; break; + case 2: targetFile = song2File; break; + case 3: targetFile = song3File; break; + } + + if (currentPlayingSongFile.equals(targetFile)) + { + mediaPlayer.pause(); + isPlaying = false; + setSongStatus(songIndex, false); + updateNowPlaying("Paused: " + getSongNameByIndex(songIndex)); + updateAllButtonIcons(); + } + } + } + + private void stopSong(int songIndex) + { + if (mediaPlayer != null && isPlaying) + { + String targetFile = ""; + switch(songIndex) + { + case 1: targetFile = song1File; break; + case 2: targetFile = song2File; break; + case 3: targetFile = song3File; break; + } + + if (currentPlayingSongFile != null && currentPlayingSongFile.equals(targetFile)) + { + mediaPlayer.stop(); + mediaPlayer.dispose(); + mediaPlayer = null; + isPlaying = false; + currentPlayingSongFile = null; + setSongStatus(songIndex, false); + updateNowPlaying("None"); + updateAllButtonIcons(); + } + else + { + setSongStatus(songIndex, false); + updateAllButtonIcons(); + } + } + else + { + setSongStatus(songIndex, false); + updateAllButtonIcons(); + } + } + + + private void stopPlayback() + { + try + { + if (mediaPlayer != null) + { + mediaPlayer.stop(); + mediaPlayer.dispose(); + mediaPlayer = null; + } + } + catch (Exception ignored) { } + + isPlaying = false; + currentPlayingSongFile = null; + isSong1Playing = false; + isSong2Playing = false; + isSong3Playing = false; + } + + private void setSongStatus(int songIndex, boolean status) + { + switch(songIndex) + { + case 1: isSong1Playing = status; break; + case 2: isSong2Playing = status; break; + case 3: isSong3Playing = status; break; + } + } + + private boolean getSongStatus(int songIndex) + { + switch(songIndex) + { + case 1: return isSong1Playing; + case 2: return isSong2Playing; + case 3: return isSong3Playing; + default: return false; + } + } + + private String getSongNameByIndex(int songIndex) + { + switch(songIndex) + { + case 1: return song1; + case 2: return song2; + case 3: return song3; + default: return "Unknown"; + } + } + + public void addToPlaylist(String songName) + { + if (!playlist.contains(songName)) + { + playlist.add(songName); + System.out.println(songName + " added to playlist."); + } + else + { + System.out.println(songName + " is already in the playlist."); + } + } + + private void updateNowPlaying(String text) + { + if (nowPlayingLabel != null) {nowPlayingLabel.setText("Now Playing: " + text);} + } + + private void updateAllButtonIcons() + { + updateIconButton(playIcon1, isSong1Playing, song1File); + updateIconButton(playIcon2, isSong2Playing, song2File); + updateIconButton(playIcon3, isSong3Playing, song3File); + } + + private void updateIconButton(ImageView icon, boolean isPlayingCurrent, String currentSongFile) + { + if (icon == null) return; + + if (isPlaying && currentPlayingSongFile != null && currentPlayingSongFile.equals(currentSongFile)) + { + if (icon.getImage() != pauseImage) {icon.setImage(pauseImage);} + } + else + { + if (icon.getImage() != playImage) {icon.setImage(playImage);} + } + } + + private Image loadImage(String resourcePath) + { + try + { + URL url = getClass().getResource(resourcePath); + if (url == null) + { + System.err.println("Image not found: " + resourcePath); + return null; + } + return new Image(url.toExternalForm()); + } + catch (Exception e) + { + System.err.println("Error loading image: " + resourcePath + " - " + e.getMessage()); + return null; + } + } + + private void applyDarkTheme() + { + if (rootPane != null) rootPane.setStyle("-fx-padding: 24; -fx-background-color: #121212;"); + if (titleLabel != null) titleLabel.setStyle("-fx-font-size: 26px; -fx-font-weight: bold; -fx-text-fill: white;"); + if (nowPlayingLabel != null) nowPlayingLabel.setStyle("-fx-padding: 12;" + "-fx-background-color: #1E1E1E;" + "-fx-background-radius: 10;" + "-fx-border-color: #444444;" + "-fx-border-radius: 10;" + "-fx-text-fill: white;"); + } + + private void applyLightTheme() + { + if (rootPane != null) rootPane.setStyle("-fx-padding: 24; -fx-background-color: #F8F8F8;"); + if (titleLabel != null) titleLabel.setStyle("-fx-font-size: 26px; -fx-font-weight: bold; -fx-text-fill: #222222;"); + if (nowPlayingLabel != null) nowPlayingLabel.setStyle("-fx-padding: 12;" + "-fx-background-color: white;" + "-fx-background-radius: 10;" + "-fx-border-color: rgba(0,0,0,0.15);" + "-fx-border-radius: 10;" + "-fx-text-fill: black;"); + } +} \ No newline at end of file diff --git a/src/main/resources/sbu/javafx/App-view.fxml b/src/main/resources/sbu/javafx/App-view.fxml index 306c8d3..a57e3f4 100644 --- a/src/main/resources/sbu/javafx/App-view.fxml +++ b/src/main/resources/sbu/javafx/App-view.fxml @@ -1,8 +1,228 @@ + + + + + + + + - - + - + + + + + \ No newline at end of file diff --git a/src/main/resources/sbu/javafx/audio/adore_you.mp3 b/src/main/resources/sbu/javafx/audio/adore_you.mp3 new file mode 100644 index 0000000..274dac2 Binary files /dev/null and b/src/main/resources/sbu/javafx/audio/adore_you.mp3 differ diff --git a/src/main/resources/sbu/javafx/audio/careless_whisper.mp3 b/src/main/resources/sbu/javafx/audio/careless_whisper.mp3 new file mode 100644 index 0000000..aaa81e6 Binary files /dev/null and b/src/main/resources/sbu/javafx/audio/careless_whisper.mp3 differ diff --git a/src/main/resources/sbu/javafx/audio/wildflower.mp3 b/src/main/resources/sbu/javafx/audio/wildflower.mp3 new file mode 100644 index 0000000..4085864 Binary files /dev/null and b/src/main/resources/sbu/javafx/audio/wildflower.mp3 differ diff --git a/src/main/resources/sbu/javafx/css/dark.css b/src/main/resources/sbu/javafx/css/dark.css new file mode 100644 index 0000000..fc1b35e --- /dev/null +++ b/src/main/resources/sbu/javafx/css/dark.css @@ -0,0 +1,15 @@ +.root +{ + -fx-background-color: #1e1e1e; +} + +.label +{ + -fx-text-fill: white; +} + +.button +{ + -fx-background-color: #444; + -fx-text-fill: white; +} \ No newline at end of file diff --git a/src/main/resources/sbu/javafx/css/light.css b/src/main/resources/sbu/javafx/css/light.css new file mode 100644 index 0000000..065037d --- /dev/null +++ b/src/main/resources/sbu/javafx/css/light.css @@ -0,0 +1,15 @@ +.root +{ + -fx-background-color: white; +} + +.label +{ + -fx-text-fill: black; +} + +.button +{ + -fx-background-color: #e0e0e0; + -fx-text-fill: black; +} \ No newline at end of file diff --git a/src/main/resources/sbu/javafx/images/add.png b/src/main/resources/sbu/javafx/images/add.png new file mode 100644 index 0000000..10691a8 Binary files /dev/null and b/src/main/resources/sbu/javafx/images/add.png differ diff --git a/src/main/resources/sbu/javafx/images/pause.png b/src/main/resources/sbu/javafx/images/pause.png new file mode 100644 index 0000000..11c7efd Binary files /dev/null and b/src/main/resources/sbu/javafx/images/pause.png differ diff --git a/src/main/resources/sbu/javafx/images/play.png b/src/main/resources/sbu/javafx/images/play.png new file mode 100644 index 0000000..b49f598 Binary files /dev/null and b/src/main/resources/sbu/javafx/images/play.png differ diff --git a/src/main/resources/sbu/javafx/images/playlist.png b/src/main/resources/sbu/javafx/images/playlist.png new file mode 100644 index 0000000..f409e15 Binary files /dev/null and b/src/main/resources/sbu/javafx/images/playlist.png differ diff --git a/src/main/resources/sbu/javafx/images/stop.png b/src/main/resources/sbu/javafx/images/stop.png new file mode 100644 index 0000000..8044083 Binary files /dev/null and b/src/main/resources/sbu/javafx/images/stop.png differ diff --git a/src/main/resources/sbu/javafx/images/theme.png b/src/main/resources/sbu/javafx/images/theme.png new file mode 100644 index 0000000..ed7e928 Binary files /dev/null and b/src/main/resources/sbu/javafx/images/theme.png differ