diff --git a/.gitignore b/.gitignore index 650c91720..e00dc3664 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,10 @@ out/ !**/src/main/**/out/ !**/src/test/**/out/ .idea/easycode.ignore +.idea/ +homework-5.iml +src/keys target/ ### Eclipse ### diff --git a/.idea/.gitignore b/.idea/.gitignore index 13566b81b..650c91720 100644 --- a/.idea/.gitignore +++ b/.idea/.gitignore @@ -1,8 +1,32 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml +### IntelliJ IDEA ### +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ +.idea/easycode.ignore + +target/ + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/.idea/checkstyle-idea.xml b/.idea/checkstyle-idea.xml index aa01ae2cb..0a0561d2c 100644 --- a/.idea/checkstyle-idea.xml +++ b/.idea/checkstyle-idea.xml @@ -1,7 +1,7 @@ - 10.18.0 + 10.18.1 JavaOnly \ No newline at end of file diff --git a/src/main/java/Constants.java b/src/main/java/Constants.java new file mode 100644 index 000000000..ce3a5c882 --- /dev/null +++ b/src/main/java/Constants.java @@ -0,0 +1,17 @@ +package main.java; + +import java.awt.Dimension; +import java.awt.Toolkit; + +/** + * Constants used in this program. + */ +public class Constants { + // App Size + private static final Dimension SCREEN_SIZE = Toolkit.getDefaultToolkit().getScreenSize(); + public static final int APP_WIDTH = SCREEN_SIZE.width; + public static final int APP_HEIGHT = SCREEN_SIZE.height; + public static final int MIN_HEIGHT = 400; + public static final int MIN_WIDTH = 400; +} + diff --git a/src/main/java/app/AppBuilder.java b/src/main/java/app/AppBuilder.java index e9eef5c81..c1f0b4ae3 100644 --- a/src/main/java/app/AppBuilder.java +++ b/src/main/java/app/AppBuilder.java @@ -1,42 +1,64 @@ package app; -import java.awt.CardLayout; +import java.awt.*; -import javax.swing.JFrame; -import javax.swing.JPanel; -import javax.swing.WindowConstants; +import javax.swing.*; -import data_access.InMemoryUserDataAccessObject; +import data_access.*; import entity.CommonUserFactory; import entity.UserFactory; import interface_adapter.ViewManagerModel; -import interface_adapter.change_password.ChangePasswordController; -import interface_adapter.change_password.ChangePasswordPresenter; -import interface_adapter.change_password.LoggedInViewModel; +import interface_adapter.keyword.KeywordController; +import interface_adapter.keyword.KeywordPresenter; +import interface_adapter.keyword.KeywordViewModel; +import interface_adapter.logged_in.LoggedInViewModel; import interface_adapter.login.LoginController; import interface_adapter.login.LoginPresenter; import interface_adapter.login.LoginViewModel; import interface_adapter.logout.LogoutController; import interface_adapter.logout.LogoutPresenter; -import interface_adapter.signup.SignupController; -import interface_adapter.signup.SignupPresenter; -import interface_adapter.signup.SignupViewModel; -import use_case.change_password.ChangePasswordInputBoundary; -import use_case.change_password.ChangePasswordInteractor; -import use_case.change_password.ChangePasswordOutputBoundary; +import interface_adapter.recommend.RecommendController; +import interface_adapter.recommend.RecommendPresenter; +import interface_adapter.recommend.RecommendViewModel; +import interface_adapter.search.SearchController; +import interface_adapter.search.SearchPresenter; +import interface_adapter.search.SearchViewModel; +import interface_adapter.similar_listeners.SimilarListenersController; +import interface_adapter.similar_listeners.SimilarListenersPresenter; +import interface_adapter.similar_listeners.SimilarListenersViewModel; +import interface_adapter.top_items.TopItemsController; +import interface_adapter.top_items.TopItemsPresenter; +import interface_adapter.top_items.TopItemsViewModel; +import use_case.keyword.KeywordInputBoundary; +import use_case.keyword.KeywordInteractor; +import use_case.keyword.KeywordOutputBoundary; import use_case.login.LoginInputBoundary; import use_case.login.LoginInteractor; import use_case.login.LoginOutputBoundary; import use_case.logout.LogoutInputBoundary; import use_case.logout.LogoutInteractor; import use_case.logout.LogoutOutputBoundary; -import use_case.signup.SignupInputBoundary; -import use_case.signup.SignupInteractor; -import use_case.signup.SignupOutputBoundary; +import use_case.recommend.RecommendInputBoundary; +import use_case.recommend.RecommendInteractor; +import use_case.recommend.RecommendOutputBoundary; +import use_case.search.SearchInputBoundary; +import use_case.search.SearchInteractor; +import use_case.search.SearchOutputBoundary; +import use_case.similar_listeners.SimilarListenersInputBoundary; +import use_case.similar_listeners.SimilarListenersInteractor; +import use_case.similar_listeners.SimilarListenersOutputBoundary; +import use_case.top_items.TopItemsInputBoundary; +import use_case.top_items.TopItemsInteractor; +import use_case.top_items.TopItemsOutputBoundary; import view.LoggedInView; import view.LoginView; -import view.SignupView; +import view.RecommendationsView; +import view.SearchView; +import view.TopItemsView; +import view.SimilarListenersView; + import view.ViewManager; +import view.*; /** * The AppBuilder class is responsible for putting together the pieces of @@ -59,37 +81,66 @@ public class AppBuilder { // thought question: is the hard dependency below a problem? private final InMemoryUserDataAccessObject userDataAccessObject = new InMemoryUserDataAccessObject(); + private final LanguageModelDataAccessObject languageModelDataAccessObject = new LanguageModelDataAccessObject(); + private final RecommendUserDataAccessObject recommendUserDataAccessObject = new RecommendUserDataAccessObject(); + private final SpotifyDataAccessObject spotifyDataAccessObject = new SpotifyDataAccessObject(); - private SignupView signupView; - private SignupViewModel signupViewModel; private LoginViewModel loginViewModel; private LoggedInViewModel loggedInViewModel; + private TopItemsViewModel topTracksAndArtistsViewModel; + private RecommendViewModel recommendViewModel; + private SearchViewModel searchViewModel; + private SimilarListenersViewModel similarListenersViewModel; private LoggedInView loggedInView; private LoginView loginView; + private SearchView searchView; + private TopItemsView topItemsView; + private SimilarListenersView similarListenersView; + private RecommendationsView recommendationsView; + private KeywordView keywordView; + private KeywordViewModel keywordViewModel; + public AppBuilder() { cardPanel.setLayout(cardLayout); } /** - * Adds the Signup View to the application. + * Adds the Login View to the application. * @return this builder */ - public AppBuilder addSignupView() { - signupViewModel = new SignupViewModel(); - signupView = new SignupView(signupViewModel); - cardPanel.add(signupView, signupView.getViewName()); + public AppBuilder addLoginView() { + loginViewModel = new LoginViewModel(); + loginView = new LoginView(loginViewModel); + cardPanel.add(loginView, loginView.getViewName()); return this; } /** - * Adds the Login View to the application. + * Adds the Search View to the application. * @return this builder */ - public AppBuilder addLoginView() { - loginViewModel = new LoginViewModel(); - loginView = new LoginView(loginViewModel); - cardPanel.add(loginView, loginView.getViewName()); + public AppBuilder addSearchView() { + searchViewModel = new SearchViewModel(); + searchView = new SearchView(searchViewModel); + cardPanel.add(searchView, searchView.getViewName()); + final LoginOutputBoundary loginOutputBoundary = new LoginPresenter(viewManagerModel, + loggedInViewModel, loginViewModel); + final LoginInputBoundary loginInteractor = new LoginInteractor( + userDataAccessObject,spotifyDataAccessObject, loginOutputBoundary); + final LoginController loginController = new LoginController(loginInteractor); + searchView.setLoginController(loginController); + return this; + } + + /** + * Adds the Recommendation View to the application. + * @return this builder + */ + public AppBuilder addRecommendationsView() { + recommendViewModel = new RecommendViewModel(); + recommendationsView = new RecommendationsView(recommendViewModel); + cardPanel.add(recommendationsView, recommendationsView.getViewName()); return this; } @@ -105,49 +156,56 @@ public AppBuilder addLoggedInView() { } /** - * Adds the Signup Use Case to the application. + * Adds the Top Tracks and Artists View to the application. * @return this builder */ - public AppBuilder addSignupUseCase() { - final SignupOutputBoundary signupOutputBoundary = new SignupPresenter(viewManagerModel, - signupViewModel, loginViewModel); - final SignupInputBoundary userSignupInteractor = new SignupInteractor( - userDataAccessObject, signupOutputBoundary, userFactory); - - final SignupController controller = new SignupController(userSignupInteractor); - signupView.setSignupController(controller); + public AppBuilder addTopItemsView() { + topTracksAndArtistsViewModel = new TopItemsViewModel(); + topItemsView = new TopItemsView(topTracksAndArtistsViewModel); + cardPanel.add(topItemsView, topItemsView.getViewName()); return this; } /** - * Adds the Login Use Case to the application. + * Adds the SimilarListeners View to the application. * @return this builder */ - public AppBuilder addLoginUseCase() { + public AppBuilder addSimilarListenersView() { + similarListenersViewModel = new SimilarListenersViewModel(); + similarListenersView = new SimilarListenersView(similarListenersViewModel); + cardPanel.add(similarListenersView, similarListenersView.getViewName()); + return this; + } + + /** + * Adds the Keyword View to the application. + * @return this builder + */ + public AppBuilder addKeywordView() { + keywordViewModel = new KeywordViewModel(); + keywordView = new KeywordView(keywordViewModel); + cardPanel.add(keywordView, keywordView.getViewName()); final LoginOutputBoundary loginOutputBoundary = new LoginPresenter(viewManagerModel, loggedInViewModel, loginViewModel); final LoginInputBoundary loginInteractor = new LoginInteractor( - userDataAccessObject, loginOutputBoundary); - + userDataAccessObject,spotifyDataAccessObject, loginOutputBoundary); final LoginController loginController = new LoginController(loginInteractor); - loginView.setLoginController(loginController); + keywordView.setLoginController(loginController); return this; } /** - * Adds the Change Password Use Case to the application. + * Adds the Login Use Case to the application. * @return this builder */ - public AppBuilder addChangePasswordUseCase() { - final ChangePasswordOutputBoundary changePasswordOutputBoundary = - new ChangePasswordPresenter(loggedInViewModel); - - final ChangePasswordInputBoundary changePasswordInteractor = - new ChangePasswordInteractor(userDataAccessObject, changePasswordOutputBoundary, userFactory); + public AppBuilder addLoginUseCase() { + final LoginOutputBoundary loginOutputBoundary = new LoginPresenter(viewManagerModel, + loggedInViewModel, loginViewModel); + final LoginInputBoundary loginInteractor = new LoginInteractor( + userDataAccessObject,spotifyDataAccessObject, loginOutputBoundary); - final ChangePasswordController changePasswordController = - new ChangePasswordController(changePasswordInteractor); - loggedInView.setChangePasswordController(changePasswordController); + final LoginController loginController = new LoginController(loginInteractor); + loginView.setLoginController(loginController); return this; } @@ -166,18 +224,136 @@ public AppBuilder addLogoutUseCase() { loggedInView.setLogoutController(logoutController); return this; } + /** + * Adds the Keyword Search Use Case to the application. + * @return this builder + */ + public AppBuilder addKeywordUseCase() { + final LoginOutputBoundary loginOutputBoundary = new LoginPresenter(viewManagerModel, + loggedInViewModel, loginViewModel); + final LoginInputBoundary loginInteractor = new LoginInteractor( + userDataAccessObject,spotifyDataAccessObject, loginOutputBoundary); + + final LoginController loginController = new LoginController(loginInteractor); + keywordView.setLoginController(loginController); + + final KeywordOutputBoundary keywordOutputBoundary = new KeywordPresenter(viewManagerModel, + loggedInViewModel, keywordViewModel); + + final KeywordInputBoundary keywordInteractor = + new KeywordInteractor(spotifyDataAccessObject, keywordOutputBoundary); + + final KeywordController keywordController = new KeywordController(keywordInteractor); + keywordView.setKeywordController(keywordController); + loggedInView.setKeywordController(keywordController); + return this; + } + /** + * Adds the Search Use Case to the application. + * @return this builder + */ + public AppBuilder addSearchUseCase() { + final LoginOutputBoundary loginOutputBoundary = new LoginPresenter(viewManagerModel, + loggedInViewModel, loginViewModel); + final LoginInputBoundary loginInteractor = new LoginInteractor( + userDataAccessObject,spotifyDataAccessObject, loginOutputBoundary); + + final LoginController loginController = new LoginController(loginInteractor); + searchView.setLoginController(loginController); + + final SearchOutputBoundary searchOutputBoundary = new SearchPresenter(viewManagerModel, + loggedInViewModel, searchViewModel); + + final SearchInputBoundary searchInteractor = + new SearchInteractor(languageModelDataAccessObject, searchOutputBoundary); + + final SearchController searchController = new SearchController(searchInteractor); + searchView.setSearchController(searchController); + loggedInView.setSearchController(searchController); + return this; + } + + /** + * Adds the Top Tracks and Artists Use Case to the application. + * @return this builder + */ + public AppBuilder addTopItemsUseCase() { + + final LoginOutputBoundary loginOutputBoundary = new LoginPresenter(viewManagerModel, + loggedInViewModel, loginViewModel); + final LoginInputBoundary loginInteractor = new LoginInteractor( + userDataAccessObject,spotifyDataAccessObject, loginOutputBoundary); + + final LoginController loginController = new LoginController(loginInteractor); + topItemsView.setLoginController(loginController); + + final TopItemsOutputBoundary topItemsOutputBoundary = new TopItemsPresenter(viewManagerModel, + topTracksAndArtistsViewModel); + + final TopItemsInputBoundary topItemsInputBoundary = + new TopItemsInteractor(spotifyDataAccessObject, topItemsOutputBoundary); + + final TopItemsController topItemsController = new TopItemsController(topItemsInputBoundary); + loggedInView.setTopTracksController(topItemsController); + return this; + } + + /** + * Adds the Recommend Use Case to the application. + * @return this builder + */ + public AppBuilder addRecommendUseCase() { + final LoginOutputBoundary loginOutputBoundary = new LoginPresenter(viewManagerModel, + loggedInViewModel, loginViewModel); + final LoginInputBoundary loginInteractor = new LoginInteractor( + userDataAccessObject,spotifyDataAccessObject, loginOutputBoundary); + + final LoginController loginController = new LoginController(loginInteractor); + recommendationsView.setLoginController(loginController); + + final RecommendOutputBoundary recommendOutputBoundary = + new RecommendPresenter(viewManagerModel, recommendViewModel); + final RecommendInputBoundary recommendInputBoundary = + new RecommendInteractor(recommendUserDataAccessObject, recommendOutputBoundary); + + final RecommendController recommendController = new RecommendController(recommendInputBoundary); + loggedInView.setRecommendController(recommendController); + return this; + } + + public AppBuilder addSimilarListenersUseCase() { + final LoginOutputBoundary loginOutputBoundary = new LoginPresenter(viewManagerModel, + loggedInViewModel, loginViewModel); + final LoginInputBoundary loginInteractor = new LoginInteractor( + userDataAccessObject,spotifyDataAccessObject, loginOutputBoundary); + + final LoginController loginController = new LoginController(loginInteractor); + similarListenersView.setLoginController(loginController); + + final SimilarListenersOutputBoundary similarListenersOutputBoundary = + new SimilarListenersPresenter(similarListenersViewModel, viewManagerModel); + final SimilarListenersInputBoundary similarListenersInputBoundary = + new SimilarListenersInteractor(spotifyDataAccessObject, similarListenersOutputBoundary); + final SimilarListenersController similarListenersController = + new SimilarListenersController(similarListenersInputBoundary); + loggedInView.setSimilarListenersController(similarListenersController); + return this; + } /** * Creates the JFrame for the application and initially sets the SignupView to be displayed. * @return the application */ public JFrame build() { - final JFrame application = new JFrame("Login Example"); + final JFrame application = new JFrame("CSC207 Project: Spotify Companion App"); application.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); +// Trying to set default size to app, but its not working +// application.setSize(Constants.APP_WIDTH, Constants.APP_HEIGHT); + application.add(cardPanel); - viewManagerModel.setState(signupView.getViewName()); + viewManagerModel.setState(loginViewModel.getViewName()); viewManagerModel.firePropertyChanged(); return application; diff --git a/src/main/java/app/Main.java b/src/main/java/app/Main.java index bef63ad7a..18f98cc65 100644 --- a/src/main/java/app/Main.java +++ b/src/main/java/app/Main.java @@ -1,6 +1,8 @@ package app; -import javax.swing.JFrame; +import javax.swing.*; +//import java.Constants; +import java.awt.*; /** * The Main class of our application. @@ -11,16 +13,24 @@ public class Main { * @param args unused arguments */ public static void main(String[] args) { + final AppBuilder appBuilder = new AppBuilder(); - // TODO: add the Logout Use Case to the app using the appBuilder final JFrame application = appBuilder - .addLoginView() - .addSignupView() - .addLoggedInView() - .addSignupUseCase() - .addLoginUseCase() - .addChangePasswordUseCase() - .build(); + .addLoginView() + .addRecommendationsView() + .addSearchView() + .addTopItemsView() + .addKeywordView() + .addLoggedInView() + .addSimilarListenersView() + .addLoginUseCase() + .addLogoutUseCase() + .addRecommendUseCase() + .addSearchUseCase() + .addTopItemsUseCase() + .addSimilarListenersUseCase() + .addKeywordUseCase() + .build(); application.pack(); application.setVisible(true); diff --git a/src/main/java/data_access/DBUserDataAccessObject.java b/src/main/java/data_access/DBUserDataAccessObject.java deleted file mode 100644 index 377ee6e7e..000000000 --- a/src/main/java/data_access/DBUserDataAccessObject.java +++ /dev/null @@ -1,165 +0,0 @@ -package data_access; - -import java.io.IOException; - -import org.json.JSONException; -import org.json.JSONObject; - -import entity.User; -import entity.UserFactory; -import okhttp3.MediaType; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.Response; -import use_case.change_password.ChangePasswordUserDataAccessInterface; -import use_case.login.LoginUserDataAccessInterface; -import use_case.logout.LogoutUserDataAccessInterface; -import use_case.signup.SignupUserDataAccessInterface; - -/** - * The DAO for user data. - */ -public class DBUserDataAccessObject implements SignupUserDataAccessInterface, - LoginUserDataAccessInterface, - ChangePasswordUserDataAccessInterface, - LogoutUserDataAccessInterface { - private static final int SUCCESS_CODE = 200; - private static final String CONTENT_TYPE_LABEL = "Content-Type"; - private static final String CONTENT_TYPE_JSON = "application/json"; - private static final String STATUS_CODE_LABEL = "status_code"; - private static final String USERNAME = "username"; - private static final String PASSWORD = "password"; - private static final String MESSAGE = "message"; - private final UserFactory userFactory; - - public DBUserDataAccessObject(UserFactory userFactory) { - this.userFactory = userFactory; - // No need to do anything to reinitialize a user list! The data is the cloud that may be miles away. - } - - @Override - public User get(String username) { - // Make an API call to get the user object. - final OkHttpClient client = new OkHttpClient().newBuilder().build(); - final Request request = new Request.Builder() - .url(String.format("http://vm003.teach.cs.toronto.edu:20112/user?username=%s", username)) - .addHeader("Content-Type", CONTENT_TYPE_JSON) - .build(); - try { - final Response response = client.newCall(request).execute(); - - final JSONObject responseBody = new JSONObject(response.body().string()); - - if (responseBody.getInt(STATUS_CODE_LABEL) == SUCCESS_CODE) { - final JSONObject userJSONObject = responseBody.getJSONObject("user"); - final String name = userJSONObject.getString(USERNAME); - final String password = userJSONObject.getString(PASSWORD); - - return userFactory.create(name, password); - } - else { - throw new RuntimeException(responseBody.getString(MESSAGE)); - } - } - catch (IOException | JSONException ex) { - throw new RuntimeException(ex); - } - } - - @Override - public void setCurrentUsername(String name) { - // this isn't implemented for the lab - } - - @Override - public boolean existsByName(String username) { - final OkHttpClient client = new OkHttpClient().newBuilder() - .build(); - final Request request = new Request.Builder() - .url(String.format("http://vm003.teach.cs.toronto.edu:20112/checkIfUserExists?username=%s", username)) - .addHeader(CONTENT_TYPE_LABEL, CONTENT_TYPE_JSON) - .build(); - try { - final Response response = client.newCall(request).execute(); - - final JSONObject responseBody = new JSONObject(response.body().string()); - - return responseBody.getInt(STATUS_CODE_LABEL) == SUCCESS_CODE; - } - catch (IOException | JSONException ex) { - throw new RuntimeException(ex); - } - } - - @Override - public void save(User user) { - final OkHttpClient client = new OkHttpClient().newBuilder() - .build(); - - // POST METHOD - final MediaType mediaType = MediaType.parse(CONTENT_TYPE_JSON); - final JSONObject requestBody = new JSONObject(); - requestBody.put(USERNAME, user.getName()); - requestBody.put(PASSWORD, user.getPassword()); - final RequestBody body = RequestBody.create(requestBody.toString(), mediaType); - final Request request = new Request.Builder() - .url("http://vm003.teach.cs.toronto.edu:20112/user") - .method("POST", body) - .addHeader(CONTENT_TYPE_LABEL, CONTENT_TYPE_JSON) - .build(); - try { - final Response response = client.newCall(request).execute(); - - final JSONObject responseBody = new JSONObject(response.body().string()); - - if (responseBody.getInt(STATUS_CODE_LABEL) == SUCCESS_CODE) { - // success! - } - else { - throw new RuntimeException(responseBody.getString(MESSAGE)); - } - } - catch (IOException | JSONException ex) { - throw new RuntimeException(ex); - } - } - - @Override - public void changePassword(User user) { - final OkHttpClient client = new OkHttpClient().newBuilder() - .build(); - - // POST METHOD - final MediaType mediaType = MediaType.parse(CONTENT_TYPE_JSON); - final JSONObject requestBody = new JSONObject(); - requestBody.put(USERNAME, user.getName()); - requestBody.put(PASSWORD, user.getPassword()); - final RequestBody body = RequestBody.create(requestBody.toString(), mediaType); - final Request request = new Request.Builder() - .url("http://vm003.teach.cs.toronto.edu:20112/user") - .method("PUT", body) - .addHeader(CONTENT_TYPE_LABEL, CONTENT_TYPE_JSON) - .build(); - try { - final Response response = client.newCall(request).execute(); - - final JSONObject responseBody = new JSONObject(response.body().string()); - - if (responseBody.getInt(STATUS_CODE_LABEL) == SUCCESS_CODE) { - // success! - } - else { - throw new RuntimeException(responseBody.getString(MESSAGE)); - } - } - catch (IOException | JSONException ex) { - throw new RuntimeException(ex); - } - } - - @Override - public String getCurrentUsername() { - return null; - } -} diff --git a/src/main/java/data_access/FileUserDataAccessObject.java b/src/main/java/data_access/FileUserDataAccessObject.java deleted file mode 100644 index d301a3241..000000000 --- a/src/main/java/data_access/FileUserDataAccessObject.java +++ /dev/null @@ -1,117 +0,0 @@ -package data_access; - -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.Map; - -import entity.User; -import entity.UserFactory; -import use_case.change_password.ChangePasswordUserDataAccessInterface; -import use_case.login.LoginUserDataAccessInterface; -import use_case.signup.SignupUserDataAccessInterface; - -/** - * DAO for user data implemented using a File to persist the data. - */ -public class FileUserDataAccessObject implements SignupUserDataAccessInterface, - LoginUserDataAccessInterface, - ChangePasswordUserDataAccessInterface { - - private static final String HEADER = "username,password"; - - private final File csvFile; - private final Map headers = new LinkedHashMap<>(); - private final Map accounts = new HashMap<>(); - private String currentUsername; - - public FileUserDataAccessObject(String csvPath, UserFactory userFactory) throws IOException { - - csvFile = new File(csvPath); - headers.put("username", 0); - headers.put("password", 1); - - if (csvFile.length() == 0) { - save(); - } - else { - - try (BufferedReader reader = new BufferedReader(new FileReader(csvFile))) { - final String header = reader.readLine(); - - if (!header.equals(HEADER)) { - throw new RuntimeException(String.format("header should be%n: %s%but was:%n%s", HEADER, header)); - } - - String row; - while ((row = reader.readLine()) != null) { - final String[] col = row.split(","); - final String username = String.valueOf(col[headers.get("username")]); - final String password = String.valueOf(col[headers.get("password")]); - final User user = userFactory.create(username, password); - accounts.put(username, user); - } - } - } - } - - private void save() { - final BufferedWriter writer; - try { - writer = new BufferedWriter(new FileWriter(csvFile)); - writer.write(String.join(",", headers.keySet())); - writer.newLine(); - - for (User user : accounts.values()) { - final String line = String.format("%s,%s", - user.getName(), user.getPassword()); - writer.write(line); - writer.newLine(); - } - - writer.close(); - - } - catch (IOException ex) { - throw new RuntimeException(ex); - } - } - - @Override - public void save(User user) { - accounts.put(user.getName(), user); - this.save(); - } - - @Override - public User get(String username) { - return accounts.get(username); - } - - @Override - public void setCurrentUsername(String name) { - this.currentUsername = name; - } - - @Override - public String getCurrentUsername() { - return this.currentUsername; - } - - @Override - public boolean existsByName(String identifier) { - return accounts.containsKey(identifier); - } - - @Override - public void changePassword(User user) { - // Replace the User object in the map - accounts.put(user.getName(), user); - save(); - } -} diff --git a/src/main/java/data_access/InMemoryUserDataAccessObject.java b/src/main/java/data_access/InMemoryUserDataAccessObject.java index 71f00862c..73dd25511 100644 --- a/src/main/java/data_access/InMemoryUserDataAccessObject.java +++ b/src/main/java/data_access/InMemoryUserDataAccessObject.java @@ -4,20 +4,16 @@ import java.util.Map; import entity.User; -import use_case.change_password.ChangePasswordUserDataAccessInterface; import use_case.login.LoginUserDataAccessInterface; import use_case.logout.LogoutUserDataAccessInterface; -import use_case.signup.SignupUserDataAccessInterface; /** * In-memory implementation of the DAO for storing user data. This implementation does * NOT persist data between runs of the program. */ -public class InMemoryUserDataAccessObject implements SignupUserDataAccessInterface, +public class InMemoryUserDataAccessObject implements LoginUserDataAccessInterface, - ChangePasswordUserDataAccessInterface, LogoutUserDataAccessInterface { - private final Map users = new HashMap<>(); private String currentUsername; @@ -29,7 +25,7 @@ public boolean existsByName(String identifier) { @Override public void save(User user) { - users.put(user.getName(), user); + users.put(user.getAccessToken(), user); } @Override @@ -38,18 +34,12 @@ public User get(String username) { } @Override - public void changePassword(User user) { - // Replace the old entry with the new password - users.put(user.getName(), user); - } - - @Override - public void setCurrentUsername(String name) { + public void setCurrentAccessToken(String name) { this.currentUsername = name; } @Override - public String getCurrentUsername() { + public String getCurrentAccessToken() { return this.currentUsername; } } diff --git a/src/main/java/data_access/KeywordTestDataAccessObject.java b/src/main/java/data_access/KeywordTestDataAccessObject.java new file mode 100644 index 000000000..12b5520d3 --- /dev/null +++ b/src/main/java/data_access/KeywordTestDataAccessObject.java @@ -0,0 +1,18 @@ +package data_access; + +import use_case.keyword.KeywordDataAccessInterface; +import java.util.ArrayList; +import java.util.List; + +public class KeywordTestDataAccessObject implements KeywordDataAccessInterface { + + @Override + public List searchSongs(String artistName, String keyword) { + if ("Taylor Swift".equals(artistName) && "Love".equals(keyword)) { + List results = new ArrayList<>(); + results.add("Love Story"); + return results; + } + return new ArrayList<>(); + } +} \ No newline at end of file diff --git a/src/main/java/data_access/LanguageModelDataAccessObject.java b/src/main/java/data_access/LanguageModelDataAccessObject.java new file mode 100644 index 000000000..47bd57ae8 --- /dev/null +++ b/src/main/java/data_access/LanguageModelDataAccessObject.java @@ -0,0 +1,149 @@ +package data_access; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.util.*; + +import com.azure.ai.openai.OpenAIClient; +import com.azure.ai.openai.OpenAIClientBuilder; +import com.azure.ai.openai.models.ChatCompletions; +import com.azure.ai.openai.models.ChatCompletionsOptions; +import com.azure.ai.openai.models.ChatRequestAssistantMessage; +import com.azure.ai.openai.models.ChatRequestMessage; +import com.azure.ai.openai.models.ChatRequestSystemMessage; +import com.azure.ai.openai.models.ChatRequestUserMessage; +import com.azure.core.credential.AzureKeyCredential; +import org.jetbrains.annotations.NotNull; +import use_case.recommend.RecommendLanguageModelDataAccessInterface; +import use_case.search.SearchLanguageModelDataAccessInterface; + +/** + * Data access object to connect to Azure openAI. + */ +public class LanguageModelDataAccessObject implements RecommendLanguageModelDataAccessInterface, + SearchLanguageModelDataAccessInterface { + + final private String endpoint = "https://spotifycompanion.openai.azure.com/"; + final private String accessToken; + private boolean keyExists; + final private String errorMessage ="The file which should contain the " + + "azure access token does not exist in the correct place. Either create the file and place a valid api key in it or move the file to the src folder"; + + public LanguageModelDataAccessObject() { + accessToken = getKey(); + } + + /** + * This is a helper method to get the Azure API key. + * @return the access token stored in the local keys.txt file + */ + private String getKey() { + final File file; + file = new File("src/keys"); + String key = "not found"; + try { + this.keyExists = file.exists(); + final BufferedReader br = new BufferedReader(new FileReader(file)); + key = br.readLine(); + + br.close(); + } + catch (FileNotFoundException ex) { + this.keyExists = false; + System.out.println(errorMessage); + return key; + } + catch (IOException ex) { + this.keyExists = false; + System.out.println(errorMessage); + System.out.println("It appears the file does not exist"); + return key; + } + return key; + } + + @Override + public String query(String prompt) { + if (!this.keyExists) { + return errorMessage; + } + final OpenAIClient client = new OpenAIClientBuilder() + .credential(new AzureKeyCredential(accessToken)) + .endpoint(endpoint) + .buildClient(); + final List chatMessages = new ArrayList<>(); + chatMessages.add(new ChatRequestSystemMessage("You are a Spotify analyst, you will either send back a " + + "list of songs based on a description or a list of songs which are similar to a provided list.")); + chatMessages.add(new ChatRequestUserMessage("Can you help me find songs similar to Driver's License")); + chatMessages.add(new ChatRequestAssistantMessage(" Similar songs include Femininomenon by Chappell Roan" + + ", Birds of A Feather by Billie Eilish and Part of Me by Noah Kahan")); + chatMessages.add(new ChatRequestUserMessage("Is there a song about a house in the south of" + + " the US and includes something about the sun?")); + chatMessages.add(new ChatRequestUserMessage("House of the Rising Sun by The Animals.")); + chatMessages.add(new ChatRequestUserMessage(prompt)); + + final ChatCompletions chatCompletions = client.getChatCompletions("gpt-4", + new ChatCompletionsOptions(chatMessages)); + final int back = chatCompletions.getChoices().size() - 1; + return chatCompletions.getChoices().get(back).getMessage().getContent(); + } + + @Override + public String getRecommendations(List songs, String topArtists) { + if (accessToken.isEmpty()) { + return "Error getting access token"; + } + if (topArtists.isEmpty()) { + return "Error getting artists from Spotify"; + } + if (songs.isEmpty()) { + return "Error getting tracks from Spotify"; + } + + final OpenAIClient client = new OpenAIClientBuilder() + .credential(new AzureKeyCredential(accessToken)) + .endpoint(endpoint) + .buildClient(); + final List chatMessages = new ArrayList<>(); + chatMessages.add(new ChatRequestSystemMessage("You are a Spotify analyst, you will send back a " + + "list of songs which are similar to a given list of songs and artists that a user has listened to.")); + chatMessages.add(new ChatRequestUserMessage("A user's top artists are" + topArtists + + ". Their top tracks are" + songs + ". What are some song recommendations, based on these?")); + chatMessages.add(new ChatRequestUserMessage(String.valueOf(songs))); + + final ChatCompletions chatCompletions = client.getChatCompletions("gpt-4", + new ChatCompletionsOptions(chatMessages)); + StringBuilder result = getStringBuilder(chatCompletions); + + // Return the result as a string + return result.toString(); + } + + @NotNull + private static StringBuilder getStringBuilder(ChatCompletions chatCompletions) { + final int back = chatCompletions.getChoices().size() - 1; + String response = chatCompletions.getChoices().get(back).getMessage().getContent(); + + // Removes extra characters placed by the AI + response = response.replace("*", ""); + + String[] lines = response.split("\n"); + + // Create a new StringBuilder to hold the result + StringBuilder result = new StringBuilder(); + + // Append lines from the second to the second last (index 1 to length-2) + for (int i = 1; i < lines.length - 1; i++) { + if (!Objects.equals(lines[i], "\n")) { + result.append(lines[i]); + } + if (i < lines.length - 2) { + result.append("\n"); // Add a newline character if not the last line + } + } + return result; + } +} diff --git a/src/main/java/data_access/LoginTestDataAccessObject.java b/src/main/java/data_access/LoginTestDataAccessObject.java new file mode 100644 index 000000000..05df81fa7 --- /dev/null +++ b/src/main/java/data_access/LoginTestDataAccessObject.java @@ -0,0 +1,10 @@ +package data_access; + +import use_case.login.LoginDataAccessInterface; + +public class LoginTestDataAccessObject implements LoginDataAccessInterface { + @Override + public void setAccessToken(String newToken) { + + } +} diff --git a/src/main/java/data_access/RecommendTestDataAccessObject.java b/src/main/java/data_access/RecommendTestDataAccessObject.java new file mode 100644 index 000000000..3d8a16992 --- /dev/null +++ b/src/main/java/data_access/RecommendTestDataAccessObject.java @@ -0,0 +1,50 @@ +package data_access; + +import use_case.recommend.RecommendSpotifyDataAccessInterface; +import use_case.recommend.RecommendUserDataAccessInterface; + +import java.util.ArrayList; +import java.util.List; + +public class RecommendTestDataAccessObject implements RecommendSpotifyDataAccessInterface, + RecommendUserDataAccessInterface { + private List topTracks; + private String topArtists; + + public RecommendTestDataAccessObject(){ + topTracks = new ArrayList<>(); + topTracks.add("(sic)"); + topTracks.add("Left Behind"); + topTracks.add("Spit It Out"); + topTracks.add("People = Shit"); + topTracks.add("Metabolic"); + topTracks.add("Everything Ends"); + + topArtists = "Slipknot, Soulfly, Korn, Sepultura, System Of A Down"; + } + + @Override + public List getTopTracks() { + return topTracks; + } + + @Override + public void setTopTracks(List topTracks) { + this.topTracks = topTracks; + } + + @Override + public String getTopArtists() { + return topArtists; + } + + @Override + public void setTopArtists(String topArtists) { + this.topArtists = topArtists; + } + + @Override + public String getRecommendations(List songs, String topArtists) { + return ""; + } +} diff --git a/src/main/java/data_access/RecommendUserDataAccessObject.java b/src/main/java/data_access/RecommendUserDataAccessObject.java new file mode 100644 index 000000000..33c961d69 --- /dev/null +++ b/src/main/java/data_access/RecommendUserDataAccessObject.java @@ -0,0 +1,45 @@ +package data_access; + +import use_case.recommend.RecommendUserDataAccessInterface; +import data_access.LanguageModelDataAccessObject; +import data_access.SpotifyDataAccessObject; + +import java.util.ArrayList; +import java.util.List; + +public class RecommendUserDataAccessObject implements RecommendUserDataAccessInterface { + private List topTracks; + private String topArtists; + + public RecommendUserDataAccessObject() { + final SpotifyDataAccessObject spotifyDataAccessObject = new SpotifyDataAccessObject(); + topTracks = spotifyDataAccessObject.getCurrentTopTracks(); + topArtists = spotifyDataAccessObject.getTopArtists(); + } + + @Override + public List getTopTracks() { + return topTracks; + } + + @Override + public void setTopTracks(List topTracks) { + this.topTracks = topTracks; + } + + @Override + public String getTopArtists() { + return topArtists; + } + + @Override + public void setTopArtists(String artists) { + this.topArtists = artists; + } + + @Override + public String getRecommendations(List songs, String topArtists) { + final LanguageModelDataAccessObject languageModel = new LanguageModelDataAccessObject(); + return languageModel.getRecommendations(songs, topArtists); + } +} diff --git a/src/main/java/data_access/SearchTestDataAccessObject.java b/src/main/java/data_access/SearchTestDataAccessObject.java new file mode 100644 index 000000000..afed8707d --- /dev/null +++ b/src/main/java/data_access/SearchTestDataAccessObject.java @@ -0,0 +1,13 @@ +package data_access; + +import use_case.search.SearchLanguageModelDataAccessInterface; + +public class SearchTestDataAccessObject implements SearchLanguageModelDataAccessInterface { + + public SearchTestDataAccessObject(){} + + @Override + public String query(String prompt) { + return "One Piece at a Time by Johnny Cash"; + } +} diff --git a/src/main/java/data_access/SimilarListenersTestDataAccessObject.java b/src/main/java/data_access/SimilarListenersTestDataAccessObject.java new file mode 100644 index 000000000..fa3733ed8 --- /dev/null +++ b/src/main/java/data_access/SimilarListenersTestDataAccessObject.java @@ -0,0 +1,21 @@ +package data_access; + +import use_case.similar_listeners.SimilarListenersDataAccessInterface; + +import java.util.List; + +public class SimilarListenersTestDataAccessObject implements SimilarListenersDataAccessInterface { + + private List followedArtists; + + @Override + public List getFollowedArtists() { + return this.followedArtists; + } + + @Override + public void setCurrentFollowedArtists(List artists) { + this.followedArtists = artists; + + } +} diff --git a/src/main/java/data_access/SpotifyDataAccessObject.java b/src/main/java/data_access/SpotifyDataAccessObject.java new file mode 100644 index 000000000..428aeb83b --- /dev/null +++ b/src/main/java/data_access/SpotifyDataAccessObject.java @@ -0,0 +1,314 @@ +package data_access; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import entity.CommonUserFactory; +import entity.User; +import entity.UserFactory; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import interface_adapter.login.LoginState; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import org.apache.hc.core5.http.ParseException; +import se.michaelthelin.spotify.SpotifyApi; +import se.michaelthelin.spotify.enums.ModelObjectType; +import se.michaelthelin.spotify.exceptions.SpotifyWebApiException; +import se.michaelthelin.spotify.model_objects.specification.*; +import se.michaelthelin.spotify.requests.data.browse.GetRecommendationsRequest; +import se.michaelthelin.spotify.requests.data.follow.GetUsersFollowedArtistsRequest; +import se.michaelthelin.spotify.requests.data.personalization.simplified.GetUsersTopArtistsRequest; +import se.michaelthelin.spotify.requests.data.personalization.simplified.GetUsersTopTracksRequest; +import use_case.login.LoginDataAccessInterface; +import use_case.recommend.RecommendUserDataAccessInterface; +import use_case.keyword.KeywordDataAccessInterface; +import use_case.similar_listeners.SimilarListenersDataAccessInterface; +import use_case.top_items.TopItemsDataAccessInterface; + +/** + * DAO for getting relevant information from Spotify API. + */ + +public class SpotifyDataAccessObject implements TopItemsDataAccessInterface, + SimilarListenersDataAccessInterface, RecommendUserDataAccessInterface, KeywordDataAccessInterface, LoginDataAccessInterface { + public static final int OFFSET = 4; + public static final int OFFSET2 = 0; + + private LoginState loginState = new LoginState(); + private String accessToken; + private List currentTopTracks; + private List currentTopArtists; + private List currentSearchResults; + private SpotifyApi spotifyApi; + private GetUsersTopTracksRequest getTopTracksRequest; + private GetUsersTopArtistsRequest getTopArtistsRequest; + + private GetUsersFollowedArtistsRequest getUsersFollowedArtistsRequest; + private static final ModelObjectType type = ModelObjectType.ARTIST; + private List currFollowedArtists; + + private GetRecommendationsRequest getRecommendationsRequest; + + public SpotifyDataAccessObject() { + this.accessToken = "this should never be reached"; + this.spotifyApi = new SpotifyApi.Builder() + .setAccessToken(accessToken) + .build(); + this.getTopTracksRequest = spotifyApi.getUsersTopTracks() + .offset(OFFSET) + .time_range("medium_term") + .build(); + this.getTopArtistsRequest = spotifyApi.getUsersTopArtists() + .offset(OFFSET) + .time_range("medium_term") + .build(); + this.currentTopTracks = getUsersTopTracksSync(); + this.currentTopArtists = getUsersTopArtistsSync(); + + this.getUsersFollowedArtistsRequest = spotifyApi.getUsersFollowedArtists(type).build(); + this.currFollowedArtists = getUsersFollowedArtistsSync(); + + this.getRecommendationsRequest = spotifyApi.getRecommendations() + .build(); + } + + // Constructor used for testing. + public SpotifyDataAccessObject(String accessToken) { + this.accessToken = accessToken; + this.spotifyApi = new SpotifyApi.Builder() + .setAccessToken(accessToken) + .build(); + this.getTopTracksRequest = spotifyApi.getUsersTopTracks() + .offset(OFFSET) + .time_range("medium_term") + .build(); + this.getTopArtistsRequest = spotifyApi.getUsersTopArtists() + .offset(OFFSET2) + .time_range("medium_term") + .build(); + this.currentTopTracks = getUsersTopTracksSync(); + this.currentTopArtists = getUsersTopArtistsSync(); + + this.getUsersFollowedArtistsRequest = spotifyApi.getUsersFollowedArtists(type).build(); + this.currFollowedArtists = getUsersFollowedArtistsSync(); + + this.getRecommendationsRequest = spotifyApi.getRecommendations() + .build(); + } + + /** + * A helper method to get the current users top tracks. + * + * @return a list of the track names. + */ + private List getUsersTopTracksSync() { + try { + // Using Spotify Wrapper to get the given users top tracks from API + final Paging trackPaging = getTopTracksRequest.execute(); + + final Track[] tracks = trackPaging.getItems(); + + final List topTracks = new ArrayList(); + for (Track track : tracks) { + topTracks.add(track.getName()); + } + // System.out.println("Top Tracks: " + topTracks); + return topTracks; + + } catch (IOException | SpotifyWebApiException | ParseException e) { + System.out.println("Error: " + e.getMessage()); + return new ArrayList<>(); + } + } + + /** + * A helper method to get the current users top artists. + * + * @return a list of the artist names. + */ + private List getUsersTopArtistsSync() { + try { + // Using Spotify Wrapper to get the given users top artists from API + final Paging artistPaging = getTopArtistsRequest.execute(); + + final Artist[] artists = artistPaging.getItems(); + + final List topArtists = new ArrayList<>(); + for (Artist artist : artists) { + topArtists.add(artist.getName()); + } + // System.out.println("Top Artists: " + topArtists); + return topArtists; + + } catch (IOException | SpotifyWebApiException | ParseException e) { + System.out.println("Error: " + e.getMessage()); + return new ArrayList<>(); + } + } + + /** + * A helper method to get the current users followed artists. + * @return a list of the artists names. + */ + private List getUsersFollowedArtistsSync() { + try { + final PagingCursorbased artistPagingCursorbased = getUsersFollowedArtistsRequest.execute(); + final Artist[] artists = artistPagingCursorbased.getItems(); + final List followedArtists = new ArrayList<>(); + for (Artist artist : artists) { + followedArtists.add(artist.getName()); + } + // System.out.println(followedArtists); + return followedArtists; + } catch (IOException | SpotifyWebApiException | ParseException e) { + System.out.println("Error: " + e.getMessage()); + return new ArrayList<>(); + } + + } + + /** + * A helper method to get the current users recommendations from spotify. + */ + private void getRecommendationsSync() { + try { + final Recommendations recommendations = getRecommendationsRequest.execute(); + + System.out.println("Length: " + recommendations.getTracks().length); + } catch (IOException | SpotifyWebApiException | ParseException e) { + System.out.println("Error: " + e.getMessage()); + } + } + /** + * A helper method to get the current users search results from spotify. + * @param artistName the name of the artist the user is searching for. + * @param keyword the keyword search the user is making. + */ + private void getSearchResultsSync(String artistName, String keyword) { + final String SEARCH_URL = "https://api.spotify.com/v1/search"; + OkHttpClient client = new OkHttpClient(); + + // Build the search query URL + HttpUrl url = HttpUrl.parse(SEARCH_URL).newBuilder() + .addQueryParameter("q", "artist:" + artistName + " " + keyword) // Combine artist and keyword + .addQueryParameter("type", "track") + .build(); + + // Prepare the API request + Request request = new Request.Builder() + .url(url) + .addHeader("Authorization", "Bearer " + accessToken) // Use the provided access token + .get() + .build(); + + try (Response response = client.newCall(request).execute()) { + if (response.isSuccessful() && response.body() != null) { + JsonObject jsonObject = JsonParser.parseString(response.body().string()).getAsJsonObject(); + List songs = new ArrayList(); + + // Extract tracks from the response + jsonObject.getAsJsonObject("tracks").getAsJsonArray("items").forEach(track -> { + String songName = track.getAsJsonObject().get("name").getAsString(); + songs.add(songName); // Add the track name to the list + }); + currentSearchResults = songs; + } else { + throw new RuntimeException("Failed to search songs: " + response.message()); + } + } catch (IOException e) { + throw new RuntimeException("Error searching songs", e); + } + } + + @Override + public List getCurrentTopTracks() { + return this.currentTopTracks; + } + + @Override + public List getTopTracks() { + return getUsersTopTracksSync(); + } + + public void setTopTracks(List topTracks) { + this.currentTopTracks = topTracks; + } + + @Override + public String getTopArtists() { + final List lst = currentTopArtists; + final StringBuilder sb = new StringBuilder(); + if (lst != null) {for (int i = 0; i < lst.size(); i ++) { + sb.append(lst.get(i)); + + if (i != lst.size() - 1) { + sb.append(", "); + } + }} + + return sb.toString(); + } + + @Override + public void setTopArtists(String artists) { + this.currentTopArtists = new ArrayList<>(); + this.currentTopArtists.addAll(Arrays.asList(artists.split(", "))); + } + + @Override + public String getRecommendations(List songs, String topArtists) { + return ""; + } + + @Override + public void setCurrentTopTracks(List tracks) { + this.currentTopTracks = tracks; + } + + @Override + public List getCurrentTopArtists() { + return this.currentTopArtists; + } + + + @Override + public void setCurrentTopArtists(List artists) { + this.currentTopArtists = artists; + } + + @Override + public List getFollowedArtists() { + return this.currFollowedArtists; + } + + @Override + public void setCurrentFollowedArtists(List followedArtists) { + this.currFollowedArtists = followedArtists; + + } + + @Override + public List searchSongs(String artistName, String keyword) { + getSearchResultsSync(artistName, keyword); + return getCurrentSearchResults(); + } + + public List getCurrentSearchResults() { + return currentSearchResults; + } + + public void setCurrentSearchResults(List currentSearchResults) { + this.currentSearchResults = currentSearchResults; + } + + @Override + public void setAccessToken(String newToken) { + this.accessToken = newToken; + } +} + diff --git a/src/main/java/data_access/TopItemsDataAccessObject.java b/src/main/java/data_access/TopItemsDataAccessObject.java new file mode 100644 index 000000000..66445f3b9 --- /dev/null +++ b/src/main/java/data_access/TopItemsDataAccessObject.java @@ -0,0 +1,35 @@ +package data_access; + +import use_case.top_items.TopItemsDataAccessInterface; + +import java.util.List; + +public class TopItemsDataAccessObject implements TopItemsDataAccessInterface { + + private List tracks; + private List artists; + + public TopItemsDataAccessObject() { + } + + @Override + public List getCurrentTopTracks() { + return tracks; + } + + @Override + public void setCurrentTopTracks(List tracks) { + this.tracks = tracks; + } + + @Override + public List getCurrentTopArtists() { + return artists; + } + + @Override + public void setCurrentTopArtists(List artists) { + this.artists = artists; + + } +} diff --git a/src/main/java/entity/CommonUser.java b/src/main/java/entity/CommonUser.java index ba25fd20a..5b83d883d 100644 --- a/src/main/java/entity/CommonUser.java +++ b/src/main/java/entity/CommonUser.java @@ -5,22 +5,14 @@ */ public class CommonUser implements User { - private final String name; - private final String password; + private final String accessToken; - public CommonUser(String name, String password) { - this.name = name; - this.password = password; + public CommonUser(String accessToken) { + this.accessToken = accessToken; } @Override - public String getName() { - return name; + public String getAccessToken() { + return accessToken; } - - @Override - public String getPassword() { - return password; - } - } diff --git a/src/main/java/entity/CommonUserFactory.java b/src/main/java/entity/CommonUserFactory.java index ebede69e3..31e71c31a 100644 --- a/src/main/java/entity/CommonUserFactory.java +++ b/src/main/java/entity/CommonUserFactory.java @@ -6,7 +6,7 @@ public class CommonUserFactory implements UserFactory { @Override - public User create(String name, String password) { - return new CommonUser(name, password); + public User create(String accessToken) { + return new CommonUser(accessToken); } } diff --git a/src/main/java/entity/User.java b/src/main/java/entity/User.java index 0ad073902..89e1df302 100644 --- a/src/main/java/entity/User.java +++ b/src/main/java/entity/User.java @@ -9,12 +9,6 @@ public interface User { * Returns the username of the user. * @return the username of the user. */ - String getName(); - - /** - * Returns the password of the user. - * @return the password of the user. - */ - String getPassword(); + String getAccessToken(); } diff --git a/src/main/java/entity/UserFactory.java b/src/main/java/entity/UserFactory.java index c7a508708..87418402a 100644 --- a/src/main/java/entity/UserFactory.java +++ b/src/main/java/entity/UserFactory.java @@ -6,10 +6,10 @@ public interface UserFactory { /** * Creates a new User. - * @param name the name of the new user - * @param password the password of the new user + * @param accessToken the name of the new user + // * @param password the password of the new user * @return the new user */ - User create(String name, String password); + User create(String accessToken); } diff --git a/src/main/java/interface_adapter/change_password/ChangePasswordController.java b/src/main/java/interface_adapter/change_password/ChangePasswordController.java deleted file mode 100644 index a7abd075f..000000000 --- a/src/main/java/interface_adapter/change_password/ChangePasswordController.java +++ /dev/null @@ -1,26 +0,0 @@ -package interface_adapter.change_password; - -import use_case.change_password.ChangePasswordInputBoundary; -import use_case.change_password.ChangePasswordInputData; - -/** - * Controller for the Change Password Use Case. - */ -public class ChangePasswordController { - private final ChangePasswordInputBoundary userChangePasswordUseCaseInteractor; - - public ChangePasswordController(ChangePasswordInputBoundary userChangePasswordUseCaseInteractor) { - this.userChangePasswordUseCaseInteractor = userChangePasswordUseCaseInteractor; - } - - /** - * Executes the Change Password Use Case. - * @param password the new password - * @param username the user whose password to change - */ - public void execute(String password, String username) { - final ChangePasswordInputData changePasswordInputData = new ChangePasswordInputData(username, password); - - userChangePasswordUseCaseInteractor.execute(changePasswordInputData); - } -} diff --git a/src/main/java/interface_adapter/change_password/ChangePasswordPresenter.java b/src/main/java/interface_adapter/change_password/ChangePasswordPresenter.java deleted file mode 100644 index 3efca4e46..000000000 --- a/src/main/java/interface_adapter/change_password/ChangePasswordPresenter.java +++ /dev/null @@ -1,31 +0,0 @@ -package interface_adapter.change_password; - -import use_case.change_password.ChangePasswordOutputBoundary; -import use_case.change_password.ChangePasswordOutputData; - -/** - * The Presenter for the Change Password Use Case. - */ -public class ChangePasswordPresenter implements ChangePasswordOutputBoundary { - - private final LoggedInViewModel loggedInViewModel; - - public ChangePasswordPresenter(LoggedInViewModel loggedInViewModel) { - this.loggedInViewModel = loggedInViewModel; - } - - @Override - public void prepareSuccessView(ChangePasswordOutputData outputData) { - // currently there isn't anything to change based on the output data, - // since the output data only contains the username, which remains the same. - // We still fire the property changed event, but just to let the view know that - // it can alert the user that their password was changed successfully.. - loggedInViewModel.firePropertyChanged("password"); - - } - - @Override - public void prepareFailView(String error) { - // note: this use case currently can't fail - } -} diff --git a/src/main/java/interface_adapter/change_password/LoggedInState.java b/src/main/java/interface_adapter/change_password/LoggedInState.java deleted file mode 100644 index 54ed80537..000000000 --- a/src/main/java/interface_adapter/change_password/LoggedInState.java +++ /dev/null @@ -1,42 +0,0 @@ -package interface_adapter.change_password; - -/** - * The State information representing the logged-in user. - */ -public class LoggedInState { - private String username = ""; - - private String password = ""; - private String passwordError; - - public LoggedInState(LoggedInState copy) { - username = copy.username; - password = copy.password; - passwordError = copy.passwordError; - } - - // Because of the previous copy constructor, the default constructor must be explicit. - public LoggedInState() { - - } - - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } - - public void setPassword(String password) { - this.password = password; - } - - public void setPasswordError(String passwordError) { - this.passwordError = passwordError; - } - - public String getPassword() { - return password; - } -} diff --git a/src/main/java/interface_adapter/keyword/KeywordController.java b/src/main/java/interface_adapter/keyword/KeywordController.java new file mode 100644 index 000000000..7f30658c3 --- /dev/null +++ b/src/main/java/interface_adapter/keyword/KeywordController.java @@ -0,0 +1,31 @@ + +package interface_adapter.keyword; + +import use_case.keyword.KeywordInputBoundary; + +public class KeywordController { + private final KeywordInputBoundary interactor; + + public KeywordController(KeywordInputBoundary interactor) { + this.interactor = interactor; + } + + + + /** + * Goes to the keyword use case page. + */ + public void execute() { + interactor.execute(); + } + + /** + * Executes the keyword Use Case. + * + * @param artist the artist the user wants info about. + * @param keyword the keyword the user wants info about. + */ + public void executeSearch(String artist, String keyword) { + interactor.executeSearch(artist, keyword); + } +} \ No newline at end of file diff --git a/src/main/java/interface_adapter/keyword/KeywordPresenter.java b/src/main/java/interface_adapter/keyword/KeywordPresenter.java new file mode 100644 index 000000000..0cb9645e5 --- /dev/null +++ b/src/main/java/interface_adapter/keyword/KeywordPresenter.java @@ -0,0 +1,45 @@ +package interface_adapter.keyword; + +import interface_adapter.ViewManagerModel; +import interface_adapter.logged_in.LoggedInViewModel; +import interface_adapter.search.SearchState; +import interface_adapter.search.SearchViewModel; +import use_case.keyword.KeywordOutputBoundary; +import use_case.keyword.KeywordOutputData; +/** + * The Presenter for the Search Use Case. + */ +public class KeywordPresenter implements KeywordOutputBoundary { + private LoggedInViewModel loggedInViewModel; + private ViewManagerModel viewManagerModel; + private KeywordViewModel keywordViewModel; + + public KeywordPresenter(ViewManagerModel viewManagerModel, + LoggedInViewModel loggedInViewModel, + KeywordViewModel keywordViewModel) { + // Done: assign to the three instance variables. + this.loggedInViewModel = loggedInViewModel; + this.viewManagerModel = viewManagerModel; + this.keywordViewModel = keywordViewModel; + + } + + @Override + public void prepareSuccessView(KeywordOutputData outputData) { + final KeywordState keywordState = keywordViewModel.getState(); + keywordState.setDisplayText(outputData.getSongs()); + keywordViewModel.setState(keywordState); + keywordViewModel.firePropertyChanged(); + // This code tells the View Manager to switch to the keyword. + this.viewManagerModel.setState(keywordViewModel.getViewName()); + this.viewManagerModel.firePropertyChanged(); + } + + @Override + public void prepareFailView(String errorMessage) { + final KeywordState keywordState = keywordViewModel.getState(); + keywordState.setDisplayText(errorMessage); // Set error message to display + keywordViewModel.setState(keywordState); + keywordViewModel.firePropertyChanged(); // Notify view of changes + } +} \ No newline at end of file diff --git a/src/main/java/interface_adapter/keyword/KeywordState.java b/src/main/java/interface_adapter/keyword/KeywordState.java new file mode 100644 index 000000000..613225204 --- /dev/null +++ b/src/main/java/interface_adapter/keyword/KeywordState.java @@ -0,0 +1,30 @@ +package interface_adapter.keyword; +/** + * The state for the KeywordViewModel. + */ +public class KeywordState { + private String displayText; + private String accessToken; + + public KeywordState(KeywordState copy) { + displayText = copy.displayText; + accessToken = copy.accessToken; + } + + // Because of the previous copy constructor, the default constructor must be explicit. + public KeywordState() { + + } + public String getDisplayText() {return displayText;} + public void setDisplayText(String displayText) { + this.displayText = displayText; + } + + public String getAccessToken() { + return accessToken; + } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } +} diff --git a/src/main/java/interface_adapter/keyword/KeywordViewModel.java b/src/main/java/interface_adapter/keyword/KeywordViewModel.java new file mode 100644 index 000000000..88ae8f3b1 --- /dev/null +++ b/src/main/java/interface_adapter/keyword/KeywordViewModel.java @@ -0,0 +1,17 @@ +package interface_adapter.keyword; + +import interface_adapter.ViewModel; +import interface_adapter.search.SearchState; + +import java.util.List; +/** +* The view model for the Search use case. +*/ +public class KeywordViewModel extends ViewModel { + + public KeywordViewModel() { + super("keyword"); + setState(new KeywordState()); + } + +} \ No newline at end of file diff --git a/src/main/java/interface_adapter/logged_in/LoggedInState.java b/src/main/java/interface_adapter/logged_in/LoggedInState.java new file mode 100644 index 000000000..ef885b214 --- /dev/null +++ b/src/main/java/interface_adapter/logged_in/LoggedInState.java @@ -0,0 +1,33 @@ +package interface_adapter.logged_in; + +/** + * The State information representing the logged-in user. + */ +public class LoggedInState { + + private String accessToken = ""; + private String tokenError; + + public LoggedInState(LoggedInState copy) { + accessToken = copy.accessToken; + tokenError = copy.tokenError; + } + + // Because of the previous copy constructor, the default constructor must be explicit. + public LoggedInState() { + + } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + public String getAccessToken() { + return accessToken; + } + + public void settokenError(String accessTokenError) { + this.tokenError = accessTokenError; + } + +} diff --git a/src/main/java/interface_adapter/change_password/LoggedInViewModel.java b/src/main/java/interface_adapter/logged_in/LoggedInViewModel.java similarity index 85% rename from src/main/java/interface_adapter/change_password/LoggedInViewModel.java rename to src/main/java/interface_adapter/logged_in/LoggedInViewModel.java index 85c81e4e6..ba0184f58 100644 --- a/src/main/java/interface_adapter/change_password/LoggedInViewModel.java +++ b/src/main/java/interface_adapter/logged_in/LoggedInViewModel.java @@ -1,4 +1,4 @@ -package interface_adapter.change_password; +package interface_adapter.logged_in; import interface_adapter.ViewModel; diff --git a/src/main/java/interface_adapter/login/LoginController.java b/src/main/java/interface_adapter/login/LoginController.java index 57e950666..0e4213298 100644 --- a/src/main/java/interface_adapter/login/LoginController.java +++ b/src/main/java/interface_adapter/login/LoginController.java @@ -16,13 +16,22 @@ public LoginController(LoginInputBoundary loginUseCaseInteractor) { /** * Executes the Login Use Case. - * @param username the username of the user logging in - * @param password the password of the user logging in + * @param loginToken the username of the user logging in */ - public void execute(String username, String password) { + public void execute(String loginToken) { final LoginInputData loginInputData = new LoginInputData( - username, password); + loginToken); loginUseCaseInteractor.execute(loginInputData); } + + /** + * Executes the Login Use Case without changing the token. + */ + public void execute() { + final LoginInputData loginInputData = new LoginInputData(); + + loginUseCaseInteractor.execute(loginInputData); + } + } diff --git a/src/main/java/interface_adapter/login/LoginPresenter.java b/src/main/java/interface_adapter/login/LoginPresenter.java index 66560d51a..670232a3a 100644 --- a/src/main/java/interface_adapter/login/LoginPresenter.java +++ b/src/main/java/interface_adapter/login/LoginPresenter.java @@ -1,8 +1,8 @@ package interface_adapter.login; import interface_adapter.ViewManagerModel; -import interface_adapter.change_password.LoggedInState; -import interface_adapter.change_password.LoggedInViewModel; +import interface_adapter.logged_in.LoggedInState; +import interface_adapter.logged_in.LoggedInViewModel; import use_case.login.LoginOutputBoundary; import use_case.login.LoginOutputData; @@ -28,7 +28,6 @@ public void prepareSuccessView(LoginOutputData response) { // On success, switch to the logged in view. final LoggedInState loggedInState = loggedInViewModel.getState(); - loggedInState.setUsername(response.getUsername()); this.loggedInViewModel.setState(loggedInState); this.loggedInViewModel.firePropertyChanged(); diff --git a/src/main/java/interface_adapter/login/LoginState.java b/src/main/java/interface_adapter/login/LoginState.java index e42632034..16e1d802c 100644 --- a/src/main/java/interface_adapter/login/LoginState.java +++ b/src/main/java/interface_adapter/login/LoginState.java @@ -4,32 +4,22 @@ * The state for the Login View Model. */ public class LoginState { - private String username = ""; + private String loginToken = ""; private String loginError; - private String password = ""; - public String getUsername() { - return username; + public String getLoginToken() { + return loginToken; } public String getLoginError() { return loginError; } - public String getPassword() { - return password; + public void setLoginToken(String loginToken) { + this.loginToken = loginToken; } - public void setUsername(String username) { - this.username = username; + public void setLoginError(String accessTokenError) { + this.loginError = accessTokenError; } - - public void setLoginError(String usernameError) { - this.loginError = usernameError; - } - - public void setPassword(String password) { - this.password = password; - } - } diff --git a/src/main/java/interface_adapter/logout/LogoutController.java b/src/main/java/interface_adapter/logout/LogoutController.java index e184a3bba..558ec6bcc 100644 --- a/src/main/java/interface_adapter/logout/LogoutController.java +++ b/src/main/java/interface_adapter/logout/LogoutController.java @@ -1,6 +1,7 @@ package interface_adapter.logout; import use_case.logout.LogoutInputBoundary; +import use_case.logout.LogoutInputData; /** * The controller for the Logout Use Case. @@ -10,7 +11,7 @@ public class LogoutController { private LogoutInputBoundary logoutUseCaseInteractor; public LogoutController(LogoutInputBoundary logoutUseCaseInteractor) { - // TODO: Save the interactor in the instance variable. + this.logoutUseCaseInteractor = logoutUseCaseInteractor; } /** @@ -18,8 +19,9 @@ public LogoutController(LogoutInputBoundary logoutUseCaseInteractor) { * @param username the username of the user logging in */ public void execute(String username) { - // TODO: run the use case interactor for the logout use case // 1. instantiate the `LogoutInputData`, which should contain the username. // 2. tell the Interactor to execute. + final LogoutInputData logoutInputData = new LogoutInputData(username); + logoutUseCaseInteractor.execute(logoutInputData); } } diff --git a/src/main/java/interface_adapter/logout/LogoutPresenter.java b/src/main/java/interface_adapter/logout/LogoutPresenter.java index 78ef306a1..a9a72f4b3 100644 --- a/src/main/java/interface_adapter/logout/LogoutPresenter.java +++ b/src/main/java/interface_adapter/logout/LogoutPresenter.java @@ -1,7 +1,9 @@ package interface_adapter.logout; import interface_adapter.ViewManagerModel; -import interface_adapter.change_password.LoggedInViewModel; +import interface_adapter.logged_in.LoggedInState; +import interface_adapter.logged_in.LoggedInViewModel; +import interface_adapter.login.LoginState; import interface_adapter.login.LoginViewModel; import use_case.logout.LogoutOutputBoundary; import use_case.logout.LogoutOutputData; @@ -16,9 +18,12 @@ public class LogoutPresenter implements LogoutOutputBoundary { private LoginViewModel loginViewModel; public LogoutPresenter(ViewManagerModel viewManagerModel, - LoggedInViewModel loggedInViewModel, + LoggedInViewModel loggedInViewModel, LoginViewModel loginViewModel) { - // TODO: assign to the three instance variables. + this.loggedInViewModel = loggedInViewModel; + this.viewManagerModel = viewManagerModel; + this.loginViewModel = loginViewModel; + } @Override @@ -29,17 +34,26 @@ public void prepareSuccessView(LogoutOutputData response) { // We also need to set the username in the LoggedInState to // the empty string. - // TODO: have prepareSuccessView update the LoggedInState + // Done have prepareSuccessView update the LoggedInState // 1. get the LoggedInState out of the appropriate View Model, // 2. set the username in the state to the empty string // 3. set the state in the LoggedInViewModel to the updated state // 4. firePropertyChanged so that the View that is listening is updated. + final LoggedInState loggedInState = loggedInViewModel.getState(); + loggedInState.setAccessToken(""); + loggedInViewModel.setState(loggedInState); + loggedInViewModel.firePropertyChanged(); - // TODO: have prepareSuccessView update the LoginState + // Done: have prepareSuccessView update the LoginState // 5. get the LoginState out of the appropriate View Model, // 6. set the username and password in the state to the empty string // 7. set the state in the LoginViewModel to the updated state // 8. firePropertyChanged so that the View that is listening is updated. + final LoginState loginState = loginViewModel.getState(); + loginState.setLoginToken(""); + // loginState.setPassword(""); + loginViewModel.setState(loginState); + loggedInViewModel.firePropertyChanged(); // This code tells the View Manager to switch to the LoginView. this.viewManagerModel.setState(loginViewModel.getViewName()); diff --git a/src/main/java/interface_adapter/recommend/RecommendController.java b/src/main/java/interface_adapter/recommend/RecommendController.java new file mode 100644 index 000000000..3e506c6b7 --- /dev/null +++ b/src/main/java/interface_adapter/recommend/RecommendController.java @@ -0,0 +1,29 @@ +package interface_adapter.recommend; + +import java.util.List; +import java.util.Map; + +import use_case.recommend.RecommendInputBoundary; +import use_case.recommend.RecommendInputData; + +/** + * The controller for the Recommend Use Case. + */ +public class RecommendController { + private final RecommendInputBoundary recommendUseCaseInteractor; + + public RecommendController(RecommendInputBoundary recommendUseCaseInteractor) { + this.recommendUseCaseInteractor = recommendUseCaseInteractor; + } + + /** + * Executes the Recommend Use Case. + * @param songRecommendations the map of song recommendations in the form of song, artist + */ + public void execute(List songRecommendations, String topArtists, String accessToken) { + final RecommendInputData recommendInputData = new RecommendInputData( + songRecommendations, topArtists, accessToken); + + recommendUseCaseInteractor.execute(recommendInputData); + } +} diff --git a/src/main/java/interface_adapter/recommend/RecommendPresenter.java b/src/main/java/interface_adapter/recommend/RecommendPresenter.java new file mode 100644 index 000000000..5859555de --- /dev/null +++ b/src/main/java/interface_adapter/recommend/RecommendPresenter.java @@ -0,0 +1,37 @@ +package interface_adapter.recommend; + +import interface_adapter.ViewManagerModel; +import use_case.recommend.RecommendOutputBoundary; +import use_case.recommend.RecommendOutputData; + +/** + * The Presenter for the Recommend Use Case. + */ +public class RecommendPresenter implements RecommendOutputBoundary { + private final RecommendViewModel recommendViewModel; + private final ViewManagerModel viewManagerModel; + + public RecommendPresenter(ViewManagerModel viewManagerModel, + RecommendViewModel recommendViewModel) { + this.viewManagerModel = viewManagerModel; + this.recommendViewModel = recommendViewModel; + } + + @Override + public void prepareSuccessView(RecommendOutputData output) { + final RecommendState recommendState = recommendViewModel.getState(); + recommendState.setAccessToken(output.getAccessToken()); + recommendState.setTopArtists(output.getTopArtists()); + recommendState.setSongRecommendations(output.getSongRecommendations()); + this.recommendViewModel.setState(recommendState); + this.recommendViewModel.firePropertyChanged(); + + this.viewManagerModel.setState(recommendViewModel.getViewName()); + this.viewManagerModel.firePropertyChanged(); + } + + @Override + public void prepareFailView(String errorMessage) { + System.out.printf(errorMessage); + } +} diff --git a/src/main/java/interface_adapter/recommend/RecommendState.java b/src/main/java/interface_adapter/recommend/RecommendState.java new file mode 100644 index 000000000..14ccb0b96 --- /dev/null +++ b/src/main/java/interface_adapter/recommend/RecommendState.java @@ -0,0 +1,39 @@ +package interface_adapter.recommend; + +/** + * The state for the Recommendation View Model. + */ +public class RecommendState { + private String songRecommendations; + private String accessToken; + private String topArtists; + + // Because of the previous copy constructor, the default constructor must be explicit. + public RecommendState() { + + } + + public void setSongRecommendations(String songRecommendations) { + this.songRecommendations = songRecommendations; + } + + public String getSongRecommendations() { + return songRecommendations; + } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + public String getAccessToken() { + return accessToken; + } + + public void setTopArtists(String topArtists) { + this.topArtists = topArtists; + } + + public String getTopArtists() { + return topArtists; + } +} diff --git a/src/main/java/interface_adapter/recommend/RecommendViewModel.java b/src/main/java/interface_adapter/recommend/RecommendViewModel.java new file mode 100644 index 000000000..5b0907bce --- /dev/null +++ b/src/main/java/interface_adapter/recommend/RecommendViewModel.java @@ -0,0 +1,13 @@ +package interface_adapter.recommend; + +import interface_adapter.ViewModel; + +/** + * The View Model for the Recommendations View. + */ +public class RecommendViewModel extends ViewModel { + public RecommendViewModel() { + super("recommendations"); + setState(new RecommendState()); + } +} diff --git a/src/main/java/interface_adapter/search/SearchController.java b/src/main/java/interface_adapter/search/SearchController.java new file mode 100644 index 000000000..dfd11b93e --- /dev/null +++ b/src/main/java/interface_adapter/search/SearchController.java @@ -0,0 +1,32 @@ +package interface_adapter.search; + +import use_case.search.SearchInputBoundary; + +/** + * The controller for the Search Use Case. + */ +public class SearchController { + + private final SearchInputBoundary searchUseCaseInteractor; + + public SearchController(SearchInputBoundary searchUseCaseInteractor) { + this.searchUseCaseInteractor = searchUseCaseInteractor; + } + + /** + * Goes to the search use case. + * @param accessToken the access token for spotify. + */ + public void execute(String accessToken) { + searchUseCaseInteractor.execute(accessToken); + } + + /** + * Executes the Search Use Case. + * @param searchText the search the user is inputting. + * @param accessToken the access token for spotify. + */ + public void executeSearch(String accessToken, String searchText) { + searchUseCaseInteractor.executeSearch(accessToken, searchText); + } +} diff --git a/src/main/java/interface_adapter/search/SearchPresenter.java b/src/main/java/interface_adapter/search/SearchPresenter.java new file mode 100644 index 000000000..6ff2b544d --- /dev/null +++ b/src/main/java/interface_adapter/search/SearchPresenter.java @@ -0,0 +1,56 @@ +package interface_adapter.search; + +import interface_adapter.ViewManagerModel; +import interface_adapter.logged_in.LoggedInViewModel; +import interface_adapter.login.LoginViewModel; +import use_case.search.SearchOutputBoundary; +import use_case.search.SearchOutputData; + +import interface_adapter.ViewManagerModel; +import interface_adapter.logged_in.LoggedInState; +import interface_adapter.logged_in.LoggedInViewModel; +import interface_adapter.login.LoginState; +import interface_adapter.login.LoginViewModel; +import use_case.logout.LogoutOutputBoundary; +import use_case.logout.LogoutOutputData; + +/** + * The Presenter for the Search Use Case. + */ +public class SearchPresenter implements SearchOutputBoundary { + + private LoggedInViewModel loggedInViewModel; + private ViewManagerModel viewManagerModel; + private SearchViewModel searchViewModel; + + public SearchPresenter(ViewManagerModel viewManagerModel, + LoggedInViewModel loggedInViewModel, + SearchViewModel searchViewModel) { + // Done: assign to the three instance variables. + this.loggedInViewModel = loggedInViewModel; + this.viewManagerModel = viewManagerModel; + this.searchViewModel = searchViewModel; + + } + + @Override + public void prepareSuccessView(SearchOutputData response) { + + final SearchState searchState = searchViewModel.getState(); + searchState.setAccessToken(response.getAccessToken()); + searchState.setDisplayText(response.getDisplayText()); + searchViewModel.setState(searchState); + searchViewModel.firePropertyChanged(); + + + // This code tells the View Manager to switch to the Search. + this.viewManagerModel.setState(searchViewModel.getViewName()); + this.viewManagerModel.firePropertyChanged(); + } + + @Override + public void prepareFailView(String error) { + // No need to add code here. We'll assume that logout can't fail. + // Thought question: is this a reasonable assumption? + } +} diff --git a/src/main/java/interface_adapter/search/SearchState.java b/src/main/java/interface_adapter/search/SearchState.java new file mode 100644 index 000000000..6cc4b28b2 --- /dev/null +++ b/src/main/java/interface_adapter/search/SearchState.java @@ -0,0 +1,32 @@ +package interface_adapter.search; + +/** + * The state for the SearchViewModel. + */ +public class SearchState { + + private String displayText; + private String accessToken; + + public SearchState(SearchState copy) { + displayText = copy.displayText; + accessToken = copy.accessToken; + } + + // Because of the previous copy constructor, the default constructor must be explicit. + public SearchState() { + + } + public String getDisplayText() {return displayText;} + public void setDisplayText(String displayText) { + this.displayText = displayText; + } + + public String getAccessToken() { + return accessToken; + } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } +} diff --git a/src/main/java/interface_adapter/search/SearchViewModel.java b/src/main/java/interface_adapter/search/SearchViewModel.java new file mode 100644 index 000000000..e953dff33 --- /dev/null +++ b/src/main/java/interface_adapter/search/SearchViewModel.java @@ -0,0 +1,14 @@ +package interface_adapter.search; + +import interface_adapter.ViewModel; + +/** +* The view model for the Search use case. + */ +public class SearchViewModel extends ViewModel { + + public SearchViewModel() { + super("search"); + setState(new SearchState()); + } +} diff --git a/src/main/java/interface_adapter/signup/SignupController.java b/src/main/java/interface_adapter/signup/SignupController.java deleted file mode 100644 index c01d25aa5..000000000 --- a/src/main/java/interface_adapter/signup/SignupController.java +++ /dev/null @@ -1,36 +0,0 @@ -package interface_adapter.signup; - -import use_case.signup.SignupInputBoundary; -import use_case.signup.SignupInputData; - -/** - * Controller for the Signup Use Case. - */ -public class SignupController { - - private final SignupInputBoundary userSignupUseCaseInteractor; - - public SignupController(SignupInputBoundary userSignupUseCaseInteractor) { - this.userSignupUseCaseInteractor = userSignupUseCaseInteractor; - } - - /** - * Executes the Signup Use Case. - * @param username the username to sign up - * @param password1 the password - * @param password2 the password repeated - */ - public void execute(String username, String password1, String password2) { - final SignupInputData signupInputData = new SignupInputData( - username, password1, password2); - - userSignupUseCaseInteractor.execute(signupInputData); - } - - /** - * Executes the "switch to LoginView" Use Case. - */ - public void switchToLoginView() { - userSignupUseCaseInteractor.switchToLoginView(); - } -} diff --git a/src/main/java/interface_adapter/signup/SignupPresenter.java b/src/main/java/interface_adapter/signup/SignupPresenter.java deleted file mode 100644 index 9b654077e..000000000 --- a/src/main/java/interface_adapter/signup/SignupPresenter.java +++ /dev/null @@ -1,50 +0,0 @@ -package interface_adapter.signup; - -import interface_adapter.ViewManagerModel; -import interface_adapter.login.LoginState; -import interface_adapter.login.LoginViewModel; -import use_case.signup.SignupOutputBoundary; -import use_case.signup.SignupOutputData; - -/** - * The Presenter for the Signup Use Case. - */ -public class SignupPresenter implements SignupOutputBoundary { - - private final SignupViewModel signupViewModel; - private final LoginViewModel loginViewModel; - private final ViewManagerModel viewManagerModel; - - public SignupPresenter(ViewManagerModel viewManagerModel, - SignupViewModel signupViewModel, - LoginViewModel loginViewModel) { - this.viewManagerModel = viewManagerModel; - this.signupViewModel = signupViewModel; - this.loginViewModel = loginViewModel; - } - - @Override - public void prepareSuccessView(SignupOutputData response) { - // On success, switch to the login view. - final LoginState loginState = loginViewModel.getState(); - loginState.setUsername(response.getUsername()); - this.loginViewModel.setState(loginState); - loginViewModel.firePropertyChanged(); - - viewManagerModel.setState(loginViewModel.getViewName()); - viewManagerModel.firePropertyChanged(); - } - - @Override - public void prepareFailView(String error) { - final SignupState signupState = signupViewModel.getState(); - signupState.setUsernameError(error); - signupViewModel.firePropertyChanged(); - } - - @Override - public void switchToLoginView() { - viewManagerModel.setState(loginViewModel.getViewName()); - viewManagerModel.firePropertyChanged(); - } -} diff --git a/src/main/java/interface_adapter/signup/SignupState.java b/src/main/java/interface_adapter/signup/SignupState.java deleted file mode 100644 index 4a7f69327..000000000 --- a/src/main/java/interface_adapter/signup/SignupState.java +++ /dev/null @@ -1,70 +0,0 @@ -package interface_adapter.signup; - -/** - * The state for the Signup View Model. - */ -public class SignupState { - private String username = ""; - private String usernameError; - private String password = ""; - private String passwordError; - private String repeatPassword = ""; - private String repeatPasswordError; - - public String getUsername() { - return username; - } - - public String getUsernameError() { - return usernameError; - } - - public String getPassword() { - return password; - } - - public String getPasswordError() { - return passwordError; - } - - public String getRepeatPassword() { - return repeatPassword; - } - - public String getRepeatPasswordError() { - return repeatPasswordError; - } - - public void setUsername(String username) { - this.username = username; - } - - public void setUsernameError(String usernameError) { - this.usernameError = usernameError; - } - - public void setPassword(String password) { - this.password = password; - } - - public void setPasswordError(String passwordError) { - this.passwordError = passwordError; - } - - public void setRepeatPassword(String repeatPassword) { - this.repeatPassword = repeatPassword; - } - - public void setRepeatPasswordError(String repeatPasswordError) { - this.repeatPasswordError = repeatPasswordError; - } - - @Override - public String toString() { - return "SignupState{" - + "username='" + username + '\'' - + ", password='" + password + '\'' - + ", repeatPassword='" + repeatPassword + '\'' - + '}'; - } -} diff --git a/src/main/java/interface_adapter/signup/SignupViewModel.java b/src/main/java/interface_adapter/signup/SignupViewModel.java deleted file mode 100644 index 01f0086bc..000000000 --- a/src/main/java/interface_adapter/signup/SignupViewModel.java +++ /dev/null @@ -1,25 +0,0 @@ -package interface_adapter.signup; - -import interface_adapter.ViewModel; - -/** - * The ViewModel for the Signup View. - */ -public class SignupViewModel extends ViewModel { - - public static final String TITLE_LABEL = "Sign Up View"; - public static final String USERNAME_LABEL = "Choose username"; - public static final String PASSWORD_LABEL = "Choose password"; - public static final String REPEAT_PASSWORD_LABEL = "Enter password again"; - - public static final String SIGNUP_BUTTON_LABEL = "Sign up"; - public static final String CANCEL_BUTTON_LABEL = "Cancel"; - - public static final String TO_LOGIN_BUTTON_LABEL = "Go to Login"; - - public SignupViewModel() { - super("sign up"); - setState(new SignupState()); - } - -} diff --git a/src/main/java/interface_adapter/similar_listeners/SimilarListenersController.java b/src/main/java/interface_adapter/similar_listeners/SimilarListenersController.java new file mode 100644 index 000000000..77dde9c77 --- /dev/null +++ b/src/main/java/interface_adapter/similar_listeners/SimilarListenersController.java @@ -0,0 +1,26 @@ +package interface_adapter.similar_listeners; + +import use_case.similar_listeners.SimilarListenersInputBoundary; +import use_case.similar_listeners.SimilarListenersInputData; + +/** + * The controller for SimilarListeners use case. + */ +public class SimilarListenersController { + private final SimilarListenersInputBoundary similarListenersInputBoundary; + + public SimilarListenersController(SimilarListenersInputBoundary similarListenersUseCaseInteractor) { + this.similarListenersInputBoundary = similarListenersUseCaseInteractor; + } + + /** + * Executes the Login Use Case. + * @param accessToken the username of the user logging in + */ + public void execute(String accessToken) { + final SimilarListenersInputData similarListenersInputData = new SimilarListenersInputData(accessToken); + + similarListenersInputBoundary.execute(similarListenersInputData); + } +} + diff --git a/src/main/java/interface_adapter/similar_listeners/SimilarListenersPresenter.java b/src/main/java/interface_adapter/similar_listeners/SimilarListenersPresenter.java new file mode 100644 index 000000000..21e948b42 --- /dev/null +++ b/src/main/java/interface_adapter/similar_listeners/SimilarListenersPresenter.java @@ -0,0 +1,47 @@ +package interface_adapter.similar_listeners; + +import interface_adapter.ViewManagerModel; +import use_case.similar_listeners.SimilarListenersOutputBoundary; +import use_case.similar_listeners.SimilarListenersOutputData; + +import java.util.ArrayList; + +/** + * The Presenter for Similar Listeners use case. + */ +public class SimilarListenersPresenter implements SimilarListenersOutputBoundary { + private final SimilarListenersViewModel similarListenersViewModel; + private final ViewManagerModel viewManagerModel; + + public SimilarListenersPresenter(SimilarListenersViewModel similarListenersViewModel, + ViewManagerModel viewManagerModel) { + this.similarListenersViewModel = similarListenersViewModel; + this.viewManagerModel = viewManagerModel; + + } + + @Override + public void prepareSuccessView(SimilarListenersOutputData similarListenersOutputData) { + final SimilarListenersState similarListenersState = similarListenersViewModel.getState(); + similarListenersState.setSimilarArtists(similarListenersOutputData.getSimilarArtists()); + similarListenersState.setAccessToken(similarListenersOutputData.getAccessToken()); + this.similarListenersViewModel.setState(similarListenersState); + similarListenersViewModel.firePropertyChanged(); + + this.viewManagerModel.setState(similarListenersViewModel.getViewName()); + this.viewManagerModel.firePropertyChanged(); + + } + + @Override + public void prepareFailView(String errorMessage) { + final SimilarListenersState similarListenersState = similarListenersViewModel.getState(); + similarListenersState.setSimilarArtistsError(errorMessage); + similarListenersState.setSimilarArtists(new ArrayList<>()); + similarListenersViewModel.firePropertyChanged(); + + this.viewManagerModel.setState(similarListenersViewModel.getViewName()); + this.viewManagerModel.firePropertyChanged(); + + } +} diff --git a/src/main/java/interface_adapter/similar_listeners/SimilarListenersState.java b/src/main/java/interface_adapter/similar_listeners/SimilarListenersState.java new file mode 100644 index 000000000..ad8e820b1 --- /dev/null +++ b/src/main/java/interface_adapter/similar_listeners/SimilarListenersState.java @@ -0,0 +1,48 @@ +package interface_adapter.similar_listeners; + +import java.util.List; + +/** + * The state for Similar Listeners View Model. + */ +public class SimilarListenersState { + private List similarArtists; + private String similarArtistsError; + private String accessToken; + + public SimilarListenersState(SimilarListenersState copy) { + similarArtists = copy.similarArtists; + similarArtistsError = copy.similarArtistsError; + accessToken = copy.accessToken; + } + + // Because of the previous copy constructor, the default constructor must be explicit. + public SimilarListenersState() { + + } + + public List getSimilarArtists() { + return similarArtists; + } + + public void setSimilarArtists(List similarArtists) { + this.similarArtists = similarArtists; + } + + public String getSimilarArtistsError() { + return similarArtistsError; + } + + void setSimilarArtistsError(String similarArtistsError) { + this.similarArtistsError = similarArtistsError; + } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + public String getAccessToken() { + return accessToken; + } + +} diff --git a/src/main/java/interface_adapter/similar_listeners/SimilarListenersViewModel.java b/src/main/java/interface_adapter/similar_listeners/SimilarListenersViewModel.java new file mode 100644 index 000000000..5d48b2846 --- /dev/null +++ b/src/main/java/interface_adapter/similar_listeners/SimilarListenersViewModel.java @@ -0,0 +1,13 @@ +package interface_adapter.similar_listeners; + +import interface_adapter.ViewModel; + +/** + * The View Model for SimilarListeners View. + */ +public class SimilarListenersViewModel extends ViewModel { + public SimilarListenersViewModel() { + super("similar listeners"); + setState(new SimilarListenersState()); + } +} diff --git a/src/main/java/interface_adapter/top_items/TopItemsController.java b/src/main/java/interface_adapter/top_items/TopItemsController.java new file mode 100644 index 000000000..e01591867 --- /dev/null +++ b/src/main/java/interface_adapter/top_items/TopItemsController.java @@ -0,0 +1,26 @@ +package interface_adapter.top_items; + +import use_case.top_items.TopItemsInputBoundary; +import use_case.top_items.TopItemsInputData; + +import java.util.List; + +/** + * The controller for the TopItems Use Case. + */ +public class TopItemsController { + final private TopItemsInputBoundary topItemsUseCaseInteractor; + + public TopItemsController(TopItemsInputBoundary topItemsUseCaseInteractor) { + this.topItemsUseCaseInteractor = topItemsUseCaseInteractor; + } + + /** + * Executes the TopItems Use Case. + */ + public void execute() { + final TopItemsInputData topItemsInputData = new TopItemsInputData(); + + topItemsUseCaseInteractor.execute(topItemsInputData); + } +} diff --git a/src/main/java/interface_adapter/top_items/TopItemsPresenter.java b/src/main/java/interface_adapter/top_items/TopItemsPresenter.java new file mode 100644 index 000000000..b31b21dc7 --- /dev/null +++ b/src/main/java/interface_adapter/top_items/TopItemsPresenter.java @@ -0,0 +1,47 @@ +package interface_adapter.top_items; + +import interface_adapter.ViewManagerModel; +import use_case.top_items.TopItemsOutputBoundary; +import use_case.top_items.TopItemsOutputData; + +import java.util.ArrayList; + +/** + * The Presenter for the Top Tracks Use Case. + */ +public class TopItemsPresenter implements TopItemsOutputBoundary { + + private final TopItemsViewModel topItemsViewModel; + private final ViewManagerModel viewManagerModel; + + public TopItemsPresenter(ViewManagerModel viewManagerModel, + TopItemsViewModel topItemsViewModel) { + this.viewManagerModel = viewManagerModel; + this.topItemsViewModel = topItemsViewModel; + } + + @Override + public void prepareSuccessView(TopItemsOutputData outputData) { + final TopItemsState topItemsState = topItemsViewModel.getState(); + topItemsState.setTracks(outputData.getTracks()); + topItemsState.setArtists(outputData.getArtists()); + + this.topItemsViewModel.setState(topItemsState); + topItemsViewModel.firePropertyChanged(); + + this.viewManagerModel.setState(topItemsViewModel.getViewName()); + this.viewManagerModel.firePropertyChanged(); + } + + @Override + public void prepareFailView(String errorMessage) { + final TopItemsState topItemsState = topItemsViewModel.getState(); + topItemsState.setTracksError(errorMessage); + topItemsState.setArtistsError(errorMessage); + topItemsState.setTracks(new ArrayList<>()); + topItemsState.setArtists(new ArrayList<>()); + + this.viewManagerModel.setState(topItemsViewModel.getViewName()); + this.viewManagerModel.firePropertyChanged(); + } +} diff --git a/src/main/java/interface_adapter/top_items/TopItemsState.java b/src/main/java/interface_adapter/top_items/TopItemsState.java new file mode 100644 index 000000000..96f6561d2 --- /dev/null +++ b/src/main/java/interface_adapter/top_items/TopItemsState.java @@ -0,0 +1,67 @@ +package interface_adapter.top_items; + +import java.util.List; + +/** + * The state for the Top Tracks View Model. + */ +public class TopItemsState { + private List tracks; + private List artists; + private String tracksError; + private String artistsError; + private String accessToken; + + public TopItemsState(TopItemsState copy) { + tracks = copy.tracks; + tracksError = copy.tracksError; + accessToken = copy.accessToken; + } + + // Because of the previous copy constructor, the default constructor must be explicit. + public TopItemsState() { + + } + + public List getTracks() { + return tracks; + } + + public String getTracksError() { + return tracksError; + } + + public void setTracks(List tracks) { + this.tracks = tracks; + } + + public void setTracksError(String tracksError) { + this.tracksError = tracksError; + } + + public List getArtists() { + return artists; + } + + public String getArtistsError() { + return artistsError; + } + + public void setArtists(List artists) { + this.artists = artists; + } + + public void setArtistsError(String artistsError) { + this.artistsError = artistsError; + } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + public String getAccessToken() { + return accessToken; + } + + +} diff --git a/src/main/java/interface_adapter/top_items/TopItemsViewModel.java b/src/main/java/interface_adapter/top_items/TopItemsViewModel.java new file mode 100644 index 000000000..9a9f69094 --- /dev/null +++ b/src/main/java/interface_adapter/top_items/TopItemsViewModel.java @@ -0,0 +1,13 @@ +package interface_adapter.top_items; + +import interface_adapter.ViewModel; + +/** + * The View Model for the Top Tracks View. + */ +public class TopItemsViewModel extends ViewModel { + public TopItemsViewModel() { + super("Top Items"); + setState(new TopItemsState()); + } +} diff --git a/src/main/java/keys.txt b/src/main/java/keys.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/main/java/use_case/change_password/ChangePasswordInputBoundary.java b/src/main/java/use_case/change_password/ChangePasswordInputBoundary.java deleted file mode 100644 index 06ae9448e..000000000 --- a/src/main/java/use_case/change_password/ChangePasswordInputBoundary.java +++ /dev/null @@ -1,14 +0,0 @@ -package use_case.change_password; - -/** - * The Change Password Use Case. - */ -public interface ChangePasswordInputBoundary { - - /** - * Execute the Change Password Use Case. - * @param changePasswordInputData the input data for this use case - */ - void execute(ChangePasswordInputData changePasswordInputData); - -} diff --git a/src/main/java/use_case/change_password/ChangePasswordInputData.java b/src/main/java/use_case/change_password/ChangePasswordInputData.java deleted file mode 100644 index 8e09d8d12..000000000 --- a/src/main/java/use_case/change_password/ChangePasswordInputData.java +++ /dev/null @@ -1,24 +0,0 @@ -package use_case.change_password; - -/** - * The input data for the Change Password Use Case. - */ -public class ChangePasswordInputData { - - private final String password; - private final String username; - - public ChangePasswordInputData(String password, String username) { - this.password = password; - this.username = username; - } - - String getPassword() { - return password; - } - - String getUsername() { - return username; - } - -} diff --git a/src/main/java/use_case/change_password/ChangePasswordInteractor.java b/src/main/java/use_case/change_password/ChangePasswordInteractor.java deleted file mode 100644 index df91196c1..000000000 --- a/src/main/java/use_case/change_password/ChangePasswordInteractor.java +++ /dev/null @@ -1,32 +0,0 @@ -package use_case.change_password; - -import entity.User; -import entity.UserFactory; - -/** - * The Change Password Interactor. - */ -public class ChangePasswordInteractor implements ChangePasswordInputBoundary { - private final ChangePasswordUserDataAccessInterface userDataAccessObject; - private final ChangePasswordOutputBoundary userPresenter; - private final UserFactory userFactory; - - public ChangePasswordInteractor(ChangePasswordUserDataAccessInterface changePasswordDataAccessInterface, - ChangePasswordOutputBoundary changePasswordOutputBoundary, - UserFactory userFactory) { - this.userDataAccessObject = changePasswordDataAccessInterface; - this.userPresenter = changePasswordOutputBoundary; - this.userFactory = userFactory; - } - - @Override - public void execute(ChangePasswordInputData changePasswordInputData) { - final User user = userFactory.create(changePasswordInputData.getUsername(), - changePasswordInputData.getPassword()); - userDataAccessObject.changePassword(user); - - final ChangePasswordOutputData changePasswordOutputData = new ChangePasswordOutputData(user.getName(), - false); - userPresenter.prepareSuccessView(changePasswordOutputData); - } -} diff --git a/src/main/java/use_case/change_password/ChangePasswordOutputBoundary.java b/src/main/java/use_case/change_password/ChangePasswordOutputBoundary.java deleted file mode 100644 index fce28367b..000000000 --- a/src/main/java/use_case/change_password/ChangePasswordOutputBoundary.java +++ /dev/null @@ -1,18 +0,0 @@ -package use_case.change_password; - -/** - * The output boundary for the Change Password Use Case. - */ -public interface ChangePasswordOutputBoundary { - /** - * Prepares the success view for the Change Password Use Case. - * @param outputData the output data - */ - void prepareSuccessView(ChangePasswordOutputData outputData); - - /** - * Prepares the failure view for the Change Password Use Case. - * @param errorMessage the explanation of the failure - */ - void prepareFailView(String errorMessage); -} diff --git a/src/main/java/use_case/change_password/ChangePasswordOutputData.java b/src/main/java/use_case/change_password/ChangePasswordOutputData.java deleted file mode 100644 index b47b83fbc..000000000 --- a/src/main/java/use_case/change_password/ChangePasswordOutputData.java +++ /dev/null @@ -1,24 +0,0 @@ -package use_case.change_password; - -/** - * Output Data for the Change Password Use Case. - */ -public class ChangePasswordOutputData { - - private final String username; - - private final boolean useCaseFailed; - - public ChangePasswordOutputData(String username, boolean useCaseFailed) { - this.username = username; - this.useCaseFailed = useCaseFailed; - } - - public String getUsername() { - return username; - } - - public boolean isUseCaseFailed() { - return useCaseFailed; - } -} diff --git a/src/main/java/use_case/change_password/ChangePasswordUserDataAccessInterface.java b/src/main/java/use_case/change_password/ChangePasswordUserDataAccessInterface.java deleted file mode 100644 index 6b73ab6b0..000000000 --- a/src/main/java/use_case/change_password/ChangePasswordUserDataAccessInterface.java +++ /dev/null @@ -1,15 +0,0 @@ -package use_case.change_password; - -import entity.User; - -/** - * The interface of the DAO for the Change Password Use Case. - */ -public interface ChangePasswordUserDataAccessInterface { - - /** - * Updates the system to record this user's password. - * @param user the user whose password is to be updated - */ - void changePassword(User user); -} diff --git a/src/main/java/use_case/keyword/KeywordDataAccessInterface.java b/src/main/java/use_case/keyword/KeywordDataAccessInterface.java new file mode 100644 index 000000000..6950c3e69 --- /dev/null +++ b/src/main/java/use_case/keyword/KeywordDataAccessInterface.java @@ -0,0 +1,20 @@ + +package use_case.keyword; + +import java.util.List; + +/** + * An interface for accessing keyword-related data. + */ +public interface KeywordDataAccessInterface { + + /** + * Searches for songs based on a given artist name and keyword about the song. + * + * @param artistName the name of the artist to search for, may be null + * @param keyword a keyword about the song, possibly a lyric, may be null + * @return the related songs + */ + List searchSongs(String artistName, String keyword); + +} \ No newline at end of file diff --git a/src/main/java/use_case/keyword/KeywordInputBoundary.java b/src/main/java/use_case/keyword/KeywordInputBoundary.java new file mode 100644 index 000000000..eba6a90d4 --- /dev/null +++ b/src/main/java/use_case/keyword/KeywordInputBoundary.java @@ -0,0 +1,20 @@ + +package use_case.keyword; + +/** + * An interface for the Keyword Input Boundary. + */ +public interface KeywordInputBoundary { + + /** + * Takes us to the keyword use case. + */ + void execute(); + + /** + * Executes the keyword search use case. + * @param artist the artist user input, may be null + * @param keyword the keyword user input, may be null + */ + void executeSearch(String artist, String keyword); +} diff --git a/src/main/java/use_case/keyword/KeywordInteractor.java b/src/main/java/use_case/keyword/KeywordInteractor.java new file mode 100644 index 000000000..0a2ddc1ca --- /dev/null +++ b/src/main/java/use_case/keyword/KeywordInteractor.java @@ -0,0 +1,54 @@ + +package use_case.keyword; + +import java.util.ArrayList; +import java.util.List; + +/** + * The interactor for the "Search by Keyword" use case. + * Handles the business logic and interacts with the Spotify API service. + */ +public class KeywordInteractor implements KeywordInputBoundary { + /** + * Interacts with Spotify API. + */ + private final KeywordDataAccessInterface keywordDataAccessInterface; + + /** + * Sends results to the presenter. + */ + private final KeywordOutputBoundary keywordPresenter; + + /** + * Constructs a KeywordInteractor. + * + * @param keywordDataAccessInterface The data access object to handle API calls, must not be null. + * @param outputBoundary The output boundary to send results or errors to the presenter, must not be + * null. + */ + public KeywordInteractor(KeywordDataAccessInterface keywordDataAccessInterface, + KeywordOutputBoundary outputBoundary) { + this.keywordDataAccessInterface = keywordDataAccessInterface; + this.keywordPresenter = outputBoundary; + } + + @Override + public void execute() { + final List songs = new ArrayList<>(); + songs.add(""); + final KeywordOutputData search = new KeywordOutputData(songs); + keywordPresenter.prepareSuccessView(search); + } + + @Override + public void executeSearch(String artist, String keyword) { + if ((artist == null || artist.trim().isEmpty()) && (keyword == null || keyword.trim().isEmpty())) { + keywordPresenter.prepareFailView("Error: Artist and keyword cannot both be empty."); + } + else { + final List songs = keywordDataAccessInterface.searchSongs(artist, keyword); + final KeywordOutputData search = new KeywordOutputData(songs); + keywordPresenter.prepareSuccessView(search); + } + } +} diff --git a/src/main/java/use_case/keyword/KeywordOutputBoundary.java b/src/main/java/use_case/keyword/KeywordOutputBoundary.java new file mode 100644 index 000000000..2e2474fcf --- /dev/null +++ b/src/main/java/use_case/keyword/KeywordOutputBoundary.java @@ -0,0 +1,20 @@ + +package use_case.keyword; + +/** + * The output boundary for the keyword Search Use Case. + */ +public interface KeywordOutputBoundary { + /** + * Prepares the success view for the keyword search Use Case. + * @param outputData the output data + */ + void prepareSuccessView(KeywordOutputData outputData); + + /** + * Prepares the failure view for the keyword Search Use Case. + * @param errorMessage the explanation of the failure + */ + void prepareFailView(String errorMessage); + +} diff --git a/src/main/java/use_case/keyword/KeywordOutputData.java b/src/main/java/use_case/keyword/KeywordOutputData.java new file mode 100644 index 000000000..d8f121a3b --- /dev/null +++ b/src/main/java/use_case/keyword/KeywordOutputData.java @@ -0,0 +1,27 @@ + +package use_case.keyword; + +import java.util.List; + +public class KeywordOutputData { + private final List songs; + private final String errorMessage; + + // Constructor for successful results + public KeywordOutputData(List songs) { + this.songs = songs; + this.errorMessage = null; + } + + public String getSongs() { + return songs.toString(); + } + + public String getErrorMessage() { + return errorMessage; + } + + public boolean hasError() { + return errorMessage != null; + } +} diff --git a/src/main/java/use_case/login/LoginDataAccessInterface.java b/src/main/java/use_case/login/LoginDataAccessInterface.java new file mode 100644 index 000000000..b04c8d814 --- /dev/null +++ b/src/main/java/use_case/login/LoginDataAccessInterface.java @@ -0,0 +1,5 @@ +package use_case.login; + +public interface LoginDataAccessInterface { + void setAccessToken(String newToken); +} diff --git a/src/main/java/use_case/login/LoginInputData.java b/src/main/java/use_case/login/LoginInputData.java index 363316832..d6819931a 100644 --- a/src/main/java/use_case/login/LoginInputData.java +++ b/src/main/java/use_case/login/LoginInputData.java @@ -5,20 +5,17 @@ */ public class LoginInputData { - private final String username; - private final String password; + private final String loginToken; - public LoginInputData(String username, String password) { - this.username = username; - this.password = password; + public LoginInputData(String loginToken) { + this.loginToken = loginToken; } - - String getUsername() { - return username; + public LoginInputData() { + loginToken = "noToken"; } - String getPassword() { - return password; + String getLoginToken() { + return loginToken; } } diff --git a/src/main/java/use_case/login/LoginInteractor.java b/src/main/java/use_case/login/LoginInteractor.java index 5b36ddcd8..cc9cf4fbe 100644 --- a/src/main/java/use_case/login/LoginInteractor.java +++ b/src/main/java/use_case/login/LoginInteractor.java @@ -1,40 +1,38 @@ package use_case.login; -import entity.User; - /** * The Login Interactor. */ public class LoginInteractor implements LoginInputBoundary { private final LoginUserDataAccessInterface userDataAccessObject; + private final LoginDataAccessInterface loginDataAccessObject; private final LoginOutputBoundary loginPresenter; - public LoginInteractor(LoginUserDataAccessInterface userDataAccessInterface, + public LoginInteractor(LoginUserDataAccessInterface userDataAccessInterface, LoginDataAccessInterface loginDataAccessObject, LoginOutputBoundary loginOutputBoundary) { this.userDataAccessObject = userDataAccessInterface; + this.loginDataAccessObject = loginDataAccessObject; this.loginPresenter = loginOutputBoundary; } @Override public void execute(LoginInputData loginInputData) { - final String username = loginInputData.getUsername(); - final String password = loginInputData.getPassword(); - if (!userDataAccessObject.existsByName(username)) { - loginPresenter.prepareFailView(username + ": Account does not exist."); - } - else { - final String pwd = userDataAccessObject.get(username).getPassword(); - if (!password.equals(pwd)) { - loginPresenter.prepareFailView("Incorrect password for \"" + username + "\"."); - } - else { - - final User user = userDataAccessObject.get(loginInputData.getUsername()); - - userDataAccessObject.setCurrentUsername(user.getName()); - final LoginOutputData loginOutputData = new LoginOutputData(user.getName(), false); + try { + if (loginInputData.getLoginToken().equals("noToken")) { + final LoginOutputData loginOutputData = new LoginOutputData(false); + loginPresenter.prepareSuccessView(loginOutputData); + } else { + final String token = loginInputData.getLoginToken(); + userDataAccessObject.setCurrentAccessToken(token); + final LoginOutputData loginOutputData = new LoginOutputData(false); + loginDataAccessObject.setAccessToken(token); loginPresenter.prepareSuccessView(loginOutputData); } } + catch (Exception e) { + final LoginOutputData loginOutputData = new LoginOutputData(false); + loginPresenter.prepareSuccessView(loginOutputData); + } + } } diff --git a/src/main/java/use_case/login/LoginOutputData.java b/src/main/java/use_case/login/LoginOutputData.java index 3ea119a8f..c49302ae4 100644 --- a/src/main/java/use_case/login/LoginOutputData.java +++ b/src/main/java/use_case/login/LoginOutputData.java @@ -5,16 +5,10 @@ */ public class LoginOutputData { - private final String username; private final boolean useCaseFailed; - public LoginOutputData(String username, boolean useCaseFailed) { - this.username = username; + public LoginOutputData( boolean useCaseFailed) { this.useCaseFailed = useCaseFailed; } - public String getUsername() { - return username; - } - } diff --git a/src/main/java/use_case/login/LoginUserDataAccessInterface.java b/src/main/java/use_case/login/LoginUserDataAccessInterface.java index 681e8a52e..6c4cffe22 100644 --- a/src/main/java/use_case/login/LoginUserDataAccessInterface.java +++ b/src/main/java/use_case/login/LoginUserDataAccessInterface.java @@ -7,12 +7,7 @@ */ public interface LoginUserDataAccessInterface { - /** - * Checks if the given username exists. - * @param username the username to look for - * @return true if a user with the given username exists; false otherwise - */ - boolean existsByName(String username); + boolean existsByName(String identifier); /** * Saves the user. @@ -28,14 +23,15 @@ public interface LoginUserDataAccessInterface { User get(String username); /** - * Returns the username of the curren user of the application. - * @return the username of the current user; null indicates that no one is logged into the application. + * Returns the access token of the curren user of the application. + * @return the access Token of the current user; null indicates that no one is logged into the application. */ - String getCurrentUsername(); + String getCurrentAccessToken(); /** - * Sets the username indicating who is the current user of the application. - * @param username the new current username; null to indicate that no one is currently logged into the application. + * Sets the access token indicating who is the current user of the application. + * @param accessToken the new current access token; null to indicate that no one is currently logged into the + * application. */ - void setCurrentUsername(String username); + void setCurrentAccessToken(String accessToken); } diff --git a/src/main/java/use_case/logout/LogoutInputData.java b/src/main/java/use_case/logout/LogoutInputData.java index 56a33b375..eb0851437 100644 --- a/src/main/java/use_case/logout/LogoutInputData.java +++ b/src/main/java/use_case/logout/LogoutInputData.java @@ -4,9 +4,13 @@ * The Input Data for the Logout Use Case. */ public class LogoutInputData { + private final String name; public LogoutInputData(String username) { - // TODO: save the current username in an instance variable and add a getter. + this.name = username; } + public String getName() { + return name; + } } diff --git a/src/main/java/use_case/logout/LogoutInteractor.java b/src/main/java/use_case/logout/LogoutInteractor.java index 1ca93b44e..1c3b5baba 100644 --- a/src/main/java/use_case/logout/LogoutInteractor.java +++ b/src/main/java/use_case/logout/LogoutInteractor.java @@ -9,17 +9,21 @@ public class LogoutInteractor implements LogoutInputBoundary { public LogoutInteractor(LogoutUserDataAccessInterface userDataAccessInterface, LogoutOutputBoundary logoutOutputBoundary) { - // TODO: save the DAO and Presenter in the instance variables. // Which parameter is the DAO and which is the presenter? + this.userDataAccessObject = userDataAccessInterface; + this.logoutPresenter = logoutOutputBoundary; } @Override public void execute(LogoutInputData logoutInputData) { - // TODO: implement the logic of the Logout Use Case (depends on the LogoutInputData.java TODO) // * get the username out of the input data, // * set the username to null in the DAO // * instantiate the `LogoutOutputData`, which needs to contain the username. // * tell the presenter to prepare a success view. + final String username = logoutInputData.getName(); + userDataAccessObject.setCurrentAccessToken(null); + final LogoutOutputData logOut = new LogoutOutputData(username, false); + logoutPresenter.prepareSuccessView(logOut); } } diff --git a/src/main/java/use_case/logout/LogoutOutputData.java b/src/main/java/use_case/logout/LogoutOutputData.java index 974279155..771c4f123 100644 --- a/src/main/java/use_case/logout/LogoutOutputData.java +++ b/src/main/java/use_case/logout/LogoutOutputData.java @@ -9,7 +9,9 @@ public class LogoutOutputData { private boolean useCaseFailed; public LogoutOutputData(String username, boolean useCaseFailed) { - // TODO: save the parameters in the instance variables. + this.username = username; + this.useCaseFailed = useCaseFailed; + } public String getUsername() { diff --git a/src/main/java/use_case/logout/LogoutUserDataAccessInterface.java b/src/main/java/use_case/logout/LogoutUserDataAccessInterface.java index 8263700e2..8ef3c6032 100644 --- a/src/main/java/use_case/logout/LogoutUserDataAccessInterface.java +++ b/src/main/java/use_case/logout/LogoutUserDataAccessInterface.java @@ -9,11 +9,11 @@ public interface LogoutUserDataAccessInterface { * Returns the username of the curren user of the application. * @return the username of the current user */ - String getCurrentUsername(); + String getCurrentAccessToken(); /** * Sets the username indicating who is the current user of the application. - * @param username the new current username + * @param accessToken the new current username */ - void setCurrentUsername(String username); + void setCurrentAccessToken(String accessToken); } diff --git a/src/main/java/use_case/recommend/RecommendInputBoundary.java b/src/main/java/use_case/recommend/RecommendInputBoundary.java new file mode 100644 index 000000000..410aa34b2 --- /dev/null +++ b/src/main/java/use_case/recommend/RecommendInputBoundary.java @@ -0,0 +1,13 @@ +package use_case.recommend; + +/** + * Input Boundary for actions which are related to recommendations. + */ +public interface RecommendInputBoundary { + + /** + * Executes the login use case. + * @param recommendInputData the input data + */ + void execute(RecommendInputData recommendInputData); +} diff --git a/src/main/java/use_case/recommend/RecommendInputData.java b/src/main/java/use_case/recommend/RecommendInputData.java new file mode 100644 index 000000000..a8a22655e --- /dev/null +++ b/src/main/java/use_case/recommend/RecommendInputData.java @@ -0,0 +1,31 @@ +package use_case.recommend; + +import java.util.List; +import java.util.Map; + +/** + * The Input Data for the Recommend Use Case. + */ +public class RecommendInputData { + private final List topTracks; + private final String topArtists; + private String accessToken; + + public RecommendInputData(List topTracks, String topArtists, String accessToken) { + this.topTracks = topTracks; + this.topArtists = topArtists; + this.accessToken = accessToken; + } + + public List getTopTracks() { + return topTracks; + } + + public String getTopArtists() { + return topArtists; + } + + public String getAccessToken() { + return accessToken; + } +} diff --git a/src/main/java/use_case/recommend/RecommendInteractor.java b/src/main/java/use_case/recommend/RecommendInteractor.java new file mode 100644 index 000000000..5194d99f2 --- /dev/null +++ b/src/main/java/use_case/recommend/RecommendInteractor.java @@ -0,0 +1,39 @@ +package use_case.recommend; + +import java.util.List; + +/** + * The Recommendations Interactor. + */ +public class RecommendInteractor implements RecommendInputBoundary { + private final RecommendUserDataAccessInterface userDataAccessObject; + private final RecommendOutputBoundary recommendationOutputBoundary; + + public RecommendInteractor(RecommendUserDataAccessInterface userDataAccessObject, + RecommendOutputBoundary recommendOutputBoundary) { + this.userDataAccessObject = userDataAccessObject; + this.recommendationOutputBoundary = recommendOutputBoundary; + } + + @Override + public void execute(RecommendInputData recommendInputData) { + // Calls Spotify API to get user data + final List topTracks = userDataAccessObject.getTopTracks(); + userDataAccessObject.setTopTracks(topTracks); + final String topArtists = userDataAccessObject.getTopArtists(); + userDataAccessObject.setTopArtists(topArtists); + // Takes user data and asks Azure for recommendations + System.out.printf("Calling spotify api with songs: " + topTracks); + final String songRecommendations = userDataAccessObject.getRecommendations(topTracks, topArtists); + // Gets spotify access token + final String accessToken = recommendInputData.getAccessToken(); + + final RecommendOutputData outputData = new RecommendOutputData(songRecommendations, topArtists, accessToken); + if (songRecommendations.contains("Error")) { + recommendationOutputBoundary.prepareFailView(songRecommendations); + } + else { + recommendationOutputBoundary.prepareSuccessView(outputData); + } + } +} diff --git a/src/main/java/use_case/recommend/RecommendLanguageModelDataAccessInterface.java b/src/main/java/use_case/recommend/RecommendLanguageModelDataAccessInterface.java new file mode 100644 index 000000000..96c3ef205 --- /dev/null +++ b/src/main/java/use_case/recommend/RecommendLanguageModelDataAccessInterface.java @@ -0,0 +1,15 @@ +package use_case.recommend; + +import java.util.List; + +/** +* The recommend DAO for accessing the Azure OpenAI api. + */ +public interface RecommendLanguageModelDataAccessInterface { + /** + * Sends a query to the LLM at the API endpoint. + * @param prompt is a list of strings which the model will respond to + * @return the model's response to that prompt + */ + String getRecommendations(List prompt, String topArtists); +} diff --git a/src/main/java/use_case/recommend/RecommendOutputBoundary.java b/src/main/java/use_case/recommend/RecommendOutputBoundary.java new file mode 100644 index 000000000..a918243e6 --- /dev/null +++ b/src/main/java/use_case/recommend/RecommendOutputBoundary.java @@ -0,0 +1,18 @@ +package use_case.recommend; + +/** + * The output boundary for the Recommend Use Case. + */ +public interface RecommendOutputBoundary { + /** + * Prepares the success view for the Login Use Case. + * @param outputData the output data + */ + void prepareSuccessView(RecommendOutputData outputData); + + /** + * Prepares the failure view for the Login Use Case. + * @param errorMessage the explanation of the failure + */ + void prepareFailView(String errorMessage); +} diff --git a/src/main/java/use_case/recommend/RecommendOutputData.java b/src/main/java/use_case/recommend/RecommendOutputData.java new file mode 100644 index 000000000..1cc81d1b4 --- /dev/null +++ b/src/main/java/use_case/recommend/RecommendOutputData.java @@ -0,0 +1,32 @@ +package use_case.recommend; + +/** + * The output data for the Recommend Use Case. + */ +public class RecommendOutputData { + private final String songRecommendations; + private final String topArtists; + private String accessToken; + + public RecommendOutputData(String songRecommendations, String topArtists, String accessToken) { + this.songRecommendations = songRecommendations; + this.topArtists = topArtists; + this.accessToken = accessToken; + } + + public String getSongRecommendations() { + return songRecommendations; + } + + public String getAccessToken() { + return accessToken; + } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + public String getTopArtists() { + return topArtists; + } +} diff --git a/src/main/java/use_case/recommend/RecommendSpotifyDataAccessInterface.java b/src/main/java/use_case/recommend/RecommendSpotifyDataAccessInterface.java new file mode 100644 index 000000000..73b7275d3 --- /dev/null +++ b/src/main/java/use_case/recommend/RecommendSpotifyDataAccessInterface.java @@ -0,0 +1,20 @@ +package use_case.recommend; + +import java.util.List; + +/** + * The recommend DAO for accessing the Spotify api. + */ +public interface RecommendSpotifyDataAccessInterface { + /** + * Sends a query to the Spotify at the API endpoint. + * @return a map of songs that the user listened to most recently + */ + List getTopTracks(); + + /** + * Sends a query to the Spotify at the API endpoint. + * @return a String of the user's top artists + */ + String getTopArtists(); +} diff --git a/src/main/java/use_case/recommend/RecommendUserDataAccessInterface.java b/src/main/java/use_case/recommend/RecommendUserDataAccessInterface.java new file mode 100644 index 000000000..80f935c50 --- /dev/null +++ b/src/main/java/use_case/recommend/RecommendUserDataAccessInterface.java @@ -0,0 +1,35 @@ +package use_case.recommend; + +import java.util.List; +import java.util.SplittableRandom; + +/** + * DAO for the Recommendations Use Case. + */ +public interface RecommendUserDataAccessInterface { + /** + * Returns the top track list of the current user of the application. + * @return the top track List of the current user; null indicates that there are no track lists. + */ + List getTopTracks(); + + /** + * Sets the top tracks list indicating who is the current user of the application. + * @param tracks the new top track list; null to indicate that there is no top tracks. + */ + void setTopTracks(List tracks); + + /** + * Returns the top artist list of the current user of the application. + * @return the top artist list of the current user; null indicates that there are no artist lists. + */ + String getTopArtists(); + + /** + * Sets the top artist list indicating who is the current user of the application. + * @param artists the new top artist list; null to indicate that there is no top tracks. + */ + void setTopArtists(String artists); + + String getRecommendations(List songs, String topArtists); +} diff --git a/src/main/java/use_case/search/SearchInputBoundary.java b/src/main/java/use_case/search/SearchInputBoundary.java new file mode 100644 index 000000000..e2dad7a59 --- /dev/null +++ b/src/main/java/use_case/search/SearchInputBoundary.java @@ -0,0 +1,16 @@ +package use_case.search; + +/** + * Input Boundary for actions which are going to the search page. + */ +public interface SearchInputBoundary { + /** + * Executes the Search use case. + */ + void execute(String accessToken); + + /** + * Executes the Search use case. + */ + void executeSearch(String accessToken, String searchText); +} diff --git a/src/main/java/use_case/search/SearchInteractor.java b/src/main/java/use_case/search/SearchInteractor.java new file mode 100644 index 000000000..527779f1d --- /dev/null +++ b/src/main/java/use_case/search/SearchInteractor.java @@ -0,0 +1,36 @@ +package use_case.search; + +/** + * The Search Interactor. + */ +public class SearchInteractor implements SearchInputBoundary { + + private final SearchLanguageModelDataAccessInterface modelDataAccess; + private final SearchOutputBoundary searchPresenter; + + /** + * The Search Interactor constructor. + * @param searchPresenter output information + */ + public SearchInteractor(SearchLanguageModelDataAccessInterface modelDataAccess, SearchOutputBoundary searchPresenter) { + this.modelDataAccess = modelDataAccess; + + this.searchPresenter = searchPresenter; + } + + @Override + public void execute(String accessToken) { + final SearchOutputData search = new SearchOutputData(accessToken,false); + searchPresenter.prepareSuccessView(search); + } + + @Override + public void executeSearch(String accessToken, String searchText){ + final SearchOutputData search = new SearchOutputData(accessToken,false); + final String response = modelDataAccess.query(searchText); + search.setDisplayText(response); + searchPresenter.prepareSuccessView(search); + } + + +} diff --git a/src/main/java/use_case/search/SearchLanguageModelDataAccessInterface.java b/src/main/java/use_case/search/SearchLanguageModelDataAccessInterface.java new file mode 100644 index 000000000..87bee79f9 --- /dev/null +++ b/src/main/java/use_case/search/SearchLanguageModelDataAccessInterface.java @@ -0,0 +1,13 @@ +package use_case.search; + +/** + * The search DAO for accessing the Azure OpenAI api. + */ +public interface SearchLanguageModelDataAccessInterface { + /** + * Sends a query to the LLM at the API endpoint. + * @param prompt is a string which the model will respond to + * @return the model's response to that prompt + */ + String query(String prompt); +} diff --git a/src/main/java/use_case/search/SearchOutputBoundary.java b/src/main/java/use_case/search/SearchOutputBoundary.java new file mode 100644 index 000000000..19a042900 --- /dev/null +++ b/src/main/java/use_case/search/SearchOutputBoundary.java @@ -0,0 +1,18 @@ +package use_case.search; + +/** + * The output boundary for the Search Use Case. + */ +public interface SearchOutputBoundary { + /** + * Prepares the success view for the Search Use Case. + * @param outputData the output data + */ + void prepareSuccessView(SearchOutputData outputData); + + /** + * Prepares the failure view for the Search Use Case. + * @param errorMessage the explanation of the failure + */ + void prepareFailView(String errorMessage); +} diff --git a/src/main/java/use_case/search/SearchOutputData.java b/src/main/java/use_case/search/SearchOutputData.java new file mode 100644 index 000000000..437665748 --- /dev/null +++ b/src/main/java/use_case/search/SearchOutputData.java @@ -0,0 +1,37 @@ +package use_case.search; +/** + * Output Data for the Search Use Case. + */ +public class SearchOutputData { + + private String accessToken; + private String displayText; + private boolean useCaseFailed; + + public SearchOutputData(String token, boolean useCaseFailed) { + this.displayText = "This is where the response will appear."; + this.useCaseFailed = useCaseFailed; + this.accessToken = token; + + } + + public boolean isUseCaseFailed() { + return useCaseFailed; + } + + public String getAccessToken() { + return accessToken; + } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + public String getDisplayText() { + return displayText; + } + + public void setDisplayText(String displayText) { + this.displayText = displayText; + } +} diff --git a/src/main/java/use_case/signup/SignupInputBoundary.java b/src/main/java/use_case/signup/SignupInputBoundary.java deleted file mode 100644 index 1cb69e02e..000000000 --- a/src/main/java/use_case/signup/SignupInputBoundary.java +++ /dev/null @@ -1,18 +0,0 @@ -package use_case.signup; - -/** - * Input Boundary for actions which are related to signing up. - */ -public interface SignupInputBoundary { - - /** - * Executes the signup use case. - * @param signupInputData the input data - */ - void execute(SignupInputData signupInputData); - - /** - * Executes the switch to login view use case. - */ - void switchToLoginView(); -} diff --git a/src/main/java/use_case/signup/SignupInputData.java b/src/main/java/use_case/signup/SignupInputData.java deleted file mode 100644 index 86c5e8abc..000000000 --- a/src/main/java/use_case/signup/SignupInputData.java +++ /dev/null @@ -1,29 +0,0 @@ -package use_case.signup; - -/** - * The Input Data for the Signup Use Case. - */ -public class SignupInputData { - - private final String username; - private final String password; - private final String repeatPassword; - - public SignupInputData(String username, String password, String repeatPassword) { - this.username = username; - this.password = password; - this.repeatPassword = repeatPassword; - } - - String getUsername() { - return username; - } - - String getPassword() { - return password; - } - - public String getRepeatPassword() { - return repeatPassword; - } -} diff --git a/src/main/java/use_case/signup/SignupInteractor.java b/src/main/java/use_case/signup/SignupInteractor.java deleted file mode 100644 index 3fd6560c7..000000000 --- a/src/main/java/use_case/signup/SignupInteractor.java +++ /dev/null @@ -1,43 +0,0 @@ -package use_case.signup; - -import entity.User; -import entity.UserFactory; - -/** - * The Signup Interactor. - */ -public class SignupInteractor implements SignupInputBoundary { - private final SignupUserDataAccessInterface userDataAccessObject; - private final SignupOutputBoundary userPresenter; - private final UserFactory userFactory; - - public SignupInteractor(SignupUserDataAccessInterface signupDataAccessInterface, - SignupOutputBoundary signupOutputBoundary, - UserFactory userFactory) { - this.userDataAccessObject = signupDataAccessInterface; - this.userPresenter = signupOutputBoundary; - this.userFactory = userFactory; - } - - @Override - public void execute(SignupInputData signupInputData) { - if (userDataAccessObject.existsByName(signupInputData.getUsername())) { - userPresenter.prepareFailView("User already exists."); - } - else if (!signupInputData.getPassword().equals(signupInputData.getRepeatPassword())) { - userPresenter.prepareFailView("Passwords don't match."); - } - else { - final User user = userFactory.create(signupInputData.getUsername(), signupInputData.getPassword()); - userDataAccessObject.save(user); - - final SignupOutputData signupOutputData = new SignupOutputData(user.getName(), false); - userPresenter.prepareSuccessView(signupOutputData); - } - } - - @Override - public void switchToLoginView() { - userPresenter.switchToLoginView(); - } -} diff --git a/src/main/java/use_case/signup/SignupOutputBoundary.java b/src/main/java/use_case/signup/SignupOutputBoundary.java deleted file mode 100644 index 314376b93..000000000 --- a/src/main/java/use_case/signup/SignupOutputBoundary.java +++ /dev/null @@ -1,24 +0,0 @@ -package use_case.signup; - -/** - * The output boundary for the Signup Use Case. - */ -public interface SignupOutputBoundary { - - /** - * Prepares the success view for the Signup Use Case. - * @param outputData the output data - */ - void prepareSuccessView(SignupOutputData outputData); - - /** - * Prepares the failure view for the Signup Use Case. - * @param errorMessage the explanation of the failure - */ - void prepareFailView(String errorMessage); - - /** - * Switches to the Login View. - */ - void switchToLoginView(); -} diff --git a/src/main/java/use_case/signup/SignupOutputData.java b/src/main/java/use_case/signup/SignupOutputData.java deleted file mode 100644 index 6dc74d2fb..000000000 --- a/src/main/java/use_case/signup/SignupOutputData.java +++ /dev/null @@ -1,24 +0,0 @@ -package use_case.signup; - -/** - * Output Data for the Signup Use Case. - */ -public class SignupOutputData { - - private final String username; - - private final boolean useCaseFailed; - - public SignupOutputData(String username, boolean useCaseFailed) { - this.username = username; - this.useCaseFailed = useCaseFailed; - } - - public String getUsername() { - return username; - } - - public boolean isUseCaseFailed() { - return useCaseFailed; - } -} diff --git a/src/main/java/use_case/signup/SignupUserDataAccessInterface.java b/src/main/java/use_case/signup/SignupUserDataAccessInterface.java deleted file mode 100644 index b9d60f585..000000000 --- a/src/main/java/use_case/signup/SignupUserDataAccessInterface.java +++ /dev/null @@ -1,22 +0,0 @@ -package use_case.signup; - -import entity.User; - -/** - * DAO for the Signup Use Case. - */ -public interface SignupUserDataAccessInterface { - - /** - * Checks if the given username exists. - * @param username the username to look for - * @return true if a user with the given username exists; false otherwise - */ - boolean existsByName(String username); - - /** - * Saves the user. - * @param user the user to save - */ - void save(User user); -} diff --git a/src/main/java/use_case/similar_listeners/SimilarListenersDataAccessInterface.java b/src/main/java/use_case/similar_listeners/SimilarListenersDataAccessInterface.java new file mode 100644 index 000000000..e41bf70d4 --- /dev/null +++ b/src/main/java/use_case/similar_listeners/SimilarListenersDataAccessInterface.java @@ -0,0 +1,22 @@ +package use_case.similar_listeners; + +import java.util.List; + +/** + * DAO for SimilarListeners use case. + */ +public interface SimilarListenersDataAccessInterface { + + /** + * Return the artists that the current user follows. + * @return list of artists that this user follows. Return an empty list if there are no followed artists. + */ + List getFollowedArtists(); + + /** + * Sets the users followed artists. + * @param followedArtists is the list of artists names. + */ + void setCurrentFollowedArtists(List followedArtists); + +} diff --git a/src/main/java/use_case/similar_listeners/SimilarListenersInputBoundary.java b/src/main/java/use_case/similar_listeners/SimilarListenersInputBoundary.java new file mode 100644 index 000000000..c3f848db7 --- /dev/null +++ b/src/main/java/use_case/similar_listeners/SimilarListenersInputBoundary.java @@ -0,0 +1,10 @@ +package use_case.similar_listeners; + +public interface SimilarListenersInputBoundary { + + /** + * Execute the similar listeners use case. + * @param similarListenersInputData the input data. + */ + void execute(SimilarListenersInputData similarListenersInputData); +} diff --git a/src/main/java/use_case/similar_listeners/SimilarListenersInputData.java b/src/main/java/use_case/similar_listeners/SimilarListenersInputData.java new file mode 100644 index 000000000..2168ad21c --- /dev/null +++ b/src/main/java/use_case/similar_listeners/SimilarListenersInputData.java @@ -0,0 +1,23 @@ +package use_case.similar_listeners; + +import java.util.ArrayList; +import java.util.List; + +/** + * The input data for SimilarListeners use case. + */ +public class SimilarListenersInputData { + + private final String accessToken; + + public SimilarListenersInputData(String accessToken) { + + this.accessToken = accessToken; + } + + String getAccessToken() { + return accessToken; + } + + +} diff --git a/src/main/java/use_case/similar_listeners/SimilarListenersInteractor.java b/src/main/java/use_case/similar_listeners/SimilarListenersInteractor.java new file mode 100644 index 000000000..d53c301b8 --- /dev/null +++ b/src/main/java/use_case/similar_listeners/SimilarListenersInteractor.java @@ -0,0 +1,36 @@ +package use_case.similar_listeners; + +import java.util.List; + +/** + * SimilarListeners Interactor. + */ +public class SimilarListenersInteractor implements SimilarListenersInputBoundary { + private final SimilarListenersDataAccessInterface userDataAccessObject; + private final SimilarListenersOutputBoundary similarListenersPresenter; + + public SimilarListenersInteractor(SimilarListenersDataAccessInterface userDataAccessInterface, + SimilarListenersOutputBoundary similarListenersOutputBoundary) { + this.userDataAccessObject = userDataAccessInterface; + this.similarListenersPresenter = similarListenersOutputBoundary; + } + + @Override + public void execute(SimilarListenersInputData similarListenersInputData) { + // final String accessToken = similarListenersInputData.getAccessToken(); + if (userDataAccessObject.getFollowedArtists().isEmpty()) { + similarListenersPresenter.prepareFailView("You do not follow any artists. " + + "Similar Listeners cannot be determined."); + } + else { + final List followedArtists = userDataAccessObject.getFollowedArtists(); + // userDataAccessObject.setCurrentFollowedArtists(followedArtists); + final String accessToken = similarListenersInputData.getAccessToken(); + final SimilarListenersOutputData similarListenersOutputData = new SimilarListenersOutputData( + followedArtists, false, accessToken); + similarListenersPresenter.prepareSuccessView(similarListenersOutputData); + } + } + +} + diff --git a/src/main/java/use_case/similar_listeners/SimilarListenersOutputBoundary.java b/src/main/java/use_case/similar_listeners/SimilarListenersOutputBoundary.java new file mode 100644 index 000000000..73acfc56b --- /dev/null +++ b/src/main/java/use_case/similar_listeners/SimilarListenersOutputBoundary.java @@ -0,0 +1,15 @@ +package use_case.similar_listeners; + +public interface SimilarListenersOutputBoundary { + /** + * Prepares the success view for the Similar Listeners Use Case. + * @param similarListenersOutputData the output data + */ + void prepareSuccessView(SimilarListenersOutputData similarListenersOutputData); + + /** + * Prepares the failure view for the Similar Listeners Use Case. + * @param errorMessage the explanation of the failure + */ + void prepareFailView(String errorMessage); +} diff --git a/src/main/java/use_case/similar_listeners/SimilarListenersOutputData.java b/src/main/java/use_case/similar_listeners/SimilarListenersOutputData.java new file mode 100644 index 000000000..b52ed5f96 --- /dev/null +++ b/src/main/java/use_case/similar_listeners/SimilarListenersOutputData.java @@ -0,0 +1,28 @@ +package use_case.similar_listeners; + +import java.util.List; + +public class SimilarListenersOutputData { + + private final List similarArtists; + private final boolean useCaseFailed; + private String accessToken; + + public SimilarListenersOutputData(List similarArtists, boolean useCaseFailed, String accessToken) { + this.similarArtists = similarArtists; + this.useCaseFailed = useCaseFailed; + this.accessToken = accessToken; + } + + public List getSimilarArtists() { + return similarArtists; + } + + public String getAccessToken() { + return accessToken; + } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } +} diff --git a/src/main/java/use_case/top_items/TopItemsDataAccessInterface.java b/src/main/java/use_case/top_items/TopItemsDataAccessInterface.java new file mode 100644 index 000000000..754929854 --- /dev/null +++ b/src/main/java/use_case/top_items/TopItemsDataAccessInterface.java @@ -0,0 +1,33 @@ +package use_case.top_items; + +import java.util.List; + +/** + * DAO for the Top Tracks Use Case. + */ +public interface TopItemsDataAccessInterface { + + /** + * Returns the top track list of the current user of the application. + * @return the top track List of the current user; null indicates that there are no track lists. + */ + List getCurrentTopTracks(); + + /** + * Sets the top tracks list indicating who is the current user of the application. + * @param tracks the new top track list; null to indicate that there is no top tracks. + */ + void setCurrentTopTracks(List tracks); + + /** + * Returns the top artist list of the current user of the application. + * @return the top artist list of the current user; null indicates that there are no artist lists. + */ + List getCurrentTopArtists(); + + /** + * Sets the top artist list indicating who is the current user of the application. + * @param artists the new top artist list; null to indicate that there is no top tracks. + */ + void setCurrentTopArtists(List artists); +} diff --git a/src/main/java/use_case/top_items/TopItemsInputBoundary.java b/src/main/java/use_case/top_items/TopItemsInputBoundary.java new file mode 100644 index 000000000..ede0422c1 --- /dev/null +++ b/src/main/java/use_case/top_items/TopItemsInputBoundary.java @@ -0,0 +1,13 @@ +package use_case.top_items; + +/** + * Input Boundary for actions which are related to TopItems Charts. + */ +public interface TopItemsInputBoundary { + + /** + * Executes the Logout use case. + * @param topItemsInputData the input data + */ + void execute(TopItemsInputData topItemsInputData); +} diff --git a/src/main/java/use_case/top_items/TopItemsInputData.java b/src/main/java/use_case/top_items/TopItemsInputData.java new file mode 100644 index 000000000..0a082c399 --- /dev/null +++ b/src/main/java/use_case/top_items/TopItemsInputData.java @@ -0,0 +1,12 @@ +package use_case.top_items; + +import java.util.List; + +/** + * The Input Data for the TopItems Use Case. + */ +public class TopItemsInputData { + + public TopItemsInputData() { + } +} diff --git a/src/main/java/use_case/top_items/TopItemsInteractor.java b/src/main/java/use_case/top_items/TopItemsInteractor.java new file mode 100644 index 000000000..7b943c52c --- /dev/null +++ b/src/main/java/use_case/top_items/TopItemsInteractor.java @@ -0,0 +1,41 @@ +package use_case.top_items; + +import java.util.List; + +/** + * The TopItems Interactor. + */ +public class TopItemsInteractor implements TopItemsInputBoundary { + private TopItemsDataAccessInterface userDataAccessObject; + private TopItemsOutputBoundary topItemsOutputBoundary; + + public TopItemsInteractor(TopItemsDataAccessInterface userDataAccessObject, + TopItemsOutputBoundary topItemsOutputBoundary) { + this.userDataAccessObject = userDataAccessObject; + this.topItemsOutputBoundary = topItemsOutputBoundary; + } + + @Override + public void execute(TopItemsInputData topItemsInputData) { + if (userDataAccessObject.getCurrentTopTracks().isEmpty() || userDataAccessObject.getCurrentTopArtists() + .isEmpty()) { + if (userDataAccessObject.getCurrentTopArtists().isEmpty() && userDataAccessObject.getCurrentTopTracks() + .isEmpty()) { + topItemsOutputBoundary.prepareFailView("Top Tracks and Top Artists cannot be determined"); + } + else if (userDataAccessObject.getCurrentTopTracks().isEmpty()) { + topItemsOutputBoundary.prepareFailView("Top Tracks cannot be determined"); + } + else { + topItemsOutputBoundary.prepareFailView("Top Artists cannot be determined"); + } + } + else { + final List topTracks = userDataAccessObject.getCurrentTopTracks(); + final List topArtists = userDataAccessObject.getCurrentTopArtists(); + final TopItemsOutputData outputData = new TopItemsOutputData(topTracks, topArtists); + topItemsOutputBoundary.prepareSuccessView(outputData); + + } + } +} diff --git a/src/main/java/use_case/top_items/TopItemsOutputBoundary.java b/src/main/java/use_case/top_items/TopItemsOutputBoundary.java new file mode 100644 index 000000000..02abb178b --- /dev/null +++ b/src/main/java/use_case/top_items/TopItemsOutputBoundary.java @@ -0,0 +1,19 @@ +package use_case.top_items; + +/** + * The output boundary for the TopItems Use Case. + */ +public interface TopItemsOutputBoundary { + + /** + * Prepares the success view for the TopItems Use Case. + * @param outputData the output data + */ + void prepareSuccessView(TopItemsOutputData outputData); + + /** + * Prepares the failure view for the TopItems Use Case. + * @param errorMessage the explanation of the failure + */ + void prepareFailView(String errorMessage); +} diff --git a/src/main/java/use_case/top_items/TopItemsOutputData.java b/src/main/java/use_case/top_items/TopItemsOutputData.java new file mode 100644 index 000000000..1b1261321 --- /dev/null +++ b/src/main/java/use_case/top_items/TopItemsOutputData.java @@ -0,0 +1,25 @@ +package use_case.top_items; + +import java.util.List; + +/** + * The output data for the TopItems Use Case. + */ +public class TopItemsOutputData { + + private final List tracks; + private final List artists; + + public TopItemsOutputData(List tracks, List artists) { + this.tracks = tracks; + this.artists = artists; + } + + public List getTracks() { + return tracks; + } + + public List getArtists() { + return artists; + } +} diff --git a/src/main/java/view/KeywordView.java b/src/main/java/view/KeywordView.java new file mode 100644 index 000000000..fb35e2d79 --- /dev/null +++ b/src/main/java/view/KeywordView.java @@ -0,0 +1,133 @@ +package view; + +import interface_adapter.keyword.KeywordController; +import interface_adapter.keyword.KeywordState; +import interface_adapter.keyword.KeywordViewModel; +import interface_adapter.login.LoginController; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +public class KeywordView extends JPanel implements PropertyChangeListener { + private final KeywordViewModel keywordViewModel; + private final String viewName = "keyword"; + private KeywordController keywordController; + private LoginController loginController; + + private final JButton homeButton; + private final JScrollPane scrollPane; + private final JTextArea resultsArea; + private final JButton searchButton; + private final JTextField artistField; + private final JTextField keywordField; + private final JLabel keywordLabel; + private final JLabel artistLabel; + private final JPanel panel = new JPanel(); + + public KeywordView(KeywordViewModel keywordViewModel) { + this.keywordViewModel = keywordViewModel; + this.keywordViewModel.addPropertyChangeListener(this); + + setLayout(new BorderLayout(10, 10)); // Add spacing between sections + setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); // Add padding around the panel + + // Input Panel + JPanel inputPanel = new JPanel(); + inputPanel.setLayout(new GridBagLayout()); + GridBagConstraints gbc = new GridBagConstraints(); + gbc.insets = new Insets(5, 5, 5, 5); // Add spacing between components + gbc.fill = GridBagConstraints.HORIZONTAL; + + // Artist Name Label and TextField + artistLabel = new JLabel("Artist Name:"); + gbc.gridx = 0; + gbc.gridy = 0; + inputPanel.add(artistLabel, gbc); + + artistField = new JTextField(); + gbc.gridx = 1; + gbc.gridy = 0; + gbc.weightx = 1.0; + inputPanel.add(artistField, gbc); + + // Keyword Label and TextField + keywordLabel = new JLabel("Keyword:"); + gbc.gridx = 0; + gbc.gridy = 1; + gbc.weightx = 0; + inputPanel.add(keywordLabel, gbc); + + keywordField = new JTextField(); + gbc.gridx = 1; + gbc.gridy = 1; + gbc.weightx = 1.0; + inputPanel.add(keywordField, gbc); + + // Search Button + searchButton = new JButton("Search"); + gbc.gridx = 2; + gbc.gridy = 0; + gbc.gridheight = 2; + gbc.fill = GridBagConstraints.BOTH; + inputPanel.add(searchButton, gbc); + + // Results Panel + resultsArea = new JTextArea(); + resultsArea.setEditable(false); + scrollPane = new JScrollPane(resultsArea); + scrollPane.setBorder(BorderFactory.createTitledBorder("Results")); + + // Button Panel + JPanel buttonPanel = new JPanel(); + buttonPanel.setLayout(new FlowLayout(FlowLayout.RIGHT, 10, 10)); + homeButton = new JButton("Home"); + buttonPanel.add(homeButton); + + // Add Listeners + searchButton.addActionListener(evt -> { + if (evt.getSource().equals(searchButton)) { + + keywordController.executeSearch(artistField.getText(), keywordField.getText()); + } + }); + + homeButton.addActionListener(evt -> { + if (evt.getSource().equals(homeButton)) { + loginController.execute(); + } + }); + + // Add Components to Main Panel + panel.setLayout(new BorderLayout(10, 10)); + panel.add(inputPanel, BorderLayout.NORTH); + panel.add(scrollPane, BorderLayout.CENTER); + panel.add(buttonPanel, BorderLayout.SOUTH); + + this.add(panel); + } + + @Override + public void propertyChange(PropertyChangeEvent evt) { + final KeywordState state = (KeywordState) evt.getNewValue(); + setFields(state); + } + + private void setFields(KeywordState state) { + resultsArea.setText(state.getDisplayText()); + } + + public String getViewName() { + return viewName; + } + + public void setKeywordController(KeywordController keywordController) { + this.keywordController = keywordController; + } + + public void setLoginController(LoginController loginController) { + this.loginController = loginController; + } +} \ No newline at end of file diff --git a/src/main/java/view/LoggedInView.java b/src/main/java/view/LoggedInView.java index 76d40c0f1..22f5f0acf 100644 --- a/src/main/java/view/LoggedInView.java +++ b/src/main/java/view/LoggedInView.java @@ -1,22 +1,24 @@ package view; -import java.awt.Component; +import java.awt.*; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; +import java.util.ArrayList; +import java.util.List; -import javax.swing.BoxLayout; -import javax.swing.JButton; -import javax.swing.JLabel; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JTextField; -import javax.swing.event.DocumentEvent; -import javax.swing.event.DocumentListener; - -import interface_adapter.change_password.ChangePasswordController; -import interface_adapter.change_password.LoggedInState; -import interface_adapter.change_password.LoggedInViewModel; +import javax.swing.*; + +import interface_adapter.keyword.KeywordController; +import interface_adapter.logged_in.LoggedInState; +import interface_adapter.logged_in.LoggedInViewModel; import interface_adapter.logout.LogoutController; +import interface_adapter.recommend.RecommendController; +import interface_adapter.search.SearchController; + +import interface_adapter.similar_listeners.SimilarListenersController; +import interface_adapter.top_items.TopItemsController; +import use_case.keyword.KeywordInteractor; + /** * The View for when the user is logged into the program. @@ -25,106 +27,178 @@ public class LoggedInView extends JPanel implements PropertyChangeListener { private final String viewName = "logged in"; private final LoggedInViewModel loggedInViewModel; - private final JLabel passwordErrorField = new JLabel(); - private ChangePasswordController changePasswordController; private LogoutController logoutController; + private SearchController searchController; + private TopItemsController topItemsController; + private RecommendController recommendController; + private SimilarListenersController similarListenersController; + private KeywordController keywordController; - private final JLabel username; - + private final JLabel accessToken; private final JButton logOut; - - private final JTextField passwordInputField = new JTextField(15); - private final JButton changePassword; + private final JPanel searchButtons = new JPanel(); + private final JPanel appButtons = new JPanel(); public LoggedInView(LoggedInViewModel loggedInViewModel) { this.loggedInViewModel = loggedInViewModel; this.loggedInViewModel.addPropertyChangeListener(this); - final JLabel title = new JLabel("Logged In Screen"); + final JLabel title = new JLabel("Home Screen"); title.setAlignmentX(Component.CENTER_ALIGNMENT); - final LabelTextPanel passwordInfo = new LabelTextPanel( - new JLabel("Password"), passwordInputField); - + final JPanel profile = new JPanel(new FlowLayout(FlowLayout.LEFT)); final JLabel usernameInfo = new JLabel("Currently logged in: "); - username = new JLabel(); - - final JPanel buttons = new JPanel(); + accessToken = new JLabel(); logOut = new JButton("Log Out"); - buttons.add(logOut); + profile.add(usernameInfo); + profile.add(accessToken); + profile.add(logOut); + + final JButton description = new JButton("Search song by description"); + searchButtons.add(description); + final JButton keyword = new JButton("Search song by keyword"); + searchButtons.add(keyword); + // Add ActionListener for the "Search song by keyword" button + /* + keyword.addActionListener(evt -> { + if (evt.getSource().equals(keyword)) { + // Retrieve the current frame + JFrame frame = (JFrame) SwingUtilities.getWindowAncestor(this); + + // Retrieve the access token + String accessToken = "BQB3C0RPRK-jw-xptb5X9nQ_A1yPkwIQBTtdhPbYa5oza3jmru20-sSaMGtPIBT7zcwHblQuYBTnh5Z_pgXC0RGrA5TagXUALHCr8hyXzc3Mxp0cgvI"; // Assuming the token is stored as the username + + // Ensure the token exists + if (accessToken == null || accessToken.isEmpty()) { + JOptionPane.showMessageDialog(null, "Error: Access token is missing. Please log in again."); + return; + } - changePassword = new JButton("Change Password"); - buttons.add(changePassword); + // Initialize components for the Keyword feature + KeywordViewModel viewModel = new KeywordViewModel(); + KeywordPresenter presenter = new KeywordPresenter(viewModel); + SpotifyService spotifyService = new SpotifyService(accessToken); + KeywordInteractor interactor = new KeywordInteractor(spotifyData, presenter); + KeywordController keywordController = new KeywordController(interactor, viewModel); + + // Create the Keyword view and set it as the content pane + KeywordView keywordPage = new KeywordView(keywordController, viewModel); + frame.setContentPane(keywordPage.getPanel(frame)); + frame.revalidate(); // Refresh the frame to display the new content + } + });*/ + final JButton home = new JButton("Home"); + appButtons.add(home); + final JButton recommendations = new JButton("Recommendations"); + appButtons.add(recommendations); + final JButton topItems = new JButton("Top Items"); + appButtons.add(topItems); + final JButton similarListeners = new JButton("Similar listeners"); + appButtons.add(similarListeners); this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); - passwordInputField.getDocument().addDocumentListener(new DocumentListener() { + logOut.addActionListener( + // This creates an anonymous subclass of ActionListener and instantiates it. + evt -> { + if (evt.getSource().equals(logOut)) { + // 1. get the state out of the loggedInViewModel. It contains the username. + final String name = loggedInViewModel.getState().getAccessToken(); + // 2. Execute the logout Controller. + logoutController.execute(name); + } + } + ); - private void documentListenerHelper() { - final LoggedInState currentState = loggedInViewModel.getState(); - currentState.setPassword(passwordInputField.getText()); - loggedInViewModel.setState(currentState); - } + description.addActionListener( + // This creates an anonymous subclass of ActionListener and instantiates it. + evt -> { + if (evt.getSource().equals(description)) { + // 1. get the state out of the loggedInViewModel. It contains the username. + final String accessToken = loggedInViewModel.getState().getAccessToken(); + // 2. Execute the logout Controller. + searchController.execute(accessToken); + } + } + ); - @Override - public void insertUpdate(DocumentEvent e) { - documentListenerHelper(); - } + recommendations.addActionListener( + evt -> { + if (evt.getSource().equals(recommendations)) { + final List topTracks = new ArrayList<>(); + final String topArtists = ""; + final String accessToken = loggedInViewModel.getState().getAccessToken(); + recommendController.execute(topTracks, topArtists, accessToken); + } + } + ); - @Override - public void removeUpdate(DocumentEvent e) { - documentListenerHelper(); - } + topItems.addActionListener( + evt -> { - @Override - public void changedUpdate(DocumentEvent e) { - documentListenerHelper(); + if (evt.getSource().equals(topItems)) { + topItemsController.execute(); + } + } + ); + + // Add an ActionListener to open the Keyword window + /* + keyword.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + // Initialize the required components + KeywordViewModel viewModel = new KeywordViewModel(); // Create a new ViewModel + KeywordPresenter presenter = new KeywordPresenter(viewModel); // Create a presenter + SpotifyService spotifyService = new SpotifyService("ACCESS_TOKEN_HERE"); // Replace with your access token + KeywordInteractor interactor = new KeywordInteractor(spotifyService, presenter); // Initialize interactor + KeywordController controller = new KeywordController(interactor, viewModel); // Initialize controller + + // Create and show the KeywordView with required dependencies + KeywordView keywordWindow = new KeywordView(controller, viewModel); + + keywordWindow.show(); } - }); + });*/ - changePassword.addActionListener( - // This creates an anonymous subclass of ActionListener and instantiates it. + keyword.addActionListener( evt -> { - if (evt.getSource().equals(changePassword)) { - final LoggedInState currentState = loggedInViewModel.getState(); + if (evt.getSource().equals(keyword)) { + final String accessToken = loggedInViewModel.getState().getAccessToken(); + keywordController.execute(); - this.changePasswordController.execute( - currentState.getUsername(), - currentState.getPassword() - ); } } ); - logOut.addActionListener( - // This creates an anonymous subclass of ActionListener and instantiates it. + similarListeners.addActionListener( evt -> { - if (evt.getSource().equals(logOut)) { - // TODO: execute the logout use case through the Controller - // 1. get the state out of the loggedInViewModel. It contains the username. - // 2. Execute the logout Controller. + if (evt.getSource().equals(similarListeners)) { + final String accessToken = loggedInViewModel.getState().getAccessToken(); + similarListenersController.execute(accessToken); + } } ); - + // Add components to the panel this.add(title); this.add(usernameInfo); - this.add(username); - - this.add(passwordInfo); - this.add(passwordErrorField); - this.add(buttons); + this.add(profile); + this.add(searchButtons); + this.add(appButtons); } @Override public void propertyChange(PropertyChangeEvent evt) { if (evt.getPropertyName().equals("state")) { final LoggedInState state = (LoggedInState) evt.getNewValue(); - username.setText(state.getUsername()); + accessToken.setText("This should be displayed"); + String token = state.getAccessToken(); + System.out.println(token); + accessToken.setText("token"); } else if (evt.getPropertyName().equals("password")) { final LoggedInState state = (LoggedInState) evt.getNewValue(); - JOptionPane.showMessageDialog(null, "password updated for " + state.getUsername()); + JOptionPane.showMessageDialog(null, "password updated for " + state.getAccessToken()); } } @@ -133,11 +207,26 @@ public String getViewName() { return viewName; } - public void setChangePasswordController(ChangePasswordController changePasswordController) { - this.changePasswordController = changePasswordController; + public void setKeywordController(KeywordController keywordController) { + this.keywordController = keywordController; } public void setLogoutController(LogoutController logoutController) { - // TODO: save the logout controller in the instance variable. + this.logoutController = logoutController; + } + + public void setSearchController(SearchController searchController) { + this.searchController = searchController; + } + + public void setTopTracksController(TopItemsController topItemsController) { + this.topItemsController = topItemsController; + } + public void setSimilarListenersController(SimilarListenersController similarListenersController) { + this.similarListenersController = similarListenersController; + } + + public void setRecommendController(RecommendController recommendController) { + this.recommendController = recommendController; } } diff --git a/src/main/java/view/LoginView.java b/src/main/java/view/LoginView.java index 96d4f3845..9ea97e602 100644 --- a/src/main/java/view/LoginView.java +++ b/src/main/java/view/LoginView.java @@ -1,17 +1,12 @@ package view; -import java.awt.Component; +import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; -import javax.swing.BoxLayout; -import javax.swing.JButton; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JPasswordField; -import javax.swing.JTextField; +import javax.swing.*; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; @@ -27,14 +22,12 @@ public class LoginView extends JPanel implements ActionListener, PropertyChangeL private final String viewName = "log in"; private final LoginViewModel loginViewModel; - private final JTextField usernameInputField = new JTextField(15); - private final JLabel usernameErrorField = new JLabel(); + private final JTextField loginTokenInputField = new JTextField(15); + private final JLabel accessTokenErrorField = new JLabel(); - private final JPasswordField passwordInputField = new JPasswordField(15); - private final JLabel passwordErrorField = new JLabel(); + private ImageIcon spotifyIcon = new ImageIcon("images/spotify2.png"); private final JButton logIn; - private final JButton cancel; private LoginController loginController; public LoginView(LoginViewModel loginViewModel) { @@ -42,19 +35,18 @@ public LoginView(LoginViewModel loginViewModel) { this.loginViewModel = loginViewModel; this.loginViewModel.addPropertyChangeListener(this); - final JLabel title = new JLabel("Login Screen"); - title.setAlignmentX(Component.CENTER_ALIGNMENT); - - final LabelTextPanel usernameInfo = new LabelTextPanel( - new JLabel("Username"), usernameInputField); - final LabelTextPanel passwordInfo = new LabelTextPanel( - new JLabel("Password"), passwordInputField); + final LabelTextPanel AccessTokenInfo = new LabelTextPanel( + new JLabel("Access Token"), loginTokenInputField); final JPanel buttons = new JPanel(); logIn = new JButton("log in"); buttons.add(logIn); - cancel = new JButton("cancel"); - buttons.add(cancel); + + final Image spotifyImage = spotifyIcon.getImage(); + final Image finalImage = spotifyImage.getScaledInstance(100, 80, java.awt.Image.SCALE_SMOOTH); + spotifyIcon = new ImageIcon(finalImage); + final JLabel spotifylabel = new JLabel(spotifyIcon); + spotifylabel.setAlignmentX(Component.CENTER_ALIGNMENT); logIn.addActionListener( new ActionListener() { @@ -63,21 +55,18 @@ public void actionPerformed(ActionEvent evt) { final LoginState currentState = loginViewModel.getState(); loginController.execute( - currentState.getUsername(), - currentState.getPassword() + currentState.getLoginToken() ); } } } ); - cancel.addActionListener(this); - - usernameInputField.getDocument().addDocumentListener(new DocumentListener() { + loginTokenInputField.getDocument().addDocumentListener(new DocumentListener() { private void documentListenerHelper() { final LoginState currentState = loginViewModel.getState(); - currentState.setUsername(usernameInputField.getText()); + currentState.setLoginToken(loginTokenInputField.getText()); loginViewModel.setState(currentState); } @@ -99,34 +88,9 @@ public void changedUpdate(DocumentEvent e) { this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); - passwordInputField.getDocument().addDocumentListener(new DocumentListener() { - - private void documentListenerHelper() { - final LoginState currentState = loginViewModel.getState(); - currentState.setPassword(new String(passwordInputField.getPassword())); - loginViewModel.setState(currentState); - } - - @Override - public void insertUpdate(DocumentEvent e) { - documentListenerHelper(); - } - - @Override - public void removeUpdate(DocumentEvent e) { - documentListenerHelper(); - } - - @Override - public void changedUpdate(DocumentEvent e) { - documentListenerHelper(); - } - }); - - this.add(title); - this.add(usernameInfo); - this.add(usernameErrorField); - this.add(passwordInfo); + this.add(spotifylabel); + this.add(AccessTokenInfo); + this.add(accessTokenErrorField); this.add(buttons); } @@ -142,12 +106,12 @@ public void actionPerformed(ActionEvent evt) { public void propertyChange(PropertyChangeEvent evt) { final LoginState state = (LoginState) evt.getNewValue(); setFields(state); - usernameErrorField.setText(state.getLoginError()); + accessTokenErrorField.setText(state.getLoginError()); } private void setFields(LoginState state) { - usernameInputField.setText(state.getUsername()); - passwordInputField.setText(state.getPassword()); + loginTokenInputField.setText(state.getLoginToken()); + } public String getViewName() { diff --git a/src/main/java/view/RecommendationsView.java b/src/main/java/view/RecommendationsView.java new file mode 100644 index 000000000..834cc931d --- /dev/null +++ b/src/main/java/view/RecommendationsView.java @@ -0,0 +1,94 @@ +package view; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; + +import interface_adapter.login.LoginController; +import interface_adapter.recommend.RecommendState; +import interface_adapter.recommend.RecommendViewModel; + +/** + * The View for when the user is logged into the program. + */ +public class RecommendationsView extends JPanel implements PropertyChangeListener { + + private final String viewName = "recommendations"; + private final RecommendViewModel recommendViewModel; + private LoginController loginController; + + private JLabel topArtists; + private final JTextArea songsTextArea; + private final JButton homeButton; + + public RecommendationsView(RecommendViewModel recommendViewModel) { + this.recommendViewModel = recommendViewModel; + this.recommendViewModel.addPropertyChangeListener(this); + + final JLabel title = new JLabel("Recommendations"); + final JPanel titlePanel = new JPanel(); + titlePanel.add(title); + + final JLabel description = new JLabel("Because you've listened to " + topArtists + + "here are some songs you might like:"); + final JPanel descriptionPanel = new JPanel(); + descriptionPanel.add(description); + + homeButton = new JButton("go back"); + final JPanel homeButtonPanel = new JPanel(); + homeButtonPanel.add(homeButton); + + final JPanel songsPanel = new JPanel(); + songsTextArea = new JTextArea(); + songsTextArea.setText("Error: songs not displaying"); + final JScrollPane songsScrollPane = new JScrollPane(songsTextArea); + songsPanel.add(songsScrollPane); + + homeButton.addActionListener( + // This creates an anonymous subclass of ActionListener and instantiates it. + evt -> { + if (evt.getSource().equals(homeButton)) { + // 1. Execute the search Controller. + loginController.execute(); + + } + } + ); + + final JPanel panel = new JPanel(); + panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); + panel.add(titlePanel); + panel.add(descriptionPanel); + panel.add(songsPanel); + panel.add(homeButtonPanel); + + this.add(panel); + } + + @Override + public void propertyChange(PropertyChangeEvent evt) { + final RecommendState state = (RecommendState) evt.getNewValue(); + setFields(state); + } + + private void setFields(RecommendState state) { + topArtists = new JLabel(); + String topArtistsText = state.getTopArtists() != null ? state.getTopArtists() : "No top artists available"; + topArtists.setText(topArtistsText); + songsTextArea.setText(state.getSongRecommendations()); + } + + public String getViewName() { + return viewName; + } + + public void setLoginController(LoginController loginController) { + this.loginController = loginController; + } +} diff --git a/src/main/java/view/SearchView.java b/src/main/java/view/SearchView.java new file mode 100644 index 000000000..c436d4ad7 --- /dev/null +++ b/src/main/java/view/SearchView.java @@ -0,0 +1,119 @@ +package view; + +import interface_adapter.logged_in.LoggedInState; +import interface_adapter.login.LoginController; +import interface_adapter.login.LoginState; +import interface_adapter.search.SearchController; +import interface_adapter.search.SearchState; +import interface_adapter.search.SearchViewModel; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +/** + * The View for when the user is searching via song description. + */ +public class SearchView extends JPanel implements PropertyChangeListener { + + private final String viewName = "search"; + private final SearchViewModel searchViewModel; + private SearchController searchController; + private LoginController loginController; + + private final int searchBoxSize = 50; + + private final JButton returnHome; + private final JButton searchButton; + private final JTextField searchInputField; + private final JLabel searchResult; + private final JLabel title; + private final JPanel searchButtons = new JPanel(); + + public SearchView(SearchViewModel searchViewModel) { + this.searchViewModel = searchViewModel; + this.searchViewModel.addPropertyChangeListener(this); + + title = new JLabel("Song Search By Description"); + + final JPanel homeButton = new JPanel(); + returnHome = new JButton("Go Back"); + homeButton.add(returnHome); + + searchResult = new JLabel(searchViewModel.getState().getDisplayText()); + + final JLabel description = new JLabel("Type In the Song Description:"); + searchButtons.add(description); + searchInputField = new JTextField(searchBoxSize); + searchButton = new JButton("Search"); + searchButtons.add(searchInputField); + searchButtons.add(searchButton); + + searchResult.setAlignmentX(Component.RIGHT_ALIGNMENT); + + this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); + /* this block of code is to be finished after a discussion about a unified return home use case.*/ + returnHome.addActionListener( + // This creates an anonymous subclass of ActionListener and instantiates it. + evt -> { + if (evt.getSource().equals(returnHome)) { + // 1. Execute the home Controller. + loginController.execute(); + + } + } + ); + + searchButton.addActionListener( + // This creates an anonymous subclass of ActionListener and instantiates it. + evt -> { + if (evt.getSource().equals(searchButton)) { + // 1. get the state out of the searchViewModel. It contains the username. + final String accessToken = searchViewModel.getState().getAccessToken(); + // 2. Execute the search Controller. + searchController.executeSearch(accessToken, searchInputField.getText()); + + } + } + ); + + this.add(title); + this.add(searchButtons); + this.add(searchResult); + this.add(homeButton); + + } + + /** + * React to a button click that results in evt. + * @param evt the ActionEvent to react to + */ + public void actionPerformed(ActionEvent evt) { + System.out.println("Click " + evt.getActionCommand()); + } + + @Override + public void propertyChange(PropertyChangeEvent evt) { + final SearchState state = (SearchState) evt.getNewValue(); + setFields(state); + } + + private void setFields(SearchState state) { + searchResult.setText(state.getDisplayText()); + + } + + public String getViewName() { + return viewName; + } + + public void setSearchController(SearchController searchController) { + this.searchController = searchController; + } + + public void setLoginController(LoginController loginController) { + this.loginController = loginController; + } +} diff --git a/src/main/java/view/SignupView.java b/src/main/java/view/SignupView.java deleted file mode 100644 index f98570d62..000000000 --- a/src/main/java/view/SignupView.java +++ /dev/null @@ -1,199 +0,0 @@ -package view; - -import java.awt.Component; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; - -import javax.swing.BoxLayout; -import javax.swing.JButton; -import javax.swing.JLabel; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JPasswordField; -import javax.swing.JTextField; -import javax.swing.event.DocumentEvent; -import javax.swing.event.DocumentListener; - -import interface_adapter.signup.SignupController; -import interface_adapter.signup.SignupState; -import interface_adapter.signup.SignupViewModel; - -/** - * The View for the Signup Use Case. - */ -public class SignupView extends JPanel implements ActionListener, PropertyChangeListener { - private final String viewName = "sign up"; - - private final SignupViewModel signupViewModel; - private final JTextField usernameInputField = new JTextField(15); - private final JPasswordField passwordInputField = new JPasswordField(15); - private final JPasswordField repeatPasswordInputField = new JPasswordField(15); - private SignupController signupController; - - private final JButton signUp; - private final JButton cancel; - private final JButton toLogin; - - public SignupView(SignupViewModel signupViewModel) { - this.signupViewModel = signupViewModel; - signupViewModel.addPropertyChangeListener(this); - - final JLabel title = new JLabel(SignupViewModel.TITLE_LABEL); - title.setAlignmentX(Component.CENTER_ALIGNMENT); - - final LabelTextPanel usernameInfo = new LabelTextPanel( - new JLabel(SignupViewModel.USERNAME_LABEL), usernameInputField); - final LabelTextPanel passwordInfo = new LabelTextPanel( - new JLabel(SignupViewModel.PASSWORD_LABEL), passwordInputField); - final LabelTextPanel repeatPasswordInfo = new LabelTextPanel( - new JLabel(SignupViewModel.REPEAT_PASSWORD_LABEL), repeatPasswordInputField); - - final JPanel buttons = new JPanel(); - toLogin = new JButton(SignupViewModel.TO_LOGIN_BUTTON_LABEL); - buttons.add(toLogin); - signUp = new JButton(SignupViewModel.SIGNUP_BUTTON_LABEL); - buttons.add(signUp); - cancel = new JButton(SignupViewModel.CANCEL_BUTTON_LABEL); - buttons.add(cancel); - - signUp.addActionListener( - // This creates an anonymous subclass of ActionListener and instantiates it. - new ActionListener() { - public void actionPerformed(ActionEvent evt) { - if (evt.getSource().equals(signUp)) { - final SignupState currentState = signupViewModel.getState(); - - signupController.execute( - currentState.getUsername(), - currentState.getPassword(), - currentState.getRepeatPassword() - ); - } - } - } - ); - - toLogin.addActionListener( - new ActionListener() { - public void actionPerformed(ActionEvent evt) { - signupController.switchToLoginView(); - } - } - ); - - cancel.addActionListener(this); - - addUsernameListener(); - addPasswordListener(); - addRepeatPasswordListener(); - - this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); - - this.add(title); - this.add(usernameInfo); - this.add(passwordInfo); - this.add(repeatPasswordInfo); - this.add(buttons); - } - - private void addUsernameListener() { - usernameInputField.getDocument().addDocumentListener(new DocumentListener() { - - private void documentListenerHelper() { - final SignupState currentState = signupViewModel.getState(); - currentState.setUsername(usernameInputField.getText()); - signupViewModel.setState(currentState); - } - - @Override - public void insertUpdate(DocumentEvent e) { - documentListenerHelper(); - } - - @Override - public void removeUpdate(DocumentEvent e) { - documentListenerHelper(); - } - - @Override - public void changedUpdate(DocumentEvent e) { - documentListenerHelper(); - } - }); - } - - private void addPasswordListener() { - passwordInputField.getDocument().addDocumentListener(new DocumentListener() { - - private void documentListenerHelper() { - final SignupState currentState = signupViewModel.getState(); - currentState.setPassword(new String(passwordInputField.getPassword())); - signupViewModel.setState(currentState); - } - - @Override - public void insertUpdate(DocumentEvent e) { - documentListenerHelper(); - } - - @Override - public void removeUpdate(DocumentEvent e) { - documentListenerHelper(); - } - - @Override - public void changedUpdate(DocumentEvent e) { - documentListenerHelper(); - } - }); - } - - private void addRepeatPasswordListener() { - repeatPasswordInputField.getDocument().addDocumentListener(new DocumentListener() { - - private void documentListenerHelper() { - final SignupState currentState = signupViewModel.getState(); - currentState.setRepeatPassword(new String(repeatPasswordInputField.getPassword())); - signupViewModel.setState(currentState); - } - - @Override - public void insertUpdate(DocumentEvent e) { - documentListenerHelper(); - } - - @Override - public void removeUpdate(DocumentEvent e) { - documentListenerHelper(); - } - - @Override - public void changedUpdate(DocumentEvent e) { - documentListenerHelper(); - } - }); - } - - @Override - public void actionPerformed(ActionEvent evt) { - JOptionPane.showMessageDialog(this, "Cancel not implemented yet."); - } - - @Override - public void propertyChange(PropertyChangeEvent evt) { - final SignupState state = (SignupState) evt.getNewValue(); - if (state.getUsernameError() != null) { - JOptionPane.showMessageDialog(this, state.getUsernameError()); - } - } - - public String getViewName() { - return viewName; - } - - public void setSignupController(SignupController controller) { - this.signupController = controller; - } -} diff --git a/src/main/java/view/SimilarListenersView.java b/src/main/java/view/SimilarListenersView.java new file mode 100644 index 000000000..3d1bad58a --- /dev/null +++ b/src/main/java/view/SimilarListenersView.java @@ -0,0 +1,87 @@ +package view; + +import java.util.List; + +import interface_adapter.ViewManagerModel; +import interface_adapter.login.LoginController; +import interface_adapter.login.LoginViewModel; +import interface_adapter.similar_listeners.SimilarListenersState; +import interface_adapter.similar_listeners.SimilarListenersViewModel; + +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +import javax.swing.*; + +public class SimilarListenersView extends JPanel implements PropertyChangeListener { + private final String viewName = "similar listeners"; + private final SimilarListenersViewModel similarListenersViewModel; + private LoginController loginController; + private final JButton back; + private final JTextArea listOfArtists; + + public SimilarListenersView(SimilarListenersViewModel similarListenersViewModel) { + this.similarListenersViewModel = similarListenersViewModel; + this.similarListenersViewModel.addPropertyChangeListener(this); + + // building the interface + final JLabel title = new JLabel("Your Similar Listeners: "); + title.setAlignmentX(Component.CENTER_ALIGNMENT); + listOfArtists = new JTextArea("list of names appear here"); + listOfArtists.setEditable(true); + final JPanel similarListenersInfo = new JPanel(); + similarListenersInfo.add(title); + similarListenersInfo.add(listOfArtists); + similarListenersInfo.setLayout(new BoxLayout(similarListenersInfo, BoxLayout.Y_AXIS)); + + back = new JButton("Go Back"); + back.setAlignmentX(Component.CENTER_ALIGNMENT); + back.addActionListener( + evt -> { + if (evt.getSource().equals(back)) { + loginController.execute(); + } + } + ); + this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); + //this.add(title); + this.add(similarListenersInfo); + this.add(back); + + } + + public String getViewName() { + return viewName; + } + + public void setLoginController(LoginController loginController) { + this.loginController = loginController; + } + + @Override + public void propertyChange(PropertyChangeEvent evt) { + final SimilarListenersState similarListenersState = (SimilarListenersState) evt.getNewValue(); + setListOfArtists(similarListenersState); + + } + + private void setListOfArtists(SimilarListenersState similarListenersState) { + if (similarListenersState.getSimilarArtists().isEmpty()) { + listOfArtists.setText(similarListenersState.getSimilarArtistsError()); + } + else { + String artistsNames = ""; + List followedArtists = similarListenersState.getSimilarArtists(); + for (String followedArtist : followedArtists) { + artistsNames += followedArtist + "\n"; + } + listOfArtists.setText(artistsNames); + } + + + } + +} diff --git a/src/main/java/view/TopItemsView.java b/src/main/java/view/TopItemsView.java new file mode 100644 index 000000000..d74c53075 --- /dev/null +++ b/src/main/java/view/TopItemsView.java @@ -0,0 +1,124 @@ +package view; + +import javax.swing.*; + +import interface_adapter.login.LoginController; +import interface_adapter.top_items.TopItemsState; +import interface_adapter.top_items.TopItemsViewModel; +import org.jfree.chart.ChartFactory; +import org.jfree.chart.ChartPanel; +import org.jfree.chart.JFreeChart; +import org.jfree.data.category.DefaultCategoryDataset; +import org.jfree.data.general.DefaultPieDataset; + +import java.awt.*; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +/** + * The View for when the user is opens Top Items Menu. + */ +public class TopItemsView extends JPanel implements PropertyChangeListener { + private final String viewName = "Top Items"; + private final TopItemsViewModel topItemsViewModel; + private LoginController loginController; + + private final JLabel welcomeLabel; + private final JButton homeButton; + + private final DefaultPieDataset dataset1 = new DefaultPieDataset<>(); + private final DefaultCategoryDataset dataset2 = new DefaultCategoryDataset(); + + public TopItemsView(TopItemsViewModel topItemsViewModel) { + this.topItemsViewModel = topItemsViewModel; + this.topItemsViewModel.addPropertyChangeListener(this); + + welcomeLabel = new JLabel("Welcome"); + welcomeLabel.setAlignmentX(Component.CENTER_ALIGNMENT); + + homeButton = new JButton("go back"); + homeButton.setAlignmentX(Component.CENTER_ALIGNMENT); + + /* dataset1.setValue("Artist 1", 200); + dataset1.setValue("Artist 2", 150); + dataset1.setValue("Artist 3", 180); + + dataset2.addValue(300, "Track 1", "1'st"); + dataset2.addValue(250, "Track 2", "2'nd"); + dataset2.addValue(200, "Track 3", "3'rd"); + dataset2.addValue(150, "Track 4", "4'th"); + dataset2.addValue(100, "Track 5", "5'th"); */ + + final JFreeChart chart = ChartFactory.createPieChart( + "Top Artists", dataset1, true, true, false); + + final JFreeChart chart2 = ChartFactory.createBarChart( + "Top Tracks", "Month", "Track Ranks", dataset2); + + final ChartPanel chartPanel = new ChartPanel(chart); + chartPanel.setPreferredSize(new Dimension(300,300)); + + final ChartPanel chartPanel1 = new ChartPanel(chart2); + chartPanel1.setPreferredSize(new Dimension(300, 300)); + + final JPanel chartLayoutPanel = new JPanel(new FlowLayout()); + chartLayoutPanel.add(chartPanel); + chartLayoutPanel.add(chartPanel1); + + this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); + + homeButton.addActionListener( + // This creates an anonymous subclass of ActionListener and instantiates it. + evt -> { + if (evt.getSource().equals(homeButton)) { + final String accessToken = topItemsViewModel.getState().getAccessToken(); + loginController.execute(accessToken); + } + } + ); + + this.add(welcomeLabel); + this.add(chartLayoutPanel); + this.add(homeButton); + } + + @Override + public void propertyChange(PropertyChangeEvent evt) { + final TopItemsState state = (TopItemsState) evt.getNewValue(); + setTrackGraph(state); + setArtistGraph(state); + } + + public String getViewName() { + return viewName; + } + + public void setLoginController(LoginController loginController) { + this.loginController = loginController; + } + /** + * The method updates the dataset for the Top Tracks Graph. + * @param state stands for the TopItemsState that is being used. + */ + public void setTrackGraph(TopItemsState state) { + int decrease = 300; + + for (int i = 0; i < 4; i++) { + dataset2.addValue(decrease, state.getTracks().get(i), String.valueOf(i + 1)); + decrease -= 50; + } + } + + /** + * The method updates the dataset for the Top Tracks Graph. + * @param state stands for the TopItemsState that is being used. + */ + public void setArtistGraph(TopItemsState state) { + int decrease = 200; + + for (int i = 0; i < 3; i++) { + dataset1.setValue(state.getArtists().get(i), decrease); + decrease -= 50; + } + } +} diff --git a/src/test/java/data_access/LanguageModelDataAccessObjectTest.java b/src/test/java/data_access/LanguageModelDataAccessObjectTest.java new file mode 100644 index 000000000..3516f1ea0 --- /dev/null +++ b/src/test/java/data_access/LanguageModelDataAccessObjectTest.java @@ -0,0 +1,18 @@ +package data_access; + +import org.junit.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class LanguageModelDataAccessObjectTest { + + @Test + public void queryTest(){ + // checks that the key is in the right place and is functional + LanguageModelDataAccessObject languageModelDataAccessObject = new LanguageModelDataAccessObject(); + final String response = languageModelDataAccessObject.query("A song where a man steals a car piece by piece."); + + assertNotNull(response); + + } +} diff --git a/src/test/java/data_access/SpotifyDataAccessObjectTest.java b/src/test/java/data_access/SpotifyDataAccessObjectTest.java new file mode 100644 index 000000000..29f960339 --- /dev/null +++ b/src/test/java/data_access/SpotifyDataAccessObjectTest.java @@ -0,0 +1,32 @@ +package data_access; + +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class SpotifyDataAccessObjectTest { + + @Test + void getCurrentTopTracksTest() { + List expected = new ArrayList(); + expected.add("Lovefool"); + expected.add("Надія є"); + SpotifyDataAccessObject dataAccessObject= new SpotifyDataAccessObject("BQDQP8XOnHL5-1B_9f7dvNJ1eN2Nj1rH6d4SZAjxfkZVZDHS-4R447KFvYGqFY4hhu2WgCscHg_egtYI-069ekDuy_MjFiIx9erWLJRYfg_mBaBwiN_DNfu9N9lUwXbz8OQ_WMnGtD5_GuCwC-b4CodFYQO3fGp1UKkkQbdyM_9qUStbGCnnxhr3IL_pDLJ8ny12eN9Pw5k1e1FjyZpLjjq7-xJV6TU"); + assertEquals(expected.get(0), dataAccessObject.getCurrentTopTracks().get(0)); + assertEquals(expected.get(1), dataAccessObject.getCurrentTopTracks().get(1)); + + } + + @Test + void getCurrentTopArtistsTest() { + List expected = new ArrayList(); + expected.add("Mad Heads"); + expected.add("Haydamaky"); + expected.add("Kozak System"); + SpotifyDataAccessObject dataAccessObject= new SpotifyDataAccessObject("BQDQP8XOnHL5-1B_9f7dvNJ1eN2Nj1rH6d4SZAjxfkZVZDHS-4R447KFvYGqFY4hhu2WgCscHg_egtYI-069ekDuy_MjFiIx9erWLJRYfg_mBaBwiN_DNfu9N9lUwXbz8OQ_WMnGtD5_GuCwC-b4CodFYQO3fGp1UKkkQbdyM_9qUStbGCnnxhr3IL_pDLJ8ny12eN9Pw5k1e1FjyZpLjjq7-xJV6TU"); + assertEquals(expected, dataAccessObject.getCurrentTopArtists()); + } +} \ No newline at end of file diff --git a/src/test/java/use_case/keyword/KeywordInteractorTest.java b/src/test/java/use_case/keyword/KeywordInteractorTest.java new file mode 100644 index 000000000..38b565066 --- /dev/null +++ b/src/test/java/use_case/keyword/KeywordInteractorTest.java @@ -0,0 +1,65 @@ + +package use_case.keyword; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import data_access.KeywordTestDataAccessObject; + + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Test class for the Keyword Search Use Case. + */ +public class KeywordInteractorTest { + + private KeywordInteractor keywordInteractor; + private KeywordTestDataAccessObject testDataAccess; + private MockKeywordPresenter mockPresenter; + + @BeforeEach + public void setUp() { + testDataAccess = new KeywordTestDataAccessObject(); + mockPresenter = new MockKeywordPresenter(); + keywordInteractor = new KeywordInteractor(testDataAccess, mockPresenter); + } + + @Test + public void testExecuteSearch_Success() { + keywordInteractor.executeSearch("Taylor Swift", "Love"); + assertEquals(1, mockPresenter.successCount); + assertEquals(0, mockPresenter.failCount); + assertFalse(mockPresenter.latestOutputData.hasError()); + assertEquals("[Love Story]", mockPresenter.latestOutputData.getSongs()); + } + + @Test + public void testExecuteSearch_Failure_EmptyInput() { + keywordInteractor.executeSearch("", ""); + assertEquals(0, mockPresenter.successCount); + assertEquals(1, mockPresenter.failCount); + assertEquals("Error: Artist and keyword cannot both be empty.", mockPresenter.latestErrorMessage); + } + + // Mock presenter class for testing + private static class MockKeywordPresenter implements KeywordOutputBoundary { + int successCount = 0; + int failCount = 0; + KeywordOutputData latestOutputData; + String latestErrorMessage; + + @Override + public void prepareSuccessView(KeywordOutputData outputData) { + successCount++; + latestOutputData = outputData; + } + + @Override + public void prepareFailView(String errorMessage) { + failCount++; + latestErrorMessage = errorMessage; + } + } +} + + diff --git a/src/test/java/use_case/login/LoginInteractorTest.java b/src/test/java/use_case/login/LoginInteractorTest.java index 9fc1bfc89..8a6ffdb1d 100644 --- a/src/test/java/use_case/login/LoginInteractorTest.java +++ b/src/test/java/use_case/login/LoginInteractorTest.java @@ -1,32 +1,32 @@ package use_case.login; import data_access.InMemoryUserDataAccessObject; +import data_access.LoginTestDataAccessObject; import entity.CommonUserFactory; import entity.User; import entity.UserFactory; import org.junit.jupiter.api.Test; -import java.time.LocalDateTime; - import static org.junit.jupiter.api.Assertions.*; class LoginInteractorTest { @Test void successTest() { - LoginInputData inputData = new LoginInputData("Paul", "password"); + LoginInputData inputData = new LoginInputData("Generic token"); LoginUserDataAccessInterface userRepository = new InMemoryUserDataAccessObject(); + LoginTestDataAccessObject spotify = new LoginTestDataAccessObject(); // For the success test, we need to add Paul to the data access repository before we log in. UserFactory factory = new CommonUserFactory(); - User user = factory.create("Paul", "password"); + User user = factory.create("Generic token"); userRepository.save(user); // This creates a successPresenter that tests whether the test case is as we expect. LoginOutputBoundary successPresenter = new LoginOutputBoundary() { @Override public void prepareSuccessView(LoginOutputData user) { - assertEquals("Paul", user.getUsername()); + assertEquals("Generic token", user.getAccessToken()); } @Override @@ -35,25 +35,26 @@ public void prepareFailView(String error) { } }; - LoginInputBoundary interactor = new LoginInteractor(userRepository, successPresenter); + LoginInputBoundary interactor = new LoginInteractor(userRepository, spotify, successPresenter); interactor.execute(inputData); } @Test void successUserLoggedInTest() { - LoginInputData inputData = new LoginInputData("Paul", "password"); + LoginTestDataAccessObject spotify = new LoginTestDataAccessObject(); + LoginInputData inputData = new LoginInputData("Generic token"); LoginUserDataAccessInterface userRepository = new InMemoryUserDataAccessObject(); // For the success test, we need to add Paul to the data access repository before we log in. UserFactory factory = new CommonUserFactory(); - User user = factory.create("Paul", "password"); + User user = factory.create("Generic token"); userRepository.save(user); // This creates a successPresenter that tests whether the test case is as we expect. LoginOutputBoundary successPresenter = new LoginOutputBoundary() { @Override public void prepareSuccessView(LoginOutputData user) { - assertEquals("Paul", userRepository.getCurrentUsername()); + assertEquals("Generic token", userRepository.getCurrentAccessToken()); } @Override @@ -62,63 +63,10 @@ public void prepareFailView(String error) { } }; - LoginInputBoundary interactor = new LoginInteractor(userRepository, successPresenter); - assertEquals(null, userRepository.getCurrentUsername()); - - interactor.execute(inputData); - } - - @Test - void failurePasswordMismatchTest() { - LoginInputData inputData = new LoginInputData("Paul", "wrong"); - LoginUserDataAccessInterface userRepository = new InMemoryUserDataAccessObject(); - - // For this failure test, we need to add Paul to the data access repository before we log in, and - // the passwords should not match. - UserFactory factory = new CommonUserFactory(); - User user = factory.create("Paul", "password"); - userRepository.save(user); - - // This creates a presenter that tests whether the test case is as we expect. - LoginOutputBoundary failurePresenter = new LoginOutputBoundary() { - @Override - public void prepareSuccessView(LoginOutputData user) { - // this should never be reached since the test case should fail - fail("Use case success is unexpected."); - } - - @Override - public void prepareFailView(String error) { - assertEquals("Incorrect password for \"Paul\".", error); - } - }; + LoginInputBoundary interactor = new LoginInteractor(userRepository,spotify, successPresenter); + assertEquals(null, userRepository.getCurrentAccessToken()); - LoginInputBoundary interactor = new LoginInteractor(userRepository, failurePresenter); interactor.execute(inputData); } - @Test - void failureUserDoesNotExistTest() { - LoginInputData inputData = new LoginInputData("Paul", "password"); - LoginUserDataAccessInterface userRepository = new InMemoryUserDataAccessObject(); - - // Add Paul to the repo so that when we check later they already exist - - // This creates a presenter that tests whether the test case is as we expect. - LoginOutputBoundary failurePresenter = new LoginOutputBoundary() { - @Override - public void prepareSuccessView(LoginOutputData user) { - // this should never be reached since the test case should fail - fail("Use case success is unexpected."); - } - - @Override - public void prepareFailView(String error) { - assertEquals("Paul: Account does not exist.", error); - } - }; - - LoginInputBoundary interactor = new LoginInteractor(userRepository, failurePresenter); - interactor.execute(inputData); - } } \ No newline at end of file diff --git a/src/test/java/use_case/logout/LogoutInteractorTest.java b/src/test/java/use_case/logout/LogoutInteractorTest.java index b005a402c..6cc242420 100644 --- a/src/test/java/use_case/logout/LogoutInteractorTest.java +++ b/src/test/java/use_case/logout/LogoutInteractorTest.java @@ -12,21 +12,21 @@ class LogoutInteractorTest { @Test void successTest() { - LogoutInputData inputData = new LogoutInputData("Paul"); + LogoutInputData inputData = new LogoutInputData("Generic token"); InMemoryUserDataAccessObject userRepository = new InMemoryUserDataAccessObject(); // For the success test, we need to add Paul to the data access repository before we log in. UserFactory factory = new CommonUserFactory(); - User user = factory.create("Paul", "password"); + User user = factory.create("Generic token"); userRepository.save(user); - userRepository.setCurrentUsername("Paul"); + userRepository.setCurrentAccessToken("Generic token"); // This creates a successPresenter that tests whether the test case is as we expect. LogoutOutputBoundary successPresenter = new LogoutOutputBoundary() { @Override public void prepareSuccessView(LogoutOutputData user) { // check that the output data contains the username of who logged out - assertEquals("Paul", user.getUsername()); + assertEquals("Generic token", user.getUsername()); } @Override @@ -38,7 +38,7 @@ public void prepareFailView(String error) { LogoutInputBoundary interactor = new LogoutInteractor(userRepository, successPresenter); interactor.execute(inputData); // check that the user was logged out - assertNull(userRepository.getCurrentUsername()); + assertNull(userRepository.getCurrentAccessToken()); } } \ No newline at end of file diff --git a/src/test/java/use_case/recommend/RecommendInteractorTest.java b/src/test/java/use_case/recommend/RecommendInteractorTest.java new file mode 100644 index 000000000..52e94ccc2 --- /dev/null +++ b/src/test/java/use_case/recommend/RecommendInteractorTest.java @@ -0,0 +1,70 @@ +package use_case.recommend; + +import data_access.RecommendTestDataAccessObject; +import data_access.RecommendUserDataAccessObject; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +public class RecommendInteractorTest { + private RecommendTestDataAccessObject dummySpotify; + private List topTracks; + private String topArtists; + + @BeforeEach + void setUp() { + dummySpotify = new RecommendTestDataAccessObject(); + topTracks = dummySpotify.getTopTracks(); + topArtists = dummySpotify.getTopArtists(); + } + + @Test + void successTest() { + // This creates a successPresenter that tests whether the test case is as we expect. + RecommendOutputBoundary successPresenter = new RecommendOutputBoundary() { + @Override + public void prepareSuccessView(RecommendOutputData outputData) { + final String topArtists = "Slipknot, Soulfly, Korn, Sepultura, System Of A Down"; + // check that the output data contains the artist names + assertEquals(topArtists, outputData.getTopArtists()); + } + + @Override + public void prepareFailView(String error) { + fail("Use case failure is unexpected."); + } + }; + + RecommendInputBoundary interactor = new RecommendInteractor(dummySpotify, successPresenter); + interactor.execute(new RecommendInputData(topTracks, topArtists, "token")); + } + + @Test + void failTest() { + // Create empty track list and inputData object + List tracks = new ArrayList<>(); + RecommendInputData inputData = new RecommendInputData(tracks, topArtists, "token"); + + RecommendUserDataAccessInterface accessObject = new RecommendUserDataAccessObject(); + accessObject.setTopTracks(tracks); + + RecommendOutputBoundary successPresenter = new RecommendOutputBoundary() { + @Override + public void prepareSuccessView(RecommendOutputData outputData) { + fail("Use case success is unexpected."); + } + + @Override + public void prepareFailView(String error) { + assertEquals("Error getting tracks from Spotify", error); + } + }; + RecommendInputBoundary interactor = new RecommendInteractor(accessObject, successPresenter); + interactor.execute(inputData); + + } +} diff --git a/src/test/java/use_case/search/SearchInteractorTest.java b/src/test/java/use_case/search/SearchInteractorTest.java new file mode 100644 index 000000000..4f3019844 --- /dev/null +++ b/src/test/java/use_case/search/SearchInteractorTest.java @@ -0,0 +1,55 @@ +package use_case.search; +import data_access.SearchTestDataAccessObject; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + + +import static org.junit.jupiter.api.Assertions.*; + +class SearchInteractorTest { + private SearchTestDataAccessObject dummyLanguageModel; + + @BeforeEach + void setUp() { + dummyLanguageModel = new SearchTestDataAccessObject(); + } + + @Test + void querySuccess(){ + // This creates a successPresenter that tests whether the test case is as we expect. + SearchOutputBoundary successPresenter = new SearchOutputBoundary() { + @Override + public void prepareSuccessView(SearchOutputData outputData) { + assertEquals("One Piece at a Time by Johnny Cash", outputData.getDisplayText()); + } + + @Override + public void prepareFailView(String error) { + fail("Use case failure is unexpected."); + } + }; + + SearchInteractor interactor = new SearchInteractor(dummyLanguageModel, successPresenter); + interactor.executeSearch("Generic Token", "A song in which a man steals a car part by part"); + } + + @Test + void goToPageSuccess(){ + // This creates a successPresenter that tests whether the test case is as we expect. + SearchOutputBoundary successPresenter = new SearchOutputBoundary() { + @Override + public void prepareSuccessView(SearchOutputData outputData) { + assertEquals("Generic Token", outputData.getAccessToken()); + } + + @Override + public void prepareFailView(String error) { + fail("Use case failure is unexpected."); + } + }; + + SearchInteractor interactor = new SearchInteractor(dummyLanguageModel, successPresenter); + interactor.execute("Generic Token"); + } + +} diff --git a/src/test/java/use_case/signup/SignupInteractorTest.java b/src/test/java/use_case/signup/SignupInteractorTest.java deleted file mode 100644 index 00f757c1c..000000000 --- a/src/test/java/use_case/signup/SignupInteractorTest.java +++ /dev/null @@ -1,104 +0,0 @@ -package use_case.signup; - -import data_access.InMemoryUserDataAccessObject; -import entity.CommonUserFactory; -import entity.User; -import entity.UserFactory; -import org.junit.jupiter.api.Test; - -import java.time.LocalDateTime; - -import static org.junit.jupiter.api.Assertions.*; - -class SignupInteractorTest { - - @Test - void successTest() { - SignupInputData inputData = new SignupInputData("Paul", "password", "password"); - SignupUserDataAccessInterface userRepository = new InMemoryUserDataAccessObject(); - - // This creates a successPresenter that tests whether the test case is as we expect. - SignupOutputBoundary successPresenter = new SignupOutputBoundary() { - @Override - public void prepareSuccessView(SignupOutputData user) { - // 2 things to check: the output data is correct, and the user has been created in the DAO. - assertEquals("Paul", user.getUsername()); - assertTrue(userRepository.existsByName("Paul")); - } - - @Override - public void prepareFailView(String error) { - fail("Use case failure is unexpected."); - } - - @Override - public void switchToLoginView() { - // This is expected - } - }; - - SignupInputBoundary interactor = new SignupInteractor(userRepository, successPresenter, new CommonUserFactory()); - interactor.execute(inputData); - } - - @Test - void failurePasswordMismatchTest() { - SignupInputData inputData = new SignupInputData("Paul", "password", "wrong"); - SignupUserDataAccessInterface userRepository = new InMemoryUserDataAccessObject(); - - // This creates a presenter that tests whether the test case is as we expect. - SignupOutputBoundary failurePresenter = new SignupOutputBoundary() { - @Override - public void prepareSuccessView(SignupOutputData user) { - // this should never be reached since the test case should fail - fail("Use case success is unexpected."); - } - - @Override - public void prepareFailView(String error) { - assertEquals("Passwords don't match.", error); - } - - @Override - public void switchToLoginView() { - // This is expected - } - }; - - SignupInputBoundary interactor = new SignupInteractor(userRepository, failurePresenter, new CommonUserFactory()); - interactor.execute(inputData); - } - - @Test - void failureUserExistsTest() { - SignupInputData inputData = new SignupInputData("Paul", "password", "wrong"); - SignupUserDataAccessInterface userRepository = new InMemoryUserDataAccessObject(); - - // Add Paul to the repo so that when we check later they already exist - UserFactory factory = new CommonUserFactory(); - User user = factory.create("Paul", "pwd"); - userRepository.save(user); - - // This creates a presenter that tests whether the test case is as we expect. - SignupOutputBoundary failurePresenter = new SignupOutputBoundary() { - @Override - public void prepareSuccessView(SignupOutputData user) { - // this should never be reached since the test case should fail - fail("Use case success is unexpected."); - } - - @Override - public void prepareFailView(String error) { - assertEquals("User already exists.", error); - } - - @Override - public void switchToLoginView() { - // This is expected - } - }; - - SignupInputBoundary interactor = new SignupInteractor(userRepository, failurePresenter, new CommonUserFactory()); - interactor.execute(inputData); - } -} \ No newline at end of file diff --git a/src/test/java/use_case/similar_listeners/SimilarListenersInteractorTest.java b/src/test/java/use_case/similar_listeners/SimilarListenersInteractorTest.java new file mode 100644 index 000000000..222723510 --- /dev/null +++ b/src/test/java/use_case/similar_listeners/SimilarListenersInteractorTest.java @@ -0,0 +1,82 @@ +package use_case.similar_listeners; + +import data_access.SimilarListenersTestDataAccessObject; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +public class SimilarListenersInteractorTest { + + @Test + void successTest() { + SimilarListenersInputData inputData = new SimilarListenersInputData("token"); + SimilarListenersDataAccessInterface accessObject = new SimilarListenersTestDataAccessObject(); + + // For the success test, we need to add Paul to the data access repository before we log in. + List allfollowedArtists = new ArrayList<>(); + allfollowedArtists.add("Michael Jackson"); + allfollowedArtists.add("Drake"); + allfollowedArtists.add("Madonna"); + allfollowedArtists.add("Lady Gaga"); + allfollowedArtists.add("The Police"); + accessObject.setCurrentFollowedArtists(allfollowedArtists); + + // This creates a successPresenter that tests whether the test case is as we expect. + SimilarListenersOutputBoundary successPresenter = new SimilarListenersOutputBoundary() { + @Override + public void prepareSuccessView(SimilarListenersOutputData outputData) { + // check that the output data contains the username of who logged out + assertEquals(allfollowedArtists, outputData.getSimilarArtists()); + assertEquals("Madonna", outputData.getSimilarArtists().get(2)); + assertEquals("token", outputData.getAccessToken()); + } + + @Override + public void prepareFailView(String error) { + fail("Use case failure is unexpected."); + } + }; + + SimilarListenersInputBoundary interactor = new SimilarListenersInteractor(accessObject, successPresenter); + interactor.execute(inputData); + } + + @Test + void failTest() { + SimilarListenersInputData inputData = new SimilarListenersInputData("token"); + SimilarListenersDataAccessInterface accessObject = new SimilarListenersTestDataAccessObject(); + + List allfollowedArtists = new ArrayList<>(); + accessObject.setCurrentFollowedArtists(allfollowedArtists); + + SimilarListenersOutputBoundary successPresenter = new SimilarListenersOutputBoundary() { + @Override + public void prepareSuccessView(SimilarListenersOutputData outputData) { + // check that the output data contains the username of who logged out + fail("Use case success is unexpected."); + } + + @Override + public void prepareFailView(String error) { + assertEquals("You do not follow any artists. " + + "Similar Listeners cannot be determined.", error); + } + }; + SimilarListenersInputBoundary interactor = new SimilarListenersInteractor(accessObject, successPresenter); + interactor.execute(inputData); + + } + @Test + void tokenTest() { + SimilarListenersOutputData outputData = + new SimilarListenersOutputData(new ArrayList<>(), false, "token"); + outputData.setAccessToken("new token"); + assertEquals("new token", outputData.getAccessToken()); + + } + } + + diff --git a/src/test/java/use_case/topitems/TopItemsInteractorTest.java b/src/test/java/use_case/topitems/TopItemsInteractorTest.java new file mode 100644 index 000000000..a4810eff7 --- /dev/null +++ b/src/test/java/use_case/topitems/TopItemsInteractorTest.java @@ -0,0 +1,138 @@ +package use_case.topitems; + +import data_access.TopItemsDataAccessObject; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import use_case.top_items.*; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +public class TopItemsInteractorTest { + private TopItemsDataAccessInterface dummyObject; + private TopItemsInputData dummyInput; + + @BeforeEach + public void setUp() { + dummyObject = new TopItemsDataAccessObject(); + dummyInput = new TopItemsInputData(); + } + + @Test + void successTest() { + List topTracks = new ArrayList<>(); + topTracks.add("Heartbreaker"); + topTracks.add("reincarnated"); + topTracks.add("Kill Bill"); + topTracks.add("Crew Love"); + + List topArtists = new ArrayList<>(); + topArtists.add("The Weeknd"); + topArtists.add("Kanye West"); + topArtists.add("A$AP Rocky"); + dummyObject.setCurrentTopTracks(topTracks); + dummyObject.setCurrentTopArtists(topArtists); + + TopItemsOutputBoundary success = new TopItemsOutputBoundary() { + @Override + public void prepareSuccessView(TopItemsOutputData outputData) { + assertEquals(topTracks, outputData.getTracks()); + assertEquals(topArtists, outputData.getArtists()); + } + + @Override + public void prepareFailView(String errorMessage) { + fail("Use Case failure is not expected"); + } + }; + + TopItemsInputBoundary topItemsInteractor = new TopItemsInteractor(dummyObject, success); + topItemsInteractor.execute(dummyInput); + } + + @Test + void failTestForTopTracks() { + List topTracks = new ArrayList<>(); + + List topArtists = new ArrayList<>(); + topArtists.add("The Weeknd"); + topArtists.add("Kanye West"); + topArtists.add("A$AP Rocky"); + + dummyObject.setCurrentTopTracks(topTracks); + dummyObject.setCurrentTopArtists(topArtists); + + TopItemsOutputBoundary fail = new TopItemsOutputBoundary() { + @Override + public void prepareSuccessView(TopItemsOutputData outputData) { + fail("User case success is not expected"); + } + + @Override + public void prepareFailView(String errorMessage) { + assertEquals("Top Tracks cannot be determined", errorMessage); + } + }; + + TopItemsInputBoundary topItemsInteractor = new TopItemsInteractor(dummyObject, fail); + topItemsInteractor.execute(dummyInput); + + } + + @Test + void failTestForTopArtists() { + List topTracks = new ArrayList<>(); + topTracks.add("Heartbreaker"); + topTracks.add("reincarnated"); + topTracks.add("Kill Bill"); + topTracks.add("Crew Love"); + + List topArtists = new ArrayList<>(); + + dummyObject.setCurrentTopTracks(topTracks); + dummyObject.setCurrentTopArtists(topArtists); + + TopItemsOutputBoundary fail = new TopItemsOutputBoundary() { + @Override + public void prepareSuccessView(TopItemsOutputData outputData) { + fail("User case success is not expected"); + } + + @Override + public void prepareFailView(String errorMessage) { + assertEquals("Top Artists cannot be determined", errorMessage); + } + }; + + TopItemsInputBoundary topItemsInteractor = new TopItemsInteractor(dummyObject, fail); + topItemsInteractor.execute(dummyInput); + } + + @Test + void failBoth() { + List topTracks = new ArrayList<>(); + + List topArtists = new ArrayList<>(); + + dummyObject.setCurrentTopTracks(topTracks); + dummyObject.setCurrentTopArtists(topArtists); + + TopItemsOutputBoundary fail = new TopItemsOutputBoundary() { + @Override + public void prepareSuccessView(TopItemsOutputData outputData) { + fail("Use case success is not expected"); + } + + @Override + public void prepareFailView(String errorMessage) { + assertEquals("Top Tracks and Top Artists cannot be determined", errorMessage); + } + }; + + TopItemsInputBoundary topItemsInteractor = new TopItemsInteractor(dummyObject, fail); + topItemsInteractor.execute(dummyInput); + } + +}