diff --git a/ Csc207 Group project view 2.pdf b/ Csc207 Group project view 2.pdf new file mode 100644 index 000000000..1d81c0630 Binary files /dev/null and b/ Csc207 Group project view 2.pdf differ diff --git a/.gitignore b/.gitignore index 650c91720..f2bd707a3 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,11 @@ out/ .idea/easycode.ignore target/ +token.txt +.idea/compiler.xml +.idea/encodings.xml +.idea/misc.xml +.idea/workspace.xml ### Eclipse ### .apt_generated @@ -29,4 +34,4 @@ bin/ .vscode/ ### Mac OS ### -.DS_Store \ No newline at end of file +.DS_Store diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 94a25f7f4..35eb1ddfb 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/ProjectLog b/ProjectLog new file mode 100644 index 000000000..af7d102c6 --- /dev/null +++ b/ProjectLog @@ -0,0 +1,40 @@ +Jason GenerateDataAccessObjects, sending requests to joke api, google gemni api +All initial draft design +Alex intitial main gui (View, ViewModel) +Cheyl Read.md completed +Cheyl Finalized UI design +Jason User and Joke entity +K1bbu7z User factory, Favrite UI +Jason: generate usecse, explain use case + + +TODO +If time permits: +K1bbu7z : Funnist Use Case, fav use case +Mika: Search Use Case, sign up usecase, log out usecase, user data access object +Alex: Fav_search Use Case, login usecase +Cheyl: add to fav Use Case, Fav joke display pannel +Jason: ViewManagerModel, AppBuilder + +If not 1: +no login, sign up, log out, user data access object, user entity has no username and password. everything gone upon closing. +No funniest? +K1bbu7z : fav use case (including display pannel) +Mika: Search Use Case +Alex: Fav_search Use Case +Cheyl: add to fav Use Case +Jason: ViewManagerModel, AppBuilder + +If not 2 (not well thought out): +No funniest, No fav_search +K1bbu7z : fav use case (just display), AppBuilder +Mika: Search Use Case, user data access object +Alex: login,sign up usecase, +Cheyl: add to fav Use Case, log out usecase +Jason: ViewManagerModel + +notes +Login Signup logout logic (3 use cases)(create user from DB/File Data, write Data from curr User). +DataAccessObject (getting user info) + + diff --git a/README.md b/README.md index 5e1d8b77c..8685db9ae 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,90 @@ -# Note Application +# Joke Machine -This is a minimal example demonstrating usage of the -password-protected user part of the API used in lab 5. +### Project for Group #269 +#### Team Name: Joke Machine +#### Domain: Joke Generation and Explanation -You can find more information about the API endpoints in -[the documentation](https://www.postman.com/cloudy-astronaut-813156/csc207-grade-apis-demo/documentation/fg3zkjm/5-password-protected-user). -If your team is considering an application for which it would be convenient to -store data in something like a database, you may find that the API calls demonstrated -here will be useful in your project, as this will allow you to store -an arbitrary JSON object associated with a username and password. -In this application, a single note has a name (the "username" in terms of the API) and the note -can be read by anyone who knows the name — but only edited by someone who -knows the password for it. +## Table of Contents -You can see the documentation in the various files for more information. +* Software Specification +* Features +* Installation Instructions +* Usage +* License +* Feedback and Contributions -## Testing +## Software Specification +Joke Machine is a joke generation and explanation platform that allows users to generate, search for, save, and understand jokes. Users can generate or search up jokes, ask for explanations, and sort their list of favorite jokes based on rating of the jokes. Joke Machine integrates with external APIs for joke generation and explanation to provide rich and diverse humor content. -The repo also includes an example of a use case interactor test, as well as -an example of an end-to-end test which automates button clicks and inspects -the contents of the actual views. This is something we discussed in the lectures -about testing in CA but had not provided a code example of before. Note, one -could also inspect the contents of the ViewModel objects instead when testing -CA to make a similar test which would be less dependent on the details of the -specific UI implementation. +## Features + Joke Generation: Generate new jokes with a variety of themes and styles. + Joke Explanation: Explain jokes, whether generated by the app or provided by the user. + Favorites Management: Save favorite jokes to revisit later. + Search and Filter: + Search for jokes by title or keywords (e.g., themes like "911"). + Search for jokes within saved list of jokes + Filter jokes by rating to find the "funniest" jokes. -## Project Starter Code +## Setup and Installation +* Before setting up the project, make sure you have the correct software installed。 + 1. Java Development Kit (JDK) version 11 or higher: + 2. https://www.oracle.com/java/technologies/javase-downloads.html + 2. IntelliJ IDEA Community or Ultimate Edition: + 3. https://www.jetbrains.com/idea/download/ + -Your team may choose to use this repo as starter code for your project. You could -also use the lab 5 code — or start from an empty repo if your team prefers. +* Once you have the software setup: + 1. Clone the repository: + 2. "https://github.com/GuoYuHeJason/CSC207GroupProject.git" + + 2. Open the Project in IntelliJ: + 3. Launch IntelliJ IDEA. + 4. Go to file and click New --> Project From Version Control. + 5. Paste the clone and open project. + + 5. Install Dependencies: + 6. Ensure that your project dependencies are configured in a build.gradle or pom.xml file. + 7. If using Maven, IntelliJ will automatically import dependencies defined in pom.xml. + If using Gradle, open the build.gradle file, and IntelliJ will synchronize the dependencies. + + 6. Run the Application: + 6. Locate the Main class (or equivalent entry point) in your project. + 7. Right-click on the file and select Run 'Main'. -If you choose to use one of the repositories we have provided, you can either make -a fork of it or copy the subset of code you want into a completely new repository. + +## Usage +### Generating Jokes +* Select the "Generate" button to create a new joke or search up joke by title or keyword +### Explaining Jokes +* If you don’t understand a joke, click on the "Explain Joke" button. + The system will provide an explanation of the joke's humor or meaning. +### Saving Favorites +* Click "favorite" to add a joke to your favorites list. +* Access your favorites anytime from the "favorited" section. +### Searching and Filtering +* Keyword Search: Enter a keyword (e.g., "911") to find jokes related to that theme. +* Funniest Jokes: Use the "Funniest" filter to sort saved jokes by their humor rating. + +## License +This project is released under the Creative Commons CC0 1.0 Universal (CC0) Public Domain Dedication. See LICENSE for details. + +## Feedback and Contributions +Contributing to the Project: +1. Fork the Repository: + * Click the "Fork" button on the top right of the repository page to create a copy of the project under your GitHub account. +2. Clone the Repository: + * "git clone https://github.com/your-username/joke-machine.git" +3. Create a New Branch: + 4. "git checkout -b feature/new-feature" +4. Make Your Changes: + 5. Add or change features +5. Submit a Pull Request: + 6. Commit and push your changes to your fork and open a pull request in the main repository. + 7. Provide a description of your changes explaining why they are valuable. + +We appreciate you for engaging with our Joke Machine! + + + diff --git a/accessibility-report.md b/accessibility-report.md new file mode 100644 index 000000000..ab6848693 --- /dev/null +++ b/accessibility-report.md @@ -0,0 +1,34 @@ +# Principles of Universal Design: + +### Principle 1: Equitable Use. +The Joke machine is useful and marketable to people with diverse abilities since it does not require much skill beyond basic linguistic abilities. +The program also does not require personal information such that the user’s privacy, safety or security would be infringed upon by using the program. + +### Principle 2: Flexibility in Use. +The design accommodates a wide range of individual preferences due to the diversity in jokes generated. + +### Principle 3: Simple and Intuitive Use. +The program is easy to understand, given the user has basic English comprehension skills and does not require any prior knowledge. +However, the program is not accessible to those who are visually impaired and would need further implementations to overcome this problem. + +## Principle 4: Perceptible Information. +Unfortunately, the program does not communicate necessary information effectively to users if they have impaired visual sensory abilities. Improvements that allow for auditory based inputs and outputs would need to be implemented. + +## Principle 5: Tolerance for Error. +The design of the program minimizes hazards and the adverse consequences of accidental or unintended actions since the scope of the domain is relatively small. Thus, the program is unlikely to cause any risk to the user. + +## Principle 6: Low Physical Effort. +The design can be used efficiently and comfortably and with a minimum of fatigue since the program does not require much input from the user. The user can simply use the program by clicking various buttons such as generate, save and revisit. + +## Principle 7: Size and Space for Approach and Use. +Appropriate size and space is provided for approach, reach, manipulation, and use regardless of the user's body size, posture, or mobility. This is because the program does not require the user to be confined in a certain physical environment. The user can simply access the program on a laptop anywhere. + + +# + +The Joke Machine would be primarily marketed towards individuals who enjoy humor such as comedy enthusiasts, social media users, and casual users who are seeking to learn about some jokes. In particular, the program may be engaging to students or those of a younger age as they may feel more pressure to have a sense of humor amongst their peer group. Furthermore, the explanation feature of the program may be attractive to those you are seeking for guidance in improving their literary skills to enhance their social interactions with their peers. + +Although our program would likely be used for those who are looking for a casual source of entertainment but may be particularly useful for those who lack confidence in their sense of humor and want to seek guidance in interpreting jokes. This may include non-native English speakers who struggle with cultural nuances or linguistic subtleties in humor. Our program also ensures inclusivity to a wide range of users, including those who suffer from physical injuries, such as a broken leg and are unable to walk. + + + diff --git a/src/main/java/app/AppBuilder.java b/src/main/java/app/AppBuilder.java new file mode 100644 index 000000000..43f609813 --- /dev/null +++ b/src/main/java/app/AppBuilder.java @@ -0,0 +1,298 @@ +package app; + +import data_access.FileDataAccessObject; +import data_access.JokeDataAccessObject; +import entity.UserFactory; +import view.ViewManagerModel; +import use_case.login.adapter.LoginController; +import use_case.login.adapter.LoginPresenter; +import view.login.LoginViewModel; +import use_case.fav_search.*; +import use_case.fav_search.adapter.FavSearchController; +import use_case.fav_search.adapter.FavSearchPresenter; +import use_case.favourite.FavouriteInputBoundary; +import use_case.favourite.FavouriteInteractor; +import use_case.favourite.FavouriteOutputBoundary; +import use_case.favourite.adapter.FavouriteController; +import use_case.favourite.adapter.FavouritePresenter; +import use_case.funniest.FunniestInputBoundary; +import use_case.funniest.FunniestInteractor; +import use_case.funniest.FunniestOutputBoundary; +import use_case.generate.GenerateInputBoundary; +import use_case.generate.GenerateInteractor; +import use_case.generate.GenerateOuputBoundary; +import use_case.generate.adapter.GenerateController; +import use_case.generate.adapter.GeneratePresenter; +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.logout.adapter.LogoutController; +import use_case.logout.adapter.LogoutPresenter; +import use_case.search.SearchInputBoundary; +import use_case.search.SearchInteractor; +import use_case.search.SearchOutputBoundary; +import use_case.search.adapter.SearchController; +import use_case.search.adapter.SearchPresenter; +import use_case.search.adapter.SearchViewModel; +import use_case.signup.SignupInputBoundary; +import use_case.signup.SignupInteractor; +import use_case.signup.SignupOutputBoundary; +import use_case.signup.adapter.SignupController; +import use_case.signup.adapter.SignupPresenter; +import view.signup.SignupViewModel; +import view.login.LoginView; +import view.SearchView; +import view.signup.SignupView; +import view.ViewManager; +import view.favourite_view.FavouriteView; +import view.favourite_view.FavouriteViewModel; +import view.joke_view.JokeFrameBuilder; +import view.main.MainView; +import view.main.MainViewModel; + +import javax.swing.*; +import java.awt.*; + +/** + * The AppBuilder class is responsible for putting together the pieces of + * our CA architecture; piece by piece. + *

+ * This is done by adding each View and then adding related Use Cases. + */ +// Checkstyle note: you can ignore the "Class Data Abstraction Coupling" +// and the "Class Fan-Out Complexity" issues for this lab; we encourage +// your team to think about ways to refactor the code to resolve these +// if your team decides to work with this as your starter code +// for your final project this term. +public class AppBuilder { + private final JPanel cardPanel = new JPanel(); + private final CardLayout cardLayout = new CardLayout(); + // thought question: is the hard dependency below a problem? + private final UserFactory userFactory = new UserFactory(); + private final ViewManagerModel viewManagerModel = new ViewManagerModel(); + private final ViewManager viewManager = new ViewManager(cardPanel, cardLayout, viewManagerModel); + + // thought question: is the hard dependency below a problem? + private final FileDataAccessObject userDataAccessObject = new FileDataAccessObject("src/main/resources/Users.json"); + private final JokeDataAccessObject jokeDataAccessObject = new JokeDataAccessObject(); + private final JokeFrameBuilder jokeFrameBuilder = new JokeFrameBuilder(); + + private SignupView signupView; + private SignupViewModel signupViewModel; + private LoginView loginView; + private LoginViewModel loginViewModel; + private MainView mainView; + private MainViewModel mainViewModel; + private SearchView searchView; + private SearchViewModel searchViewModel; + private FavouriteView favouriteView; + private FavouriteViewModel favouriteViewModel; + + public AppBuilder() { + cardPanel.setLayout(cardLayout); + } + + /** + * Adds the Signup View to the application. + * @return this builder + */ + public AppBuilder addSignupView() { + signupViewModel = new SignupViewModel(); + signupView = new SignupView(signupViewModel); + cardPanel.add(signupView, signupView.getViewName()); + return this; + } + + /** + * Adds the Login View to the application. + * @return this builder + */ + public AppBuilder addLoginView() { + loginViewModel = new LoginViewModel(); + loginView = new LoginView(loginViewModel); + cardPanel.add(loginView, loginView.getViewName()); + return this; + } + + /** + * Adds the LoggedIn View to the application. + * @return this builder + */ + public AppBuilder addMainView() { + mainViewModel = new MainViewModel(); + mainView = new MainView(mainViewModel); + cardPanel.add(mainView, mainView.getViewName()); + return this; + } + + /** + * Adds the LoggedIn View to the application. + * @return this builder + */ + public AppBuilder addSearchView() { + searchViewModel = new SearchViewModel(); + searchView = new SearchView(searchViewModel); + cardPanel.add(searchView, searchView.getViewName()); + return this; + } + + public AppBuilder addFavouriteView() { + favouriteViewModel = new FavouriteViewModel(); + favouriteView = new FavouriteView(favouriteViewModel); + cardPanel.add(favouriteView, favouriteView.getViewName()); + return this; + } + + + /** + * Adds the Signup Use Case to the application. + * @return this builder + */ + public AppBuilder addSignupUseCase() { + final SignupOutputBoundary signupOutputBoundary = new SignupPresenter(viewManagerModel, + signupViewModel, loginViewModel, searchViewModel); + final SignupInputBoundary userSignupInteractor = new SignupInteractor( + userDataAccessObject, signupOutputBoundary, userFactory); + + final SignupController controller = new SignupController(userSignupInteractor); + signupView.setSignupController(controller); + return this; + } + + /** + * Adds the Login Use Case to the application. + * @return this builder + */ + public AppBuilder addLoginUseCase() { + // viewModels Injected to Presenter here. + final LoginOutputBoundary loginOutputBoundary = new LoginPresenter(viewManagerModel, + mainViewModel, loginViewModel); + final LoginInputBoundary loginInteractor = new LoginInteractor( + userDataAccessObject, loginOutputBoundary); + + final LoginController loginController = new LoginController(loginInteractor); + loginView.setLoginController(loginController); + return this; + } + + /** + * Adds the Change Password Use Case to the application. + * @return this builder + */ + public AppBuilder addSearchUseCase() { + final SearchOutputBoundary searchOutputBoundary = + new SearchPresenter(userDataAccessObject, jokeFrameBuilder); + + final SearchInputBoundary searchInteractor = + new SearchInteractor(jokeDataAccessObject, searchOutputBoundary); + + final SearchController searchController = + new SearchController(searchInteractor); + mainView.setSearchController(searchController); + searchView.setSearchController(searchController); + return this; + } + + /** + * Adds the Change Password Use Case to the application. + * @return this builder + */ + public AppBuilder addFavouriteUseCase() { + final FavouriteOutputBoundary favouriteOutputBoundary = + new FavouritePresenter(favouriteViewModel); + + final FavouriteInputBoundary favouriteInputBoundary = + new FavouriteInteractor(userDataAccessObject, favouriteOutputBoundary); + + final FavouriteController favouriteController = + new FavouriteController(favouriteInputBoundary); + mainView.setFavouriteController(favouriteController); + return this; + } + + /** + * Adds the Change Password Use Case to the application. + * @return this builder + */ + public AppBuilder addFavSearchUseCase() { + final FavSearchOutputBoundary favSearchOutputBoundary = + new FavSearchPresenter(jokeFrameBuilder); + + final FavSearchInputBoundary favSearchInteractor = + new FavSearchInteractor(favSearchOutputBoundary, userDataAccessObject); + + final FavSearchController favSearchController = + new FavSearchController(favSearchInteractor); + favouriteView.setFavSearchController(favSearchController); + return this; + } + + /** + * Adds the Change Password Use Case to the application. + * @return this builder + */ + public AppBuilder addFunniestUseCase() { + final FunniestOutputBoundary funniestOutputBoundary = + new FunniestPresenter(favouriteViewModel); + + final FunniestInputBoundary funniestInputBoundary = + new FunniestInteractor(funniestOutputBoundary); + + final FunniestController funniestController = + new FunniestController(funniestInputBoundary); + favouriteView.setFunniestController(funniestController); + return this; + } + + /** + * Adds the Change Password Use Case to the application. + * @return this builder + */ + public AppBuilder addGenerateUseCase() { + final GenerateOuputBoundary generateOuputBoundary = + new GeneratePresenter(jokeFrameBuilder); + + final GenerateInputBoundary generateInputBoundary = + new GenerateInteractor(jokeDataAccessObject, generateOuputBoundary); + + final GenerateController generateController = + new GenerateController(generateInputBoundary); + mainView.setGenerateController(generateController); + return this; + } + + /** + * Adds the Logout Use Case to the application. + * @return this builder + */ + public AppBuilder addLogoutUseCase() { + final LogoutOutputBoundary logoutOutputBoundary = new LogoutPresenter(viewManagerModel, + mainViewModel, loginViewModel); + + final LogoutInputBoundary logoutInteractor = + new LogoutInteractor(userDataAccessObject, logoutOutputBoundary); + + final LogoutController logoutController = new LogoutController(logoutInteractor); + mainView.setLogoutController(logoutController); + 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("Joke Machine"); + application.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); + + application.add(cardPanel); + + viewManagerModel.setState(signupView.getViewName()); + viewManagerModel.firePropertyChanged(); + + return application; + } +} diff --git a/src/main/java/app/Main.java b/src/main/java/app/Main.java new file mode 100644 index 000000000..4fbdf659e --- /dev/null +++ b/src/main/java/app/Main.java @@ -0,0 +1,33 @@ +package app; + +import javax.swing.*; + +/** + * The Main class of our application. + */ +public class Main { + /** + * Builds and runs the CA architecture of the application. + * @param args unused arguments + */ + public static void main(String[] args) { + final AppBuilder appBuilder = new AppBuilder(); + final JFrame application = appBuilder + .addLoginView() + .addSignupView() + .addMainView() + .addFavouriteView() + .addSignupUseCase() + .addLoginUseCase() + .addSearchUseCase() + .addFavouriteUseCase() + .addGenerateUseCase() + .addFunniestUseCase() + .addFavSearchUseCase() + .addLogoutUseCase() + .build(); + + application.pack(); + application.setVisible(true); + } +} diff --git a/src/main/java/data_access/ExplanationDataAccessObject.java b/src/main/java/data_access/ExplanationDataAccessObject.java new file mode 100644 index 000000000..4c9e31fbe --- /dev/null +++ b/src/main/java/data_access/ExplanationDataAccessObject.java @@ -0,0 +1,79 @@ +package data_access; + +import okhttp3.*; +import org.json.JSONException; +import org.json.JSONObject; +import use_case.explanation.ExplanationDataAccessInterface; + +import java.io.IOException; + +public class ExplanationDataAccessObject implements ExplanationDataAccessInterface { + + private static final String TOKEN = "token"; + private static final String GOOGLE_API_KEY = System.getenv(TOKEN); + private static final String API_URL = "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=" + GOOGLE_API_KEY; + private static final String APPLICATION_JSON = "application/json"; + + // uses java sdk instead of https +// const genAI = new GoogleGenerativeAI(process.env.API_KEY); +//const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" }); +// +//const prompt = "Write a story about a magic backpack."; +// +//const result = await model.generateContent(prompt); +//console.log(result.response.text()); + + @Override + public String getExplanation(String joke) { + + String prompt = "explain this joke" + joke; + + // Build client + final OkHttpClient client = new OkHttpClient().newBuilder() + .build(); + + // type of request body is json + final MediaType mediaType = MediaType.parse(APPLICATION_JSON); + //make request body + final JSONObject requestBody = new JSONObject() +// .put("generationConfig", new JSONObject().put("maxOutputTokens", 1)) + .put("contents", new JSONObject() + .put("parts", new JSONObject() + .put("text", prompt) + ) + ); + // RequestBody takes in type and content (string) + final RequestBody body = RequestBody.create(mediaType, requestBody.toString()); + + // add arguments to url, or add headers as needed + final Request request = new Request.Builder() + .url(String.format(API_URL)) + .post(body) + .build(); + + // Hint: look at the API documentation to understand what the response looks like. + try { + + // Client creates a Call with Request + // Call returns the Response from execute() + final Response response = client.newCall(request).execute(); + if (response.isSuccessful()) { + final JSONObject responseBody = new JSONObject(response.body().string()); + + JSONObject candidates = responseBody.getJSONArray("candidates").getJSONObject(0); + if (candidates.getString("finishReason").equals("STOP")) { + return candidates + .getJSONObject("content") + .getJSONArray("parts").getJSONObject(0) + .getString("text"); + } else { + return "this joke is too inappropriate for me!"; + } + } else { + throw new RuntimeException(response.message()); + } + } catch (IOException | JSONException event) { + throw new RuntimeException(event); + } + } +} \ No newline at end of file diff --git a/src/main/java/data_access/FavouriteDataAccessObject.java b/src/main/java/data_access/FavouriteDataAccessObject.java new file mode 100644 index 000000000..d9a19da49 --- /dev/null +++ b/src/main/java/data_access/FavouriteDataAccessObject.java @@ -0,0 +1,4 @@ +package data_access; + +public class FavouriteDataAccessObject { +} diff --git a/src/main/java/data_access/FileDataAccessObject.java b/src/main/java/data_access/FileDataAccessObject.java new file mode 100644 index 000000000..0b2e26c38 --- /dev/null +++ b/src/main/java/data_access/FileDataAccessObject.java @@ -0,0 +1,130 @@ +package data_access; + +import java.io.*; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.*; + +import org.json.JSONArray; +import org.json.JSONObject; + +import entity.Joke; +import entity.JokeFactory; +import entity.User; +import entity.UserFactory; +import use_case.add_to_fav.AddToFavDataAccessInterface; +import use_case.logout.LogoutUserDataAccessInterface; +import use_case.signup.SignupUserDataAccessInterface; + +/** + * DAO for user data implemented using a File to persist the data. + */ +public class FileDataAccessObject implements SignupUserDataAccessInterface, + LogoutUserDataAccessInterface, + AddToFavDataAccessInterface { + + private File jsonFile; + private List users = new ArrayList<>(); + private UserFactory userFactory = new UserFactory(); + private JokeFactory jokeFactory = new JokeFactory(); + private String currentUserName; + + /** + * Constructs a Data Access Object populated using data from the specified resources file. + * @param filename the name of the file in resources to load the data from + * @throws RuntimeException if the resource file can't be loaded properly + */ + public FileDataAccessObject(String filename) { + // read the file to get the data to populate things... + try { + + final String jsonString = Files.readString( + Paths.get(getClass().getClassLoader().getResource(filename).toURI())); + + final JSONArray jsonArray = new JSONArray(jsonString); + + // this for loop makes the instance variables what's said in comments + for (int i = 0; i < jsonArray.length(); i++) { + final JSONObject jsonObject = jsonArray.getJSONObject(i); + final String username = jsonObject.getString("username"); + final String password = jsonObject.getString("password"); + final JSONArray favorites = jsonObject.getJSONArray("favorites"); + final List jokeList = new ArrayList<>(); + for (int j = 0; j < favorites.length(); j++) { + final JSONObject jokeObject = favorites.getJSONObject(j); + final String jokeContent = jokeObject.getString("content"); + final String jokeExplanation = jokeObject.getString("explanation"); + final int jokeScore = jokeObject.getInt("score"); + final Joke joke = jokeFactory.create(jokeContent, jokeScore); + joke.setExplanation(jokeExplanation); + jokeList.add(joke); + } + final User user = userFactory.create(username, password, jokeList); + users.add(user); + } + + } + catch (IOException | URISyntaxException ex) { + throw new RuntimeException(ex); + } + } + + private void save() { + final JSONArray jsonArray = new JSONArray(); + + // Convert each user into a JSONObject and add to the JSONArray + for (User user : users) { + final JSONObject jsonObject = new JSONObject(); + jsonObject.put("Username", user.getName()); + jsonObject.put("Password", user.getPassword()); + jsonObject.put("favorites", user.getFavorites()); + jsonArray.put(jsonObject); + } + + // Write the JSON array to the file + try (FileWriter fileWriter = new FileWriter("Users.json")) { + fileWriter.write(jsonArray.toString()); + System.out.println("Users saved successfully to " + "Users.json"); + } + catch (IOException ex) { + System.err.println("Error saving users: " + ex.getMessage()); + } + } + + // TODO change, update user + @Override + public void save(User user) { + if (this.existsByName(user.getName())) { + final User currentUser = this.get(user.getName()); + users.remove(currentUser); + } + users.add(user); + this.save(); + } + + public User get(String username) { + for (int i = 0; i < users.size(); i++) { + final User currentuser = users.get(i); + if (currentuser.getName().equals(username)) { + return currentuser; + } + } + return null; + } + + @Override + public boolean existsByName(String username) { + return this.get(username) != null; + } + + @Override + public String getCurrentUsername() { + return this.currentUserName; + } + + @Override + public void setCurrentUsername(String username) { + this.currentUserName = username; + } +} diff --git a/src/main/java/data_access/JokeDataAccessObject.java b/src/main/java/data_access/JokeDataAccessObject.java new file mode 100644 index 000000000..231474fe9 --- /dev/null +++ b/src/main/java/data_access/JokeDataAccessObject.java @@ -0,0 +1,95 @@ +package data_access; + +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import org.json.JSONException; +import org.json.JSONObject; +import use_case.generate.GenerateDataAccessInterface; +import use_case.search.SearchDataAccessInterface; + +import java.io.IOException; + + +public class JokeDataAccessObject implements GenerateDataAccessInterface, + SearchDataAccessInterface { + + private static final String MESSAGE = "message"; + private static final String API_URL = "https://v2.jokeapi.dev/joke/Any"; + private static final String API_SEARCH_URL = "https://v2.jokeapi.dev/joke/Any?contains="; + + @Override + public String getJokeContent() { + + // Build client + // Build the request to get joke. + // client sends request + final OkHttpClient client = new OkHttpClient().newBuilder() + .build(); + // add arguments to url, or add headers as needed + final Request request = new Request.Builder() + .url(String.format(API_URL)) + .build(); + + // Hint: look at the API documentation to understand what the response looks like. + try { + // Client creates a Call with Request + // Call returns the Response from execute() + final Response response = client.newCall(request).execute(); + + // JSONObject is a dictionary that can be written to a JSON file + // a JSON file has multiple JSONObjects: + // file -> String -> JSONArray -> JSONObject + // String jsonString = Files.readString(Paths.get(getClass().getClassLoader().getResource(filename).toURI())) + // JSONArray jsonArray = new JSONArray(jsonString); + // JSONObject jsonObject = jsonArray.getJSONObject(i); + final JSONObject responseBody = new JSONObject(response.body().string()); + + // Response body (the JSONObject) varies for API. + if (responseBody.getBoolean("error")) { + throw new RuntimeException(responseBody.getString(MESSAGE)); + } else { + switch (responseBody.getString("type")) { + case "single": + return responseBody.getString("joke"); + case "twopart": + return responseBody.getString("setup") + responseBody.getString("delivery"); + default: + return "something funny"; + } + } + } + catch (IOException | JSONException event) { + throw new RuntimeException(event); + } + } + + @Override + public String searchJoke(String keyword) { + final OkHttpClient client = new OkHttpClient().newBuilder() + .build(); + final Request request = new Request.Builder() + .url(String.format(API_SEARCH_URL + keyword)) + .build(); + try { + final Response response = client.newCall(request).execute(); + final JSONObject responseBody = new JSONObject(response.body().string()); + if (responseBody.getBoolean("error")) { + return responseBody.getString(MESSAGE); + } + else { + switch (responseBody.getString("type")) { + case "single": + return responseBody.getString("joke"); + case "twopart": + return responseBody.getString("setup") + responseBody.getString("delivery"); + default: + return "something funny"; + } + } + } + catch (IOException | JSONException event) { + throw new RuntimeException(event); + } + } +} diff --git a/src/main/java/data_access/MockExplanationDataAccessObject.java b/src/main/java/data_access/MockExplanationDataAccessObject.java new file mode 100644 index 000000000..99f8693fd --- /dev/null +++ b/src/main/java/data_access/MockExplanationDataAccessObject.java @@ -0,0 +1,11 @@ +package data_access; + +import use_case.explanation.ExplanationDataAccessInterface; + +public class MockExplanationDataAccessObject implements ExplanationDataAccessInterface { + + @Override + public String getExplanation(String joke) { + return "haha, that's funny"; + } +} diff --git a/src/main/java/entity/Joke.java b/src/main/java/entity/Joke.java new file mode 100644 index 000000000..9fa36adfb --- /dev/null +++ b/src/main/java/entity/Joke.java @@ -0,0 +1,31 @@ +package entity; + +// there should be a joke factory, a user factory +public class Joke { + + private final String content; + private String explanation; + private final int score; + + public Joke(String content, int score) { + this.content = content; + this.explanation = "explanation"; + this.score = score; + } + + public String getContent() { + return content; + } + + public String getExplanation() { + return explanation; + } + + public void setExplanation(String explanation) { + this.explanation = explanation; + } + + public int getScore() { + return score; + } +} diff --git a/src/main/java/entity/JokeFactory.java b/src/main/java/entity/JokeFactory.java new file mode 100644 index 000000000..ae88e7009 --- /dev/null +++ b/src/main/java/entity/JokeFactory.java @@ -0,0 +1,7 @@ +package entity; + +public class JokeFactory { + public Joke create(String content, int score) { + return new Joke(content, score); + } +} diff --git a/src/main/java/entity/User.java b/src/main/java/entity/User.java index e0c57e9a6..e5fb79e60 100644 --- a/src/main/java/entity/User.java +++ b/src/main/java/entity/User.java @@ -1,5 +1,7 @@ package entity; +import java.util.List; + /** * The representation of a password-protected user for our program. */ @@ -7,12 +9,19 @@ public class User { private final String name; private final String password; + private List favorites; public User(String name, String password) { this.name = name; this.password = password; } + public User(String name, String password, List favorites) { + this.name = name; + this.password = password; + this.favorites = favorites; + } + public String getName() { return name; } @@ -21,4 +30,12 @@ public String getPassword() { return password; } + public List getFavorites() { + return favorites; + } + + public void addToFavorites(Joke joke) { + this.favorites.add(joke); + } + } diff --git a/src/main/java/entity/UserFactory.java b/src/main/java/entity/UserFactory.java new file mode 100644 index 000000000..a80963cee --- /dev/null +++ b/src/main/java/entity/UserFactory.java @@ -0,0 +1,25 @@ +package entity; + +import java.util.List; + +/** + * Factory for creating users. + */ +public class UserFactory { + /** + * Creates a new User. + * + * @param name the name of the new user + * @param password the password of the new user + * @param favorites the list of user's favourite joke(s) + * @return the new user + */ + public User create(String name, String password, List favorites) { + return new User(name, password, favorites); + } + + public User create(String name, String password) { + return new User(name, password); + } + +} diff --git a/src/main/java/interface_adapter/ViewManagerModel.java b/src/main/java/interface_adapter/ViewManagerModel.java new file mode 100644 index 000000000..7c66f615c --- /dev/null +++ b/src/main/java/interface_adapter/ViewManagerModel.java @@ -0,0 +1,13 @@ +package interface_adapter; + +/** + * Model for the View Manager. Its state is the name of the View which + * is currently active. An initial state of "" is used. + */ +public class ViewManagerModel extends ViewModel { + + public ViewManagerModel() { + super("view manager"); + this.setState(""); + } +} diff --git a/src/main/java/interface_adapter/ViewModel.java b/src/main/java/interface_adapter/ViewModel.java index 130d797a1..b67420a16 100644 --- a/src/main/java/interface_adapter/ViewModel.java +++ b/src/main/java/interface_adapter/ViewModel.java @@ -45,8 +45,6 @@ public void firePropertyChanged() { * Fires a property changed event for the state of this ViewModel, which * allows the user to specify a different propertyName. This can be useful * when a class is listening for multiple kinds of property changes. - *

For example, the LoggedInView listens for two kinds of property changes; - * it can use the property name to distinguish which property has changed.

* @param propertyName the label for the property that was changed */ public void firePropertyChanged(String propertyName) { diff --git a/src/main/java/interface_adapter/joke/JokeController.java b/src/main/java/interface_adapter/joke/JokeController.java new file mode 100644 index 000000000..70889dc48 --- /dev/null +++ b/src/main/java/interface_adapter/joke/JokeController.java @@ -0,0 +1,27 @@ +package interface_adapter.joke; + +import use_case.joke.JokeInputBoundary; +import use_case.joke.JokeInputData; + +/** + * The controller for the Joke Use Case. + */ +public class JokeController { + + private final JokeInputBoundary jokeUseCaseInteractor; + + public JokeController(JokeInputBoundary jokeUseCaseInteractor) { + this.jokeUseCaseInteractor = jokeUseCaseInteractor; + } + + /** + * Executes the Joke Use Case. + * @param actionType the type of action (e.g., "generate", "search", "favorite") + * @param query the search query for finding a joke, if applicable + */ + public void execute(String actionType, String query) { + JokeInputData jokeInputData = new JokeInputData(actionType, query); + + jokeUseCaseInteractor.execute(jokeInputData); + } +} diff --git a/src/main/java/interface_adapter/joke/JokePresenter.java b/src/main/java/interface_adapter/joke/JokePresenter.java new file mode 100644 index 000000000..91898ffd7 --- /dev/null +++ b/src/main/java/interface_adapter/joke/JokePresenter.java @@ -0,0 +1,40 @@ +package interface_adapter.joke; + +import interface_adapter.ViewManagerModel; +import use_case.joke.JokeOutputBoundary; +import use_case.joke.JokeOutputData; + +/** + * The Presenter for the Joke Use Case. + */ +public class JokePresenter implements JokeOutputBoundary { + + private final JokeViewModel jokeViewModel; + private final ViewManagerModel viewManagerModel; + + public JokePresenter(ViewManagerModel viewManagerModel, JokeViewModel jokeViewModel) { + this.viewManagerModel = viewManagerModel; + this.jokeViewModel = jokeViewModel; + } + + @Override + public void prepareSuccessView(JokeOutputData response) { + // Update the JokeState with the new joke + JokeState jokeState = jokeViewModel.getState(); + jokeState.setJokeText(response.getJokeText()); + jokeState.setFavoriteStatus(response.isFavorite()); + jokeViewModel.setState(jokeState); + jokeViewModel.firePropertyChanged(); + + viewManagerModel.setState(jokeViewModel.getViewName()); + viewManagerModel.firePropertyChanged(); + } + + @Override + public void prepareFailView(String error) { + JokeState jokeState = jokeViewModel.getState(); + jokeState.setErrorMessage(error); + jokeViewModel.setState(jokeState); + jokeViewModel.firePropertyChanged("error"); + } +} diff --git a/src/main/java/interface_adapter/joke/JokeState.java b/src/main/java/interface_adapter/joke/JokeState.java new file mode 100644 index 000000000..97277eb1e --- /dev/null +++ b/src/main/java/interface_adapter/joke/JokeState.java @@ -0,0 +1,25 @@ +package interface_adapter.joke; + +/** + * The state for the Joke View Model. + */ +public class JokeState { + private String jokeText = ""; + private String errorMessage; + + public String getJokeText() { + return jokeText; + } + + public String getErrorMessage() { + return errorMessage; + } + + public void setJokeText(String jokeText) { + this.jokeText = jokeText; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } +} diff --git a/src/main/java/interface_adapter/joke/MainState.java b/src/main/java/interface_adapter/joke/MainState.java new file mode 100644 index 000000000..50ed380f9 --- /dev/null +++ b/src/main/java/interface_adapter/joke/MainState.java @@ -0,0 +1,7 @@ +package interface_adapter.joke; + +/** + * The state for the Joke View Model. + */ +public class MainState { +} diff --git a/src/main/java/interface_adapter/joke/MainViewModel.java b/src/main/java/interface_adapter/joke/MainViewModel.java new file mode 100644 index 000000000..61a93c8bf --- /dev/null +++ b/src/main/java/interface_adapter/joke/MainViewModel.java @@ -0,0 +1,14 @@ +package interface_adapter.joke; + +import interface_adapter.ViewModel; + +/** + * The View Model for the Joke View. + */ +public class MainViewModel extends ViewModel { + + public MainViewModel() { + super("joke view"); + setState(new MainState()); + } +} diff --git a/src/main/java/interface_adapter/login/LoginState.java b/src/main/java/interface_adapter/login/LoginState.java new file mode 100644 index 000000000..4b5e28fc7 --- /dev/null +++ b/src/main/java/interface_adapter/login/LoginState.java @@ -0,0 +1,4 @@ +package interface_adapter.login; + +public class LoginState { +} diff --git a/src/main/java/interface_adapter/login/LoginViewModel.java b/src/main/java/interface_adapter/login/LoginViewModel.java new file mode 100644 index 000000000..a93900bee --- /dev/null +++ b/src/main/java/interface_adapter/login/LoginViewModel.java @@ -0,0 +1,4 @@ +package interface_adapter.login; + +public class LoginViewModel { +} diff --git a/src/main/java/interface_adapter/note/NoteViewModel.java b/src/main/java/interface_adapter/note/NoteViewModel.java index 6e185d0fa..f085f6df8 100644 --- a/src/main/java/interface_adapter/note/NoteViewModel.java +++ b/src/main/java/interface_adapter/note/NoteViewModel.java @@ -1,6 +1,6 @@ package interface_adapter.note; -import interface_adapter.ViewModel; +import view.ViewModel; /** * The ViewModel for the NoteView. diff --git a/src/main/java/interface_adapter/search_favourites/SearchFavouritesPresenter.java b/src/main/java/interface_adapter/search_favourites/SearchFavouritesPresenter.java new file mode 100644 index 000000000..933f20b9b --- /dev/null +++ b/src/main/java/interface_adapter/search_favourites/SearchFavouritesPresenter.java @@ -0,0 +1,27 @@ +package interface_adapter.search_favourites; + +import use_case.search_favourites.SearchFavouritesOutputBoundary; + +public class SearchFavouritesPresenter implements SearchFavouritesOutputBoundary { + + private String resultMessage; + private String errorMessage; + + @Override + public void presentFavouritesSearchResult(String result) { + this.resultMessage = result; + } + + @Override + public void presentFavouritesSearchError(String error) { + this.errorMessage = error; + } + + public String getResultMessage() { + return resultMessage; + } + + public String getErrorMessage() { + return errorMessage; + } +} diff --git a/src/main/java/use_case/add_to_fav/AddToFavDataAccessInterface.java b/src/main/java/use_case/add_to_fav/AddToFavDataAccessInterface.java new file mode 100644 index 000000000..1adba621b --- /dev/null +++ b/src/main/java/use_case/add_to_fav/AddToFavDataAccessInterface.java @@ -0,0 +1,28 @@ + +package use_case.add_to_fav; + +import entity.Joke; +import entity.User; + +/** + * The AddToFavDataAccessInterface defines methods for accessing + * user and joke data in the data storage layer. + */ +public interface AddToFavDataAccessInterface { + /** + * Retrieves a user by username. + * + * @param username the username of the user + * @return the User object, or null if not found + */ + User get(String username); + + String getCurrentUsername(); + /** + * Saves the updated user to the data storage. + * + * @param user the User object to save + */ + + void save(User user); +} \ No newline at end of file diff --git a/src/main/java/use_case/add_to_fav/AddToFavInputBoundary.java b/src/main/java/use_case/add_to_fav/AddToFavInputBoundary.java new file mode 100644 index 000000000..e84ce5108 --- /dev/null +++ b/src/main/java/use_case/add_to_fav/AddToFavInputBoundary.java @@ -0,0 +1,15 @@ +package use_case.add_to_fav; + +/** + * The AddToFavInputBoundary interface defines the input boundary + * for the Add to Favorites use case. + */ +public interface AddToFavInputBoundary { + /** + * Adds a joke to a user's favorites list. + * + * @param inputData the input data containing the username and joke ID + * @return a response model indicating the result of the operation + */ + void executeAddToFav(AddToFavInputData inputData); +} diff --git a/src/main/java/use_case/add_to_fav/AddToFavInputData.java b/src/main/java/use_case/add_to_fav/AddToFavInputData.java new file mode 100644 index 000000000..c0991620f --- /dev/null +++ b/src/main/java/use_case/add_to_fav/AddToFavInputData.java @@ -0,0 +1,39 @@ +package use_case.add_to_fav; + +/** + * The AddToFavInputData class represents the input data + * for the Add to Favorites use case. + */ +public class AddToFavInputData { + private final String jokeContent; + private final String explanation; + + /** + * Constructs an AddToFavInputData with the specified username and joke ID. + * + * @param jokeContent the username of the user + * @param explanation the ID of the joke to add to favorites + */ + public AddToFavInputData(String jokeContent, String explanation) { + this.jokeContent = jokeContent; + this.explanation = explanation; + } + + /** + * Gets the username of the user. + * + * @return the username of the user + */ + public String getJokeContent() { + return jokeContent; + } + + /** + * Gets the ID of the joke. + * + * @return the joke ID + */ + public String getExplanation() { + return explanation; + } +} diff --git a/src/main/java/use_case/add_to_fav/AddToFavInteractor.java b/src/main/java/use_case/add_to_fav/AddToFavInteractor.java new file mode 100644 index 000000000..81e0754f2 --- /dev/null +++ b/src/main/java/use_case/add_to_fav/AddToFavInteractor.java @@ -0,0 +1,49 @@ +package use_case.add_to_fav; + +import entity.Joke; +import entity.JokeFactory; +import entity.User; + +/** + * The AddToFavInteractor class implements the business logic for adding jokes + * to a user's favorites list. It communicates with the data access layer to + * retrieve and save data, and uses the output boundary (presenter) to format + * the response. + */ +public class AddToFavInteractor implements AddToFavInputBoundary { + private final AddToFavDataAccessInterface dataAccess; + private final AddToFavOutputBoundary outputBoundary; + + /** + * Constructs an AddToFavInteractor with the specified data access interface + * and output boundary (presenter). + * + * @param dataAccess the interface for accessing user and joke data + * @param outputBoundary the presenter that formats output data + */ + public AddToFavInteractor(AddToFavDataAccessInterface dataAccess, AddToFavOutputBoundary outputBoundary) { + this.dataAccess = dataAccess; + this.outputBoundary = outputBoundary; + } + + /** + * Handles the business logic for adding a joke to a user's favorites list. + * + * @param inputData the input data containing the username and joke ID + */ + @Override + public void executeAddToFav(AddToFavInputData inputData) { + final User user = dataAccess.get(dataAccess.getCurrentUsername()); + if (user == null) { + outputBoundary.prepareFailView("User not found."); + } + else { + JokeFactory jokeFactory = new JokeFactory(); + Joke joke = jokeFactory.create(inputData.getJokeContent(), (int) (Math.random()*100)); + joke.setExplanation(inputData.getExplanation()); + user.getFavorites().add(joke); + dataAccess.save(user); + outputBoundary.prepareSuccessView("Added"); + } + } +} diff --git a/src/main/java/use_case/add_to_fav/AddToFavOutputBoundary.java b/src/main/java/use_case/add_to_fav/AddToFavOutputBoundary.java new file mode 100644 index 000000000..f3681db29 --- /dev/null +++ b/src/main/java/use_case/add_to_fav/AddToFavOutputBoundary.java @@ -0,0 +1,12 @@ +package use_case.add_to_fav; + +/** + * The AddToFavOutputBoundary interface defines the output boundary + * for the Add to Favorites use case. + */ +public interface AddToFavOutputBoundary { + + void prepareSuccessView(String added); + + void prepareFailView(String error); +} diff --git a/src/main/java/use_case/add_to_fav/adapter/AddToFavController.java b/src/main/java/use_case/add_to_fav/adapter/AddToFavController.java new file mode 100644 index 000000000..5aa94db22 --- /dev/null +++ b/src/main/java/use_case/add_to_fav/adapter/AddToFavController.java @@ -0,0 +1,33 @@ +package use_case.add_to_fav.adapter; + +import use_case.add_to_fav.*; + +/** + * The AddToFavController class handles user requests for adding a joke to their favorites. + * It delegates the request to the interactor via the input boundary and returns a response + * to the user interface layer. + */ +public class AddToFavController { + private final AddToFavInputBoundary interactor; + + /** + * Constructs an AddToFavController with the specified input boundary (interactor). + * + * @param interactor the interactor that handles the business logic for adding to favorites + */ + public AddToFavController(AddToFavInputBoundary interactor) { + this.interactor = interactor; + } + + /** + * Adds a joke to the user's list of favorites. + * + * @param jokeContent the username of the user + * @param explanation the ID of the joke to add to favorites + * @return a response model containing the result of the operation + */ + public void execute(String jokeContent, String explanation) { + final AddToFavInputData inputData = new AddToFavInputData(jokeContent, explanation); + interactor.executeAddToFav(inputData); + } +} diff --git a/src/main/java/use_case/add_to_fav/adapter/AddToFavPresenter.java b/src/main/java/use_case/add_to_fav/adapter/AddToFavPresenter.java new file mode 100644 index 000000000..ec99ea1c5 --- /dev/null +++ b/src/main/java/use_case/add_to_fav/adapter/AddToFavPresenter.java @@ -0,0 +1,34 @@ +package use_case.add_to_fav.adapter; + +import use_case.add_to_fav.AddToFavOutputBoundary; +import view.joke_view.JokeState; +import view.joke_view.JokeViewModel; + +/** + * The AddToFavPresenter class formats the output of the Add to Favorites use case + * for the view layer. + */ +public class AddToFavPresenter implements AddToFavOutputBoundary { + + private final JokeViewModel jokeViewModel; + + public AddToFavPresenter(JokeViewModel jokeViewModel) { + this.jokeViewModel = jokeViewModel; + } + + @Override + public void prepareSuccessView(String added) { + final JokeState jokeState = jokeViewModel.getState(); + jokeState.setAddToFavText(added); + jokeViewModel.setState(jokeState); + jokeViewModel.firePropertyChanged(); + } + + @Override + public void prepareFailView(String error) { + final JokeState jokeState = jokeViewModel.getState(); + jokeState.setAddToFavText(error); + jokeViewModel.setState(jokeState); + jokeViewModel.firePropertyChanged(); + } +} diff --git a/src/main/java/use_case/explanation/ExplanationDataAccessInterface.java b/src/main/java/use_case/explanation/ExplanationDataAccessInterface.java new file mode 100644 index 000000000..99fa3c61c --- /dev/null +++ b/src/main/java/use_case/explanation/ExplanationDataAccessInterface.java @@ -0,0 +1,5 @@ +package use_case.explanation; + +public interface ExplanationDataAccessInterface { + String getExplanation(String joke); +} diff --git a/src/main/java/use_case/explanation/ExplanationInputBoundary.java b/src/main/java/use_case/explanation/ExplanationInputBoundary.java new file mode 100644 index 000000000..205ff33d1 --- /dev/null +++ b/src/main/java/use_case/explanation/ExplanationInputBoundary.java @@ -0,0 +1,9 @@ +package use_case.explanation; + +public interface ExplanationInputBoundary { + + /** + * Executes the explanation use case. + */ + void executeExplanation(ExplanationInputData explanationInputData); +} diff --git a/src/main/java/use_case/explanation/ExplanationInputData.java b/src/main/java/use_case/explanation/ExplanationInputData.java new file mode 100644 index 000000000..c52ad6cf1 --- /dev/null +++ b/src/main/java/use_case/explanation/ExplanationInputData.java @@ -0,0 +1,18 @@ +package use_case.explanation; + +/** + * The Input Data for the Login Use Case. + */ +public class ExplanationInputData { + + private final String jokeContent; + + public ExplanationInputData(String jokeContent) { + this.jokeContent = jokeContent; + } + + String getJokeContent() { + return jokeContent; + } + +} diff --git a/src/main/java/use_case/explanation/ExplanationInteractor.java b/src/main/java/use_case/explanation/ExplanationInteractor.java new file mode 100644 index 000000000..a19a390d6 --- /dev/null +++ b/src/main/java/use_case/explanation/ExplanationInteractor.java @@ -0,0 +1,26 @@ +package use_case.explanation; + +import use_case.generate.GenerateDataAccessInterface; +import use_case.generate.GenerateOuputBoundary; + +public class ExplanationInteractor implements ExplanationInputBoundary { + + private final ExplanationDataAccessInterface explanationDataAccessObject; + private final ExplanationOutputBoundary explanationOutputBoundary; + + public ExplanationInteractor(ExplanationDataAccessInterface explanationDataAccessObject, ExplanationOutputBoundary explanationOutputBoundary) { + this.explanationDataAccessObject = explanationDataAccessObject; + this.explanationOutputBoundary = explanationOutputBoundary; + } + + @Override + public void executeExplanation(ExplanationInputData explanationInputData) { + try { + final String explanation = explanationDataAccessObject.getExplanation(explanationInputData.getJokeContent()); + explanationOutputBoundary.prepareSuccessView(new ExplanationOutputData(explanation)); + } + catch (RuntimeException ex) { + explanationOutputBoundary.prepareFailView(ex.getMessage()); + } + } +} diff --git a/src/main/java/use_case/explanation/ExplanationOutputBoundary.java b/src/main/java/use_case/explanation/ExplanationOutputBoundary.java new file mode 100644 index 000000000..cdd828990 --- /dev/null +++ b/src/main/java/use_case/explanation/ExplanationOutputBoundary.java @@ -0,0 +1,15 @@ +package use_case.explanation; + +public interface ExplanationOutputBoundary { + /** + * Prepares the success view for the Note related Use Cases. + * @param explanationOutputData the output data + */ + void prepareSuccessView(ExplanationOutputData explanationOutputData); + + /** + * Prepares the failure view. + * @param errorMessage the explanation of the failure + */ + void prepareFailView(String errorMessage); +} diff --git a/src/main/java/use_case/explanation/ExplanationOutputData.java b/src/main/java/use_case/explanation/ExplanationOutputData.java new file mode 100644 index 000000000..d974b4acf --- /dev/null +++ b/src/main/java/use_case/explanation/ExplanationOutputData.java @@ -0,0 +1,18 @@ +package use_case.explanation; + +/** + * Output Data for the Explanation Use Case. + */ +public class ExplanationOutputData { + + private final String explanation; + + public ExplanationOutputData(String explanation) { + this.explanation = explanation; +// this.useCaseFailed = useCaseFailed; + } + + public String getExplanation() { + return explanation; + } +} diff --git a/src/main/java/use_case/explanation/adapter/ExplanationController.java b/src/main/java/use_case/explanation/adapter/ExplanationController.java new file mode 100644 index 000000000..084a0a521 --- /dev/null +++ b/src/main/java/use_case/explanation/adapter/ExplanationController.java @@ -0,0 +1,18 @@ +package use_case.explanation.adapter; + +import use_case.explanation.ExplanationInputBoundary; +import use_case.explanation.ExplanationInputData; + +public class ExplanationController { + + private final ExplanationInputBoundary explanationInputBoundary; + + public ExplanationController(ExplanationInputBoundary explanationInteractor) { + this.explanationInputBoundary = explanationInteractor; + } + + public void execute(String jokeContent) { + ExplanationInputData input = new ExplanationInputData(jokeContent); + explanationInputBoundary.executeExplanation(input); + } +} diff --git a/src/main/java/use_case/explanation/adapter/ExplanationPresenter.java b/src/main/java/use_case/explanation/adapter/ExplanationPresenter.java new file mode 100644 index 000000000..21d07a55b --- /dev/null +++ b/src/main/java/use_case/explanation/adapter/ExplanationPresenter.java @@ -0,0 +1,28 @@ +package use_case.explanation.adapter; + +import use_case.explanation.ExplanationOutputBoundary; +import use_case.explanation.ExplanationOutputData; +import view.joke_view.JokeState; +import view.joke_view.JokeViewModel; + +public class ExplanationPresenter implements ExplanationOutputBoundary { + + private final JokeViewModel jokeViewModel; + + public ExplanationPresenter(JokeViewModel jokeViewModel) { + this.jokeViewModel = jokeViewModel; + } + + @Override + public void prepareSuccessView(ExplanationOutputData explanationOutputData) { + final JokeState jokeState = jokeViewModel.getState(); + jokeState.setExplanation(explanationOutputData.getExplanation()); + jokeViewModel.setState(jokeState); + jokeViewModel.firePropertyChanged(); + } + + @Override + //TODO to be continued + public void prepareFailView(String errorMessage) { + } +} diff --git a/src/main/java/use_case/fav_search/FavSearchDataAccessInterface.java b/src/main/java/use_case/fav_search/FavSearchDataAccessInterface.java new file mode 100644 index 000000000..908a3643d --- /dev/null +++ b/src/main/java/use_case/fav_search/FavSearchDataAccessInterface.java @@ -0,0 +1,13 @@ +package use_case.fav_search; + +import entity.Joke; + +import java.util.List; + +public interface FavSearchDataAccessInterface { + /** + * Get the user's favourites list. + * @return a list of jokes marked as favourites. + */ + List getFavourites(); +} diff --git a/src/main/java/use_case/fav_search/FavSearchInputBoundary.java b/src/main/java/use_case/fav_search/FavSearchInputBoundary.java new file mode 100644 index 000000000..e4aac40d8 --- /dev/null +++ b/src/main/java/use_case/fav_search/FavSearchInputBoundary.java @@ -0,0 +1,9 @@ +package use_case.fav_search; + +public interface FavSearchInputBoundary { + /** + * Search for jokes in the user's favourites. + * @param keyword the keyword to search for. + */ + void executeSearch(String keyword); +} diff --git a/src/main/java/use_case/fav_search/FavSearchInteractor.java b/src/main/java/use_case/fav_search/FavSearchInteractor.java new file mode 100644 index 000000000..5ebc125d3 --- /dev/null +++ b/src/main/java/use_case/fav_search/FavSearchInteractor.java @@ -0,0 +1,40 @@ +package use_case.fav_search; + +import entity.Joke; + +import java.util.List; +import java.util.stream.Collectors; + +public class FavSearchInteractor implements FavSearchInputBoundary { + private final FavSearchDataAccessInterface dataAccess; + private final FavSearchOutputBoundary presenter; + + public FavSearchInteractor(FavSearchOutputBoundary presenter, FavSearchDataAccessInterface dataAccess) { + this.dataAccess = dataAccess; + this.presenter = presenter; + } + + @Override + public void executeSearch(String keyword) { + try { + final List favourites = dataAccess.getFavourites(); + final List matchingJokes = favourites.stream() + .filter(joke -> joke.getContent().toLowerCase().contains(keyword.toLowerCase())) + .collect(Collectors.toList()); + + if (matchingJokes.isEmpty()) { + presenter.presentFailure("No matching jokes found in favourites."); + } + else { + final Joke selectedJoke = matchingJokes.get(0); + final FavSearchOutputData outputData = new FavSearchOutputData( + selectedJoke.getContent() + ); + presenter.prepareSuccessView(outputData); + } + } catch (RuntimeException e) { + presenter.presentFailure("Error searching favourites: " + e.getMessage()); + } + } + +} diff --git a/src/main/java/use_case/fav_search/FavSearchOutputBoundary.java b/src/main/java/use_case/fav_search/FavSearchOutputBoundary.java new file mode 100644 index 000000000..07e5ff644 --- /dev/null +++ b/src/main/java/use_case/fav_search/FavSearchOutputBoundary.java @@ -0,0 +1,16 @@ +package use_case.fav_search; + +public interface FavSearchOutputBoundary { + + /** + * Prepares the view for successful search results. + * @param searchFavouritesOutputData the output data containing the joke content. + */ + void prepareSuccessView(FavSearchOutputData searchFavouritesOutputData); + + /** + * Presents an error message if the search fails. + * @param message the error message. + */ + void presentFailure(String message); +} diff --git a/src/main/java/use_case/fav_search/FavSearchOutputData.java b/src/main/java/use_case/fav_search/FavSearchOutputData.java new file mode 100644 index 000000000..5f7371a8f --- /dev/null +++ b/src/main/java/use_case/fav_search/FavSearchOutputData.java @@ -0,0 +1,15 @@ +package use_case.fav_search; + +public class FavSearchOutputData { + + private final String jokeContent; + + public FavSearchOutputData(String jokeContent) { + + this.jokeContent = jokeContent; + } + + public String getJokeContent() { + return jokeContent; + } +} diff --git a/src/main/java/use_case/fav_search/adapter/FavSearchController.java b/src/main/java/use_case/fav_search/adapter/FavSearchController.java new file mode 100644 index 000000000..48269c7d3 --- /dev/null +++ b/src/main/java/use_case/fav_search/adapter/FavSearchController.java @@ -0,0 +1,16 @@ +package use_case.fav_search.adapter; + +import use_case.fav_search.FavSearchInputBoundary; + +public class FavSearchController { + + private final FavSearchInputBoundary interactor; + + public FavSearchController(FavSearchInputBoundary interactor) { + this.interactor = interactor; + } + + public void executeSearch(String keyword) { + interactor.executeSearch(keyword); + } +} diff --git a/src/main/java/use_case/fav_search/adapter/FavSearchPresenter.java b/src/main/java/use_case/fav_search/adapter/FavSearchPresenter.java new file mode 100644 index 000000000..c33b2f026 --- /dev/null +++ b/src/main/java/use_case/fav_search/adapter/FavSearchPresenter.java @@ -0,0 +1,40 @@ +package use_case.fav_search.adapter; + +import use_case.fav_search.FavSearchOutputBoundary; +import use_case.fav_search.FavSearchOutputData; +import view.favourite_view.FavouriteView; +import view.favourite_view.FavouriteViewModel; +import view.joke_view.JokeFrameBuilder; + +import javax.swing.*; + +/** + Search favourites presenter. + */ + +public class FavSearchPresenter implements FavSearchOutputBoundary { + + private final JokeFrameBuilder jokeFrameBuilder; + + public FavSearchPresenter(JokeFrameBuilder jokeFrameBuilder) { + this.jokeFrameBuilder = jokeFrameBuilder; + } + + @Override + public void prepareSuccessView(FavSearchOutputData searchFavouritesOutputData) { + final JFrame frame = jokeFrameBuilder + .addJokeView() + .setJokeContent(searchFavouritesOutputData.getJokeContent()) + .addExplanationUseCase() + .addAddToFavUseCase() + .build(); + + frame.pack(); + frame.setVisible(true); + } + + @Override + public void presentFailure(String message) { + + } +} diff --git a/src/main/java/use_case/favourite/FavouriteInputBoundary.java b/src/main/java/use_case/favourite/FavouriteInputBoundary.java new file mode 100644 index 000000000..f4893d07d --- /dev/null +++ b/src/main/java/use_case/favourite/FavouriteInputBoundary.java @@ -0,0 +1,10 @@ +package use_case.favourite; + +public interface FavouriteInputBoundary { + + /** + * Executes the favourite use case. + * @param favouriteInputData the input data + */ + void execute(FavouriteInputData favouriteInputData); +} diff --git a/src/main/java/use_case/favourite/FavouriteInputData.java b/src/main/java/use_case/favourite/FavouriteInputData.java new file mode 100644 index 000000000..a100dc392 --- /dev/null +++ b/src/main/java/use_case/favourite/FavouriteInputData.java @@ -0,0 +1,15 @@ +package use_case.favourite; + +import entity.User; + +public class FavouriteInputData { + private final User user; + + public FavouriteInputData(User user) { + this.user = user; + } + + User getUser() { + return user; + } +} diff --git a/src/main/java/use_case/favourite/FavouriteInteractor.java b/src/main/java/use_case/favourite/FavouriteInteractor.java new file mode 100644 index 000000000..29ca54f18 --- /dev/null +++ b/src/main/java/use_case/favourite/FavouriteInteractor.java @@ -0,0 +1,28 @@ +package use_case.favourite; + +import entity.Joke; + +import java.util.List; + +/** + * The "Use Case Interactor" for getting user's favourite + */ +public class FavouriteInteractor implements FavouriteInputBoundary { + + private final FavouriteUserDataAccessInterface favouriteUserDataAccessInterface; + private final FavouriteOutputBoundary favouriteOutputBoundary; + + public FavouriteInteractor(FavouriteUserDataAccessInterface favouriteUserDataAccessInterface, FavouriteOutputBoundary favouriteOutputBoundary) { + this.favouriteUserDataAccessInterface = favouriteUserDataAccessInterface; + this.favouriteOutputBoundary = favouriteOutputBoundary; + } + + @Override + public void execute(FavouriteInputData favouriteInputData) { + final List jokeList = favouriteUserDataAccessInterface.get(favouriteUserDataAccessInterface.getCurrentUsername()).getFavorites(); + // interactor gets input data and outputs output data + final FavouriteOutputData favouriteOutputData = new FavouriteOutputData(jokeList); + + favouriteOutputBoundary.prepareSuccessView(favouriteOutputData); + } +} diff --git a/src/main/java/use_case/favourite/FavouriteOutputBoundary.java b/src/main/java/use_case/favourite/FavouriteOutputBoundary.java new file mode 100644 index 000000000..66130662f --- /dev/null +++ b/src/main/java/use_case/favourite/FavouriteOutputBoundary.java @@ -0,0 +1,15 @@ +package use_case.favourite; + +public interface FavouriteOutputBoundary { + /** + * Prepares the success view for the Note related Use Cases. + * @param favouriteOutputData the output data + */ + void prepareSuccessView(FavouriteOutputData favouriteOutputData); + + /** + * Prepares the failure view. + * @param errorMessage the explanation of the failure + */ + void prepareFailView(String errorMessage); +} diff --git a/src/main/java/use_case/favourite/FavouriteOutputData.java b/src/main/java/use_case/favourite/FavouriteOutputData.java new file mode 100644 index 000000000..34d2c8e63 --- /dev/null +++ b/src/main/java/use_case/favourite/FavouriteOutputData.java @@ -0,0 +1,17 @@ +package use_case.favourite; + +import java.util.List; + +import entity.Joke; + +public class FavouriteOutputData { + private List favouriteJokeList; + + public FavouriteOutputData(List favouriteJokeList) { + this.favouriteJokeList = favouriteJokeList; + } + + public List getFavouriteJokeList() { + return favouriteJokeList; + } +} diff --git a/src/main/java/use_case/favourite/FavouriteUserDataAccessInterface.java b/src/main/java/use_case/favourite/FavouriteUserDataAccessInterface.java new file mode 100644 index 000000000..eb684a2dc --- /dev/null +++ b/src/main/java/use_case/favourite/FavouriteUserDataAccessInterface.java @@ -0,0 +1,9 @@ +package use_case.favourite; + +import entity.User; + +public interface FavouriteUserDataAccessInterface { + String getCurrentUsername(); + + User get(String username); +} diff --git a/src/main/java/use_case/favourite/adapter/FavouriteController.java b/src/main/java/use_case/favourite/adapter/FavouriteController.java new file mode 100644 index 000000000..b41bb8729 --- /dev/null +++ b/src/main/java/use_case/favourite/adapter/FavouriteController.java @@ -0,0 +1,19 @@ +package use_case.favourite.adapter; + +import entity.User; +import use_case.favourite.FavouriteInputBoundary; +import use_case.favourite.FavouriteInputData; + +public class FavouriteController { + + private final FavouriteInputBoundary favouriteInputBoundary; + + public FavouriteController(FavouriteInputBoundary favouriteInputBoundary) { + this.favouriteInputBoundary = favouriteInputBoundary; + } + + public void execute(User user) { + final FavouriteInputData input = new FavouriteInputData(user); + favouriteInputBoundary.execute(input); + } +} diff --git a/src/main/java/use_case/favourite/adapter/FavouritePresenter.java b/src/main/java/use_case/favourite/adapter/FavouritePresenter.java new file mode 100644 index 000000000..0f3685713 --- /dev/null +++ b/src/main/java/use_case/favourite/adapter/FavouritePresenter.java @@ -0,0 +1,33 @@ +package use_case.favourite.adapter; + +import use_case.favourite.FavouriteOutputBoundary; +import use_case.favourite.FavouriteOutputData; +import view.favourite_view.FavouriteState; +import view.favourite_view.FavouriteViewModel; + +public class FavouritePresenter implements FavouriteOutputBoundary { + + private final FavouriteViewModel favouriteViewModel; + + public FavouritePresenter(FavouriteViewModel favouriteViewModel) { + this.favouriteViewModel = favouriteViewModel; + } + + @Override + public void prepareSuccessView(FavouriteOutputData favouriteOutputData) { + final FavouriteState favouriteState = favouriteViewModel.getState(); + favouriteState.setFavourites(favouriteOutputData.getFavouriteJokeList()); + favouriteViewModel.setState(favouriteState); + favouriteViewModel.firePropertyChanged(); + } + + @Override + public void prepareFailView(String errorMessage) { + + } + + @Override + public void prepareFailureView(String errormessage) { + + } +} \ No newline at end of file diff --git a/src/main/java/use_case/funniest/FunniestInputBoundary.java b/src/main/java/use_case/funniest/FunniestInputBoundary.java new file mode 100644 index 000000000..74dba47e9 --- /dev/null +++ b/src/main/java/use_case/funniest/FunniestInputBoundary.java @@ -0,0 +1,11 @@ +package use_case.funniest; + +import entity.User; + +public interface FunniestInputBoundary { + /** + * Search the funniest joke by keyword. + * @param funniestInputData the input data. + */ + void execute(FunniestInputData funniestInputData); +} diff --git a/src/main/java/use_case/funniest/FunniestInputData.java b/src/main/java/use_case/funniest/FunniestInputData.java new file mode 100644 index 000000000..2943d68ae --- /dev/null +++ b/src/main/java/use_case/funniest/FunniestInputData.java @@ -0,0 +1,17 @@ +package use_case.funniest; + +import java.util.List; + +import entity.Joke; + +public class FunniestInputData { + private final List favouriteJokeList; + + public FunniestInputData(List favouriteJokeList) { + this.favouriteJokeList = favouriteJokeList; + } + + List getFavouriteJokeList() { + return favouriteJokeList; + } +} diff --git a/src/main/java/use_case/funniest/FunniestInteractor.java b/src/main/java/use_case/funniest/FunniestInteractor.java new file mode 100644 index 000000000..100a20fad --- /dev/null +++ b/src/main/java/use_case/funniest/FunniestInteractor.java @@ -0,0 +1,29 @@ +package use_case.funniest; + +import entity.Joke; + +import java.util.List; + +public class FunniestInteractor implements FunniestInputBoundary { + + private final FunniestOutputBoundary funniestOutputBoundary; + + public FunniestInteractor(FunniestOutputBoundary funniestoutputBoundary) { + this.funniestOutputBoundary = funniestoutputBoundary; + } + + @Override + public void execute(FunniestInputData funniestInputData) { + final List jokeList = funniestInputData.getFavouriteJokeList(); + Joke funniestJoke = jokeList.get(0); + for (Joke joke : jokeList) { + if (joke.getScore() > funniestJoke.getScore()) { + funniestJoke = joke; + } + } + // interactor gets input data and outputs output data + final FunniestOutputData funniestOutputData = new FunniestOutputData(funniestJoke); + + funniestOutputBoundary.prepareSuccessView(funniestOutputData); + } +} diff --git a/src/main/java/use_case/funniest/FunniestOutputBoundary.java b/src/main/java/use_case/funniest/FunniestOutputBoundary.java new file mode 100644 index 000000000..da46843c7 --- /dev/null +++ b/src/main/java/use_case/funniest/FunniestOutputBoundary.java @@ -0,0 +1,15 @@ +package use_case.funniest; + +public interface FunniestOutputBoundary { + /** + * Prepares the success view for the Note related Use Cases. + * @param funniestOutputData the output data + */ + void prepareSuccessView(FunniestOutputData funniestOutputData); + + /** + * Prepares the failure view. + * @param errorMessage the explanation of the failure + */ + void prepareFailView(String errorMessage); +} diff --git a/src/main/java/use_case/funniest/FunniestOutputData.java b/src/main/java/use_case/funniest/FunniestOutputData.java new file mode 100644 index 000000000..91cf96165 --- /dev/null +++ b/src/main/java/use_case/funniest/FunniestOutputData.java @@ -0,0 +1,19 @@ +package use_case.funniest; + +import entity.Joke; + +public class FunniestOutputData { + private final Joke joke; + + public FunniestOutputData(Joke joke) { + this.joke = joke; + } + + public Joke getJoke() { + return joke; + } + + public String getJokeContent() { + return joke.getContent(); + } +} diff --git a/src/main/java/use_case/funniest/adapter/FunniestController.java b/src/main/java/use_case/funniest/adapter/FunniestController.java new file mode 100644 index 000000000..1d58d2d6d --- /dev/null +++ b/src/main/java/use_case/funniest/adapter/FunniestController.java @@ -0,0 +1,20 @@ +package use_case.funniest.adapter; + +import entity.Joke; +import use_case.funniest.FunniestInputBoundary; +import use_case.funniest.FunniestInputData; + +import java.util.List; + +public class FunniestController { + private final FunniestInputBoundary funniestInputBoundary; + + public FunniestController(FunniestInputBoundary funniestInteractor) { + this.funniestInputBoundary = funniestInteractor; + } + + public void execute(List favourites) { + final FunniestInputData funniestInputData = new FunniestInputData(favourites); + funniestInputBoundary.execute(funniestInputData); + } +} diff --git a/src/main/java/use_case/funniest/adapter/FunniestPresenter.java b/src/main/java/use_case/funniest/adapter/FunniestPresenter.java new file mode 100644 index 000000000..ea9773b5c --- /dev/null +++ b/src/main/java/use_case/funniest/adapter/FunniestPresenter.java @@ -0,0 +1,37 @@ +package use_case.funniest.adapter; + +import data_access.FileDataAccessObject; +import use_case.funniest.FunniestOutputBoundary; +import use_case.funniest.FunniestOutputData; +import view.joke_view.JokeFrameBuilder; + +import javax.swing.*; + +public class FunniestPresenter implements FunniestOutputBoundary { + + private final JokeFrameBuilder jokeFrameBuilder; + private final FileDataAccessObject fileDataAccessObject; + + public FunniestPresenter(JokeFrameBuilder jokeFrameBuilder, FileDataAccessObject fileDataAccessObject) { + this.fileDataAccessObject = fileDataAccessObject; + this.jokeFrameBuilder = jokeFrameBuilder; + } + + @Override + public void prepareSuccessView(FunniestOutputData funniestOutputData) { + final JFrame frame = jokeFrameBuilder + .addJokeView() + .setJokeContent(funniestOutputData.getJokeContent()) + .addExplanationUseCase() + .addAddToFavUseCase(fileDataAccessObject) + .build(); + + frame.pack(); + frame.setVisible(true); + } + + @Override + public void prepareFailView(String errorMessage) { + + } +} diff --git a/src/main/java/use_case/generate/GenerateDataAccessInterface.java b/src/main/java/use_case/generate/GenerateDataAccessInterface.java new file mode 100644 index 000000000..9dc1412a6 --- /dev/null +++ b/src/main/java/use_case/generate/GenerateDataAccessInterface.java @@ -0,0 +1,5 @@ +package use_case.generate; + +public interface GenerateDataAccessInterface { + String getJokeContent() throws RuntimeException; +} diff --git a/src/main/java/use_case/generate/GenerateInputBoundary.java b/src/main/java/use_case/generate/GenerateInputBoundary.java new file mode 100644 index 000000000..785fe6b1a --- /dev/null +++ b/src/main/java/use_case/generate/GenerateInputBoundary.java @@ -0,0 +1,10 @@ +package use_case.generate; + +public interface GenerateInputBoundary { + + /** + * Executes the generate use case. + */ + //TODO change to using inputData + void executeGenerate(); +} diff --git a/src/main/java/use_case/generate/GenerateInteractor.java b/src/main/java/use_case/generate/GenerateInteractor.java new file mode 100644 index 000000000..382c1bb0c --- /dev/null +++ b/src/main/java/use_case/generate/GenerateInteractor.java @@ -0,0 +1,34 @@ +package use_case.generate; + +import use_case.note.DataAccessException; +import use_case.note.NoteDataAccessInterface; +import use_case.note.NoteOutputBoundary; + +/** + * The "Use Case Interactor" for generating joke + */ +public class GenerateInteractor implements GenerateInputBoundary{ + + private final GenerateDataAccessInterface generateDataAccessInterface; + private final GenerateOuputBoundary generateOuputBoundary; + + public GenerateInteractor(GenerateDataAccessInterface generateDataAccessInterface, + GenerateOuputBoundary generateOuputBoundary) { + this.generateDataAccessInterface = generateDataAccessInterface; + this.generateOuputBoundary = generateOuputBoundary; + } + + @Override + public void executeGenerate() { + try { + final String jokeContent = generateDataAccessInterface.getJokeContent(); + // interactor gets input data and outputs output data + final GenerateOutputData generateOutputData = new GenerateOutputData(jokeContent); + + generateOuputBoundary.prepareSuccessView(generateOutputData); + } + catch (RuntimeException ex) { + generateOuputBoundary.prepareFailView(ex.getMessage()); + } + } +} diff --git a/src/main/java/use_case/generate/GenerateOuputBoundary.java b/src/main/java/use_case/generate/GenerateOuputBoundary.java new file mode 100644 index 000000000..db8836c1c --- /dev/null +++ b/src/main/java/use_case/generate/GenerateOuputBoundary.java @@ -0,0 +1,15 @@ +package use_case.generate; + +public interface GenerateOuputBoundary { + /** + * Prepares the success view for the Note related Use Cases. + * @param jokeContent the output data + */ + void prepareSuccessView(GenerateOutputData generateOutputData); + + /** + * Prepares the failure view. + * @param errorMessage the explanation of the failure + */ + void prepareFailView(String errorMessage); +} diff --git a/src/main/java/use_case/generate/GenerateOutputData.java b/src/main/java/use_case/generate/GenerateOutputData.java new file mode 100644 index 000000000..c95ea0d13 --- /dev/null +++ b/src/main/java/use_case/generate/GenerateOutputData.java @@ -0,0 +1,18 @@ +package use_case.generate; + +/** + * Output Data for the Generate Use Case. + */ +public class GenerateOutputData { + + private final String jokeContent; + + public GenerateOutputData(String jokeContent) { + this.jokeContent = jokeContent; +// this.useCaseFailed = useCaseFailed; + } + + public String getJokeContent() { + return jokeContent; + } +} diff --git a/src/main/java/use_case/generate/adapter/DemoGeneratePresenter.java b/src/main/java/use_case/generate/adapter/DemoGeneratePresenter.java new file mode 100644 index 000000000..b769568da --- /dev/null +++ b/src/main/java/use_case/generate/adapter/DemoGeneratePresenter.java @@ -0,0 +1,49 @@ +//package use_case.generate.adapter; +// +//import data_access.ExplanationDataAccessObject; +//import data_access.MockExplanationDataAccessObject; +//import use_case.explain.ExplanationDataAccessInterface; +//import use_case.generate.GenerateOuputBoundary; +// +//import javax.swing.*; +// +///** +// * simple presenter for demo, need to change (view model and stuff) +// */ +//public class DemoGeneratePresenter implements GenerateOuputBoundary { +// public static final int HEIGHT = 300; +// public static final int WIDTH = 400; +// +// @Override +// public void prepareSuccessView(String jokeContent) { +// ExplanationDataAccessInterface explainator = new MockExplanationDataAccessObject(); +//// ExplanationDataAccessInterface explainator = new ExplanationDataAccessObject(); +// final JFrame frame = new JFrame(); +// frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); +// frame.setTitle("Joke"); +// frame.setSize(WIDTH, HEIGHT); +// +// JPanel panel = new JPanel(); +// panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); +// panel.add(new JTextArea(jokeContent + explainator.getExplanation(jokeContent))); +// +// frame.add(panel); +// frame.setVisible(true); +// } +// +// @Override +// public void prepareFailView(String errorMessage) { +// final JFrame frame = new JFrame(); +// frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); +// frame.setTitle("Joke"); +// frame.setSize(WIDTH, HEIGHT); +// +// JPanel panel = new JPanel(); +// panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); +// panel.add(new JTextArea(5, 20)); +// +// +// frame.add(panel); +// frame.setVisible(true); +// } +//} diff --git a/src/main/java/use_case/generate/adapter/GenerateController.java b/src/main/java/use_case/generate/adapter/GenerateController.java new file mode 100644 index 000000000..07efb3d59 --- /dev/null +++ b/src/main/java/use_case/generate/adapter/GenerateController.java @@ -0,0 +1,24 @@ +package use_case.generate.adapter; + +import use_case.generate.GenerateInputBoundary; + +/** + * Controller for our generate Use Cases. + */ +public class GenerateController { + + private final GenerateInputBoundary generateInputBoundary; + + public GenerateController(GenerateInputBoundary generateInputBoundary) { + this.generateInputBoundary = generateInputBoundary; + } + + /** + * Executes the generate related Use Cases. + */ + public void execute() { + generateInputBoundary.executeGenerate(); + } + //may change to add user + //controller is responsible for changing raw info from view states to inputData +} diff --git a/src/main/java/use_case/generate/adapter/GeneratePresenter.java b/src/main/java/use_case/generate/adapter/GeneratePresenter.java new file mode 100644 index 000000000..622bd9d90 --- /dev/null +++ b/src/main/java/use_case/generate/adapter/GeneratePresenter.java @@ -0,0 +1,41 @@ +package use_case.generate.adapter; + +import data_access.FileDataAccessObject; +import use_case.generate.GenerateOuputBoundary; +import use_case.generate.GenerateOutputData; +import view.joke_view.JokeFrameBuilder; +import visitor.GenerateVisitor; +import visitor.JokeVisitor; +import visitor.Visitor; + +public class GeneratePresenter implements GenerateOuputBoundary { + // not a typical presenter, more similar to main + + private final FileDataAccessObject fileDataAccessObject; + + public GeneratePresenter(FileDataAccessObject fileDataAccessObject) { + this.fileDataAccessObject = fileDataAccessObject; + } + + /** + * Prepares the success view for the Note related Use Cases. + * + * @param generateOutputData the output data + */ + @Override + public void prepareSuccessView(GenerateOutputData generateOutputData) { + final JokeVisitor visitor = new Visitor(fileDataAccessObject); + visitor.visit(generateOutputData); + } + + /** + * Prepares the failure view for the Note related Use Cases. + * + * @param errorMessage the explanation of the failure + */ + @Override + public void prepareFailView(String errorMessage) { +// jokeViewModel.getState().setError(errorMessage); +// jokeViewModel.firePropertyChanged(); + } +} diff --git a/src/main/java/use_case/login/LoginInputBoundary.java b/src/main/java/use_case/login/LoginInputBoundary.java new file mode 100644 index 000000000..faf72dc96 --- /dev/null +++ b/src/main/java/use_case/login/LoginInputBoundary.java @@ -0,0 +1,13 @@ +package use_case.login; + +/** + * Input Boundary for actions which are related to logging in. + */ +public interface LoginInputBoundary { + + /** + * Executes the login use case. + * @param loginInputData the input data + */ + void execute(LoginInputData loginInputData); +} diff --git a/src/main/java/use_case/login/LoginInputData.java b/src/main/java/use_case/login/LoginInputData.java new file mode 100644 index 000000000..363316832 --- /dev/null +++ b/src/main/java/use_case/login/LoginInputData.java @@ -0,0 +1,24 @@ +package use_case.login; + +/** + * The Input Data for the Login Use Case. + */ +public class LoginInputData { + + private final String username; + private final String password; + + public LoginInputData(String username, String password) { + this.username = username; + this.password = password; + } + + String getUsername() { + return username; + } + + String getPassword() { + return password; + } + +} diff --git a/src/main/java/use_case/login/LoginInteractor.java b/src/main/java/use_case/login/LoginInteractor.java new file mode 100644 index 000000000..b9621b19e --- /dev/null +++ b/src/main/java/use_case/login/LoginInteractor.java @@ -0,0 +1,32 @@ +package use_case.login; + +import entity.User; + +public class LoginInteractor implements LoginInputBoundary { + private final LoginUserDataAccessInterface userDataAccess; + private final LoginOutputBoundary presenter; + + public LoginInteractor(LoginUserDataAccessInterface userDataAccess, LoginOutputBoundary presenter) { + this.userDataAccess = userDataAccess; + this.presenter = presenter; + } + + @Override + public void execute(LoginInputData inputData) { + final String username = inputData.getUsername(); + final String password = inputData.getPassword(); + + if (!userDataAccess.existsByName(username)) { + presenter.prepareFailView("User does not exist."); + } + else { + final User user = userDataAccess.get(username); + if (!user.getPassword().equals(password)) { + presenter.prepareFailView("Incorrect password."); + } + else { + presenter.prepareSuccessView(new LoginOutputData(username)); + } + } + } +} diff --git a/src/main/java/use_case/login/LoginOutputBoundary.java b/src/main/java/use_case/login/LoginOutputBoundary.java new file mode 100644 index 000000000..08bc4731f --- /dev/null +++ b/src/main/java/use_case/login/LoginOutputBoundary.java @@ -0,0 +1,18 @@ +package use_case.login; + +/** + * The output boundary for the Login Use Case. + */ +public interface LoginOutputBoundary { + /** + * Prepares the success view for the Login Use Case. + * @param outputData the output data + */ + void prepareSuccessView(LoginOutputData 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/login/LoginOutputData.java b/src/main/java/use_case/login/LoginOutputData.java new file mode 100644 index 000000000..8f7110fab --- /dev/null +++ b/src/main/java/use_case/login/LoginOutputData.java @@ -0,0 +1,13 @@ +package use_case.login; + +public class LoginOutputData { + private final String username; + + public LoginOutputData(String username) { + this.username = username; + } + + 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 new file mode 100644 index 000000000..681e8a52e --- /dev/null +++ b/src/main/java/use_case/login/LoginUserDataAccessInterface.java @@ -0,0 +1,41 @@ +package use_case.login; + +import entity.User; + +/** + * DAO for the Login Use Case. + */ +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); + + /** + * Saves the user. + * @param user the user to save + */ + void save(User user); + + /** + * Returns the user with the given username. + * @param username the username to look up + * @return the user with the given username + */ + 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. + */ + String getCurrentUsername(); + + /** + * 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. + */ + void setCurrentUsername(String username); +} diff --git a/src/main/java/use_case/login/adapter/LoginController.java b/src/main/java/use_case/login/adapter/LoginController.java new file mode 100644 index 000000000..2bc6068e2 --- /dev/null +++ b/src/main/java/use_case/login/adapter/LoginController.java @@ -0,0 +1,28 @@ +package use_case.login.adapter; + +import use_case.login.LoginInputBoundary; +import use_case.login.LoginInputData; + +/** + * The controller for the Login Use Case. + */ +public class LoginController { + + private final LoginInputBoundary loginUseCaseInteractor; + + public LoginController(LoginInputBoundary loginUseCaseInteractor) { + this.loginUseCaseInteractor = loginUseCaseInteractor; + } + + /** + * Executes the Login Use Case. + * @param username the username of the user logging in + * @param password the password of the user logging in + */ + public void execute(String username, String password) { + final LoginInputData loginInputData = new LoginInputData( + username, password); + + loginUseCaseInteractor.execute(loginInputData); + } +} diff --git a/src/main/java/use_case/login/adapter/LoginPresenter.java b/src/main/java/use_case/login/adapter/LoginPresenter.java new file mode 100644 index 000000000..6f8b25e45 --- /dev/null +++ b/src/main/java/use_case/login/adapter/LoginPresenter.java @@ -0,0 +1,37 @@ +package use_case.login.adapter; + +import use_case.login.LoginOutputBoundary; +import use_case.login.LoginOutputData; +import view.login.LoginViewModel; +import view.ViewManagerModel; +import view.main.MainState; +import view.main.MainView; +import view.main.MainViewModel; + +public class LoginPresenter implements LoginOutputBoundary { + + private final MainViewModel mainViewModel; + private final LoginViewModel loginViewModel; + private final ViewManagerModel viewManagerModel; + + public LoginPresenter(ViewManagerModel viewManagerModel, MainViewModel mainViewModel, LoginViewModel loginViewModel) { + this.mainViewModel = mainViewModel; + this.loginViewModel = loginViewModel; + this.viewManagerModel = viewManagerModel; + } + + @Override + public void prepareSuccessView(LoginOutputData response) { + final MainState mainState = mainViewModel.getState(); + mainState.setUsername(response.getUsername()); + mainViewModel.setState(mainState); + mainViewModel.firePropertyChanged(); + + this.viewManagerModel.setState(mainViewModel.getViewName()); + this.viewManagerModel.firePropertyChanged(); + } + + @Override + public void prepareFailView(String errorMessage) { + } +} diff --git a/src/main/java/use_case/logout/LogoutInputBoundary.java b/src/main/java/use_case/logout/LogoutInputBoundary.java new file mode 100644 index 000000000..189f50168 --- /dev/null +++ b/src/main/java/use_case/logout/LogoutInputBoundary.java @@ -0,0 +1,13 @@ +package use_case.logout; + +/** + * Input Boundary for actions which are related to logging in. + */ +public interface LogoutInputBoundary { + + /** + * Executes the Logout use case. + * @param LogoutInputData the input data + */ + void execute(LogoutInputData LogoutInputData); +} diff --git a/src/main/java/use_case/logout/LogoutInputData.java b/src/main/java/use_case/logout/LogoutInputData.java new file mode 100644 index 000000000..660e855fe --- /dev/null +++ b/src/main/java/use_case/logout/LogoutInputData.java @@ -0,0 +1,17 @@ +package use_case.logout; + +/** + * The Input Data for the Logout Use Case. + */ +public class LogoutInputData { + + private final String username; + + public LogoutInputData(String username) { + this.username = username; + } + + String getUsername() { + return username; } + +} diff --git a/src/main/java/use_case/logout/LogoutInteractor.java b/src/main/java/use_case/logout/LogoutInteractor.java new file mode 100644 index 000000000..9a468dd11 --- /dev/null +++ b/src/main/java/use_case/logout/LogoutInteractor.java @@ -0,0 +1,29 @@ +package use_case.logout; + +import data_access.FileDataAccessObject; +import entity.User; + +/** + * The Logout Interactor. + */ +public class LogoutInteractor implements LogoutInputBoundary { + private FileDataAccessObject fileDataAccessObject; + private LogoutOutputBoundary logoutPresenter; + + public LogoutInteractor(FileDataAccessObject userDataAccessInterface, + LogoutOutputBoundary logoutOutputBoundary) { + this.fileDataAccessObject = userDataAccessInterface; + this.logoutPresenter = logoutOutputBoundary; + } + + @Override + public void execute(LogoutInputData logoutInputData) { + final String username = logoutInputData.getUsername(); + final User currentUser = fileDataAccessObject.get(username); + fileDataAccessObject.save(currentUser); + fileDataAccessObject.setCurrentUsername(null); + final LogoutOutputData logoutOutputData = new LogoutOutputData(username, false); + logoutPresenter.prepareSuccessView(logoutOutputData); + } +} + diff --git a/src/main/java/use_case/logout/LogoutOutputBoundary.java b/src/main/java/use_case/logout/LogoutOutputBoundary.java new file mode 100644 index 000000000..935a06bdc --- /dev/null +++ b/src/main/java/use_case/logout/LogoutOutputBoundary.java @@ -0,0 +1,18 @@ +package use_case.logout; + +/** + * The output boundary for the Login Use Case. + */ +public interface LogoutOutputBoundary { + /** + * Prepares the success view for the Login Use Case. + * @param outputData the output data + */ + void prepareSuccessView(LogoutOutputData 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/logout/LogoutOutputData.java b/src/main/java/use_case/logout/LogoutOutputData.java new file mode 100644 index 000000000..9f8e20539 --- /dev/null +++ b/src/main/java/use_case/logout/LogoutOutputData.java @@ -0,0 +1,23 @@ +package use_case.logout; + +/** + * Output Data for the Logout Use Case. + */ +public class LogoutOutputData { + + private String username; + private boolean useCaseFailed; + + public LogoutOutputData(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/logout/LogoutUserDataAccessInterface.java b/src/main/java/use_case/logout/LogoutUserDataAccessInterface.java new file mode 100644 index 000000000..8263700e2 --- /dev/null +++ b/src/main/java/use_case/logout/LogoutUserDataAccessInterface.java @@ -0,0 +1,19 @@ +package use_case.logout; + +/** + * DAO for the Logout Use Case. + */ +public interface LogoutUserDataAccessInterface { + + /** + * Returns the username of the curren user of the application. + * @return the username of the current user + */ + String getCurrentUsername(); + + /** + * Sets the username indicating who is the current user of the application. + * @param username the new current username + */ + void setCurrentUsername(String username); +} diff --git a/src/main/java/use_case/logout/adapter/LogoutController.java b/src/main/java/use_case/logout/adapter/LogoutController.java new file mode 100644 index 000000000..304b0833c --- /dev/null +++ b/src/main/java/use_case/logout/adapter/LogoutController.java @@ -0,0 +1,27 @@ +package use_case.logout.adapter; + +import use_case.logout.LogoutInputBoundary; +import use_case.logout.LogoutInputData; + +/** + * The controller for the Logout Ue Case. + */ +public class LogoutController { + + private LogoutInputBoundary logoutUseCaseInteractor; + + public LogoutController(LogoutInputBoundary logoutUseCaseInteractor) { + this.logoutUseCaseInteractor = logoutUseCaseInteractor; + } + + /** + * Executes the Logout Use Case. + * @param username the username of the user logging in + */ + public void execute(String username) { + + final LogoutInputData logoutInputData = new LogoutInputData(username); + + logoutUseCaseInteractor.execute(logoutInputData); + } +} diff --git a/src/main/java/use_case/logout/adapter/LogoutPresenter.java b/src/main/java/use_case/logout/adapter/LogoutPresenter.java new file mode 100644 index 000000000..f28e1c40d --- /dev/null +++ b/src/main/java/use_case/logout/adapter/LogoutPresenter.java @@ -0,0 +1,50 @@ +package use_case.logout.adapter; + +import interface_adapter.ViewManagerModel; +import interface_adapter.change_password.LoggedInState; +import interface_adapter.change_password.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 Logout Use Case. + */ +public class LogoutPresenter implements LogoutOutputBoundary { + + private final LoggedInViewModel loggedInViewModel; + private final ViewManagerModel viewManagerModel; + private final LoginViewModel loginViewModel; + + public LogoutPresenter(ViewManagerModel viewManagerModel, + LoggedInViewModel loggedInViewModel, + LoginViewModel loginViewModel) { + this.loggedInViewModel = loggedInViewModel; + this.viewManagerModel = viewManagerModel; + this.loginViewModel = loginViewModel; + } + + @Override + public void prepareSuccessView(LogoutOutputData response) { + final LoggedInState loggedInState = loggedInViewModel.getState(); + loggedInState.setUsername(""); + loggedInViewModel.setState(loggedInState); + loggedInViewModel.firePropertyChanged(); + final LoginState loginState = loginViewModel.getState(); + loginState.setUsername(""); + loginState.setPassword(""); + loginViewModel.setState(loginState); + loginViewModel.firePropertyChanged(); + this.viewManagerModel.setState(loggedInViewModel.getViewName()); + this.viewManagerModel.firePropertyChanged(); + this.viewManagerModel.setState(loginViewModel.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/use_case/search/SearchDataAccessInterface.java b/src/main/java/use_case/search/SearchDataAccessInterface.java new file mode 100644 index 000000000..5372efdb6 --- /dev/null +++ b/src/main/java/use_case/search/SearchDataAccessInterface.java @@ -0,0 +1,10 @@ +package use_case.search; + +public interface SearchDataAccessInterface { + /** + * Search jokes with the keyword and show them. + * @param keyword the keyword that want to be searched. + * @return return a String of joke that we want + */ + String searchJoke(String keyword); +} diff --git a/src/main/java/use_case/search/SearchInputBoundary.java b/src/main/java/use_case/search/SearchInputBoundary.java new file mode 100755 index 000000000..3906769fe --- /dev/null +++ b/src/main/java/use_case/search/SearchInputBoundary.java @@ -0,0 +1,11 @@ +package use_case.search; + +public interface SearchInputBoundary { + /** + * Search jokes by keyword. + * @param keyword the keyword that wants to be searched. + */ + void executeSearch(String keyword); + + void switchToSearchView(); +} diff --git a/src/main/java/use_case/search/SearchInteractor.java b/src/main/java/use_case/search/SearchInteractor.java new file mode 100755 index 000000000..02ac4ff8f --- /dev/null +++ b/src/main/java/use_case/search/SearchInteractor.java @@ -0,0 +1,29 @@ +package use_case.search; + +public class SearchInteractor implements SearchInputBoundary { + private final SearchDataAccessInterface searchDataAccessObject; + private final SearchOutputBoundary searchPresenter; + + public SearchInteractor(SearchDataAccessInterface searchDataAccessObject, + SearchOutputBoundary searchPresenter) { + this.searchDataAccessObject = searchDataAccessObject; + this.searchPresenter = searchPresenter; + } + + @Override + public void executeSearch(String keyword) { + final String jokeContent = searchDataAccessObject.searchJoke(keyword); + final SearchOutputData searchOutputData = new SearchOutputData(jokeContent); + if (searchOutputData.getError()) { + final String message = "failed to find a joke"; + searchPresenter.prepareFailureView(message); + } + else { + searchPresenter.prepareSuccessView(searchOutputData); + } + } + + public void switchToSearchView() { + searchPresenter.switchToSearchView(); + } +} diff --git a/src/main/java/use_case/search/SearchOutputBoundary.java b/src/main/java/use_case/search/SearchOutputBoundary.java new file mode 100755 index 000000000..6c05c7e49 --- /dev/null +++ b/src/main/java/use_case/search/SearchOutputBoundary.java @@ -0,0 +1,18 @@ +package use_case.search; + +public interface SearchOutputBoundary { + /** + * Prepares the success view. + * @param jokeContent the joke we want to present. + */ + void prepareSuccessView(SearchOutputData jokeContent); + + /** + * Prepares the failure view. + * @param errormessage a message when fails. + */ + void prepareFailureView(String errormessage); + // How to determine what is failure or success, depends on the format of API return_value + + void switchToSearchView(); +} 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..6516c0e40 --- /dev/null +++ b/src/main/java/use_case/search/SearchOutputData.java @@ -0,0 +1,25 @@ +package use_case.search; + +import org.json.JSONObject; + +public class SearchOutputData { + + private final String jokeContent; + + public SearchOutputData(String jokeContent) { + this.jokeContent = jokeContent; + } + + public String getJokeContent() { + return jokeContent; + } + + public Boolean getError() { + if ("No matching joke found".equals(jokeContent)) { + return Boolean.TRUE; + } + else { + return Boolean.FALSE; + } + } +} \ No newline at end of file diff --git a/src/main/java/use_case/search/adapter/SearchController.java b/src/main/java/use_case/search/adapter/SearchController.java new file mode 100755 index 000000000..ca2465118 --- /dev/null +++ b/src/main/java/use_case/search/adapter/SearchController.java @@ -0,0 +1,20 @@ +package use_case.search.adapter; + +import use_case.search.SearchInputBoundary; + +public class SearchController { + + private final SearchInputBoundary searchInputBoundary; + + public SearchController(SearchInputBoundary searchInteractor) { + this.searchInputBoundary = searchInteractor; + } + + public void execute(String keywords) { + searchInputBoundary.executeSearch(keywords); + } + + public void switchtoSearchView() { + searchInputBoundary.switchToSearchView(); + } +} \ No newline at end of file diff --git a/src/main/java/use_case/search/adapter/SearchPresenter.java b/src/main/java/use_case/search/adapter/SearchPresenter.java new file mode 100755 index 000000000..3ac036475 --- /dev/null +++ b/src/main/java/use_case/search/adapter/SearchPresenter.java @@ -0,0 +1,54 @@ +package use_case.search.adapter; + +import data_access.FileDataAccessObject; +import interface_adapter.ViewManagerModel; +import use_case.search.SearchOutputBoundary; +import use_case.search.SearchOutputData; +import view.joke_view.JokeFrameBuilder; +import visitor.Visitor; + +import javax.swing.*; + +public class SearchPresenter implements SearchOutputBoundary { + + private final Visitor searchVisitor; + private final JokeFrameBuilder jokeFrameBuilder; + private final ViewManagerModel viewManagerModel; + private final SearchViewModel searchViewModel; + private final FileDataAccessObject fileDataAccessObject; + + public SearchPresenter(SearchViewModel searchViewModel, + ViewManagerModel viewManagerModel, + FileDataAccessObject fileDataAccessObject, + JokeFrameBuilder jokeFrameBuilder) { + this.fileDataAccessObject = fileDataAccessObject; + this.searchVisitor = new Visitor(fileDataAccessObject); + this.jokeFrameBuilder = jokeFrameBuilder; + this.viewManagerModel = viewManagerModel; + this.searchViewModel = searchViewModel; + } + + @Override + public void prepareSuccessView(SearchOutputData searchOutputData) { + searchVisitor.visit(searchOutputData); + } + + @Override + public void prepareFailureView(String errormessage) { + final JFrame frame = jokeFrameBuilder + .addJokeView() + .setJokeContent(errormessage) + .addExplanationUseCase() + .addAddToFavUseCase(fileDataAccessObject) + .build(); + + frame.pack(); + frame.setVisible(true); + } + + @Override + public void switchToSearchView() { + viewManagerModel.setState(searchViewModel.getViewName()); + viewManagerModel.firePropertyChanged(); + } +} \ No newline at end of file diff --git a/src/main/java/use_case/search/adapter/SearchState.java b/src/main/java/use_case/search/adapter/SearchState.java new file mode 100644 index 000000000..20be341a1 --- /dev/null +++ b/src/main/java/use_case/search/adapter/SearchState.java @@ -0,0 +1,21 @@ +package use_case.search.adapter; + +public class SearchState { + // info that can change + private String keyWord = ""; + + @Override + public String toString() { + return "SearchState{" + + "keyWord='" + getKeyWord() + '\'' + + '}'; + } + + public String getKeyWord() { + return keyWord; + } + + public void setKeyWord(String keyWord) { + this.keyWord = keyWord; + } +} \ No newline at end of file diff --git a/src/main/java/use_case/search/adapter/SearchViewModel.java b/src/main/java/use_case/search/adapter/SearchViewModel.java new file mode 100644 index 000000000..6565ea22a --- /dev/null +++ b/src/main/java/use_case/search/adapter/SearchViewModel.java @@ -0,0 +1,16 @@ +package use_case.search.adapter; + +import view.ViewModel; + +public class SearchViewModel extends ViewModel { + + public static final String TITLE_LABEL = "Search"; + public static final String SEARCH_LABEL = "Enter keyword to find a joke:"; + public static final String SEARCH_BUTTON_LABEL = "Search"; + public static final String CANCEL_BUTTON_LABEL = "Cancel"; + + public SearchViewModel() { + super("Search"); + setState(new SearchState()); + } +} diff --git a/src/main/java/use_case/search_favourites/SearchFavouritesInteractor.java b/src/main/java/use_case/search_favourites/SearchFavouritesInteractor.java new file mode 100644 index 000000000..f5502b7d6 --- /dev/null +++ b/src/main/java/use_case/search_favourites/SearchFavouritesInteractor.java @@ -0,0 +1,41 @@ +package use_case.search_favourites; + +import entity.Joke; + +import java.util.List; +import java.util.stream.Collectors; + +public class SearchFavouritesInteractor implements SearchFavouritesInputBoundary { + private final SearchFavouritesDataAccessInterface dataAccess; + private final SearchFavouritesOutputBoundary presenter; + + public SearchFavouritesInteractor(SearchFavouritesDataAccessInterface dataAccess, + SearchFavouritesOutputBoundary presenter) { + this.dataAccess = dataAccess; + this.presenter = presenter; + } + + @Override + public void executeSearch(String keyword) { + try { + final List favourites = dataAccess.getFavourites(); + final List matchingJokes = favourites.stream() + .filter(joke -> joke.getContent().toLowerCase().contains(keyword.toLowerCase())) + .collect(Collectors.toList()); + + if (matchingJokes.isEmpty()) { + presenter.presentFailure("No matching jokes found in favourites."); + } + else { + final Joke selectedJoke = matchingJokes.get(0); + final SearchFavouritesOutputData outputData = new SearchFavouritesOutputData( + selectedJoke.getContent() + ); + presenter.prepareSuccessView(outputData); + } + } catch (RuntimeException e) { + presenter.presentFailure("Error searching favourites: " + e.getMessage()); + } + } + +} diff --git a/src/main/java/use_case/signup/SignupInputBoundary.java b/src/main/java/use_case/signup/SignupInputBoundary.java new file mode 100755 index 000000000..a0e249275 --- /dev/null +++ b/src/main/java/use_case/signup/SignupInputBoundary.java @@ -0,0 +1,18 @@ +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 new file mode 100644 index 000000000..86c5e8abc --- /dev/null +++ b/src/main/java/use_case/signup/SignupInputData.java @@ -0,0 +1,29 @@ +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 new file mode 100755 index 000000000..5db12d60b --- /dev/null +++ b/src/main/java/use_case/signup/SignupInteractor.java @@ -0,0 +1,43 @@ +package use_case.signup; + +import entity.User; +import entity.UserFactory; + +/** + * The Signup Interactor. + */ +public class SignupInteractor implements SignupInputBoundary { + private final SignupUserDataAccessInterface fileDataAccessObject; + private final SignupOutputBoundary userPresenter; + private final UserFactory userFactory; + + public SignupInteractor(SignupUserDataAccessInterface signupDataAccessInterface, + SignupOutputBoundary signupOutputBoundary, + UserFactory userFactory) { + this.fileDataAccessObject = signupDataAccessInterface; + this.userPresenter = signupOutputBoundary; + this.userFactory = userFactory; + } + + @Override + public void execute(SignupInputData signupInputData) { + if (fileDataAccessObject.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()); + fileDataAccessObject.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 new file mode 100755 index 000000000..f4859555a --- /dev/null +++ b/src/main/java/use_case/signup/SignupOutputBoundary.java @@ -0,0 +1,24 @@ +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 new file mode 100644 index 000000000..6dc74d2fb --- /dev/null +++ b/src/main/java/use_case/signup/SignupOutputData.java @@ -0,0 +1,24 @@ +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 new file mode 100644 index 000000000..b9d60f585 --- /dev/null +++ b/src/main/java/use_case/signup/SignupUserDataAccessInterface.java @@ -0,0 +1,22 @@ +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/signup/adapter/SignupController.java b/src/main/java/use_case/signup/adapter/SignupController.java new file mode 100755 index 000000000..e035e11a3 --- /dev/null +++ b/src/main/java/use_case/signup/adapter/SignupController.java @@ -0,0 +1,36 @@ +package use_case.signup.adapter; + +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/use_case/signup/adapter/SignupPresenter.java b/src/main/java/use_case/signup/adapter/SignupPresenter.java new file mode 100755 index 000000000..dfce4d45c --- /dev/null +++ b/src/main/java/use_case/signup/adapter/SignupPresenter.java @@ -0,0 +1,56 @@ +package use_case.signup.adapter; + +import interface_adapter.ViewManagerModel; +import use_case.login.LoginState; +import view.login.LoginViewModel; +import use_case.search.adapter.SearchViewModel; +import use_case.signup.SignupOutputBoundary; +import use_case.signup.SignupOutputData; +import view.signup.SignupState; +import view.signup.SignupViewModel; + +/** + * The Presenter for the Signup Use Case. + */ +public class SignupPresenter implements SignupOutputBoundary { + + private final SignupViewModel signupViewModel; + private final LoginViewModel loginViewModel; + private final SearchViewModel searchViewModel; + private final ViewManagerModel viewManagerModel; + + public SignupPresenter(ViewManagerModel viewManagerModel, + SignupViewModel signupViewModel, + LoginViewModel loginViewModel, + SearchViewModel searchViewModel) { + this.viewManagerModel = viewManagerModel; + this.signupViewModel = signupViewModel; + this.loginViewModel = loginViewModel; + this.searchViewModel = searchViewModel; + } + + @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/view/FavoritePanel.java b/src/main/java/view/FavoritePanel.java new file mode 100644 index 000000000..aaf7a324f --- /dev/null +++ b/src/main/java/view/FavoritePanel.java @@ -0,0 +1,135 @@ +package view; + +import java.awt.BorderLayout; +import java.awt.FlowLayout; +import java.awt.event.ActionListener; + +import javax.swing.DefaultListModel; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextField; +import javax.swing.ListSelectionModel; + +/** + * A FavoritePanel that includes a scrollable list of favorite jokes. + */ +public class FavoritePanel extends JPanel { + + // UI Components + private final JTextField keywordField = new JTextField(20); // Text field to enter a keyword + private final JButton searchButton = new JButton("Search"); + private final JButton funniestButton = new JButton("Funniest"); + private final JButton cancelButton = new JButton("Cancel"); + private final DefaultListModel jokeListModel = new DefaultListModel<>(); + private final JList jokeList = new JList<>(jokeListModel); // Scrollable joke list + private final JScrollPane scrollPane = new JScrollPane(jokeList); // Scroll pane for the list + private final JButton goToJokePageButton = new JButton("Go to Joke Page"); + private final JButton goToLoginPageButton = new JButton("Go to Login Page"); + + public FavoritePanel() { + // Set layout + setLayout(new BorderLayout()); + + // Top Panel: Search bar and action buttons + final JPanel topPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + topPanel.add(new JLabel("Enter Keyword:")); + topPanel.add(keywordField); + topPanel.add(searchButton); + topPanel.add(funniestButton); + topPanel.add(cancelButton); + add(topPanel, BorderLayout.NORTH); + + // Main Content: Scrollable list of favorite jokes + jokeList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); + add(scrollPane, BorderLayout.CENTER); + + // Bottom Panel: Navigation buttons + final JPanel bottomPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); + bottomPanel.add(goToJokePageButton); + bottomPanel.add(goToLoginPageButton); + add(bottomPanel, BorderLayout.SOUTH); + + // Populate with dummy data for demonstration + populateDummyData(); + } + + /** + * Populates the joke list with dummy data for testing. + * Remove this method in production. + */ + private void populateDummyData() { + for (int i = 1; i <= 50; i++) { + jokeListModel.addElement("Favorite Joke " + i); + } + } + + /** + * Updates the joke list with the provided list of jokes. + * + * @param jokes the list of jokes to display + */ + public void updateJokeList(java.util.List jokes) { + jokeListModel.clear(); + for (String joke : jokes) { + jokeListModel.addElement(joke); + } + } + + /** + * Gets the keyword entered in the search field. + * + * @return the entered keyword + */ + public String getEnteredKeyword() { + return keywordField.getText().trim(); + } + + /** + * Sets the ActionListener for the search button. + * + * @param listener the ActionListener for the search button + */ + public void setSearchButtonListener(ActionListener listener) { + searchButton.addActionListener(listener); + } + + /** + * Sets the ActionListener for the funniest button. + * + * @param listener the ActionListener for the funniest button + */ + public void setFunniestButtonListener(ActionListener listener) { + funniestButton.addActionListener(listener); + } + + /** + * Sets the ActionListener for the cancel button. + * + * @param listener the ActionListener for the cancel button + */ + public void setCancelButtonListener(ActionListener listener) { + cancelButton.addActionListener(listener); + } + + /** + * Sets the ActionListener for the "Go to Joke Page" button. + * + * @param listener the ActionListener for the navigation button + */ + public void setGoToJokePageButtonListener(ActionListener listener) { + goToJokePageButton.addActionListener(listener); + } + + /** + * Sets the ActionListener for the "Go to Login Page" button. + * + * @param listener the ActionListener for the navigation button + */ + public void setGoToLoginPageButtonListener(ActionListener listener) { + goToLoginPageButton.addActionListener(listener); + } +} diff --git a/src/main/java/view/LabelTextPanel.java b/src/main/java/view/LabelTextPanel.java new file mode 100644 index 000000000..59a0cef79 --- /dev/null +++ b/src/main/java/view/LabelTextPanel.java @@ -0,0 +1,15 @@ +package view; + +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +/** + * A panel containing a label and a text field. + */ +public class LabelTextPanel extends JPanel { + public LabelTextPanel(JLabel label, JTextField textField) { + this.add(label); + this.add(textField); + } +} \ No newline at end of file diff --git a/src/main/java/view/NoteView.java b/src/main/java/view/NoteView.java index 331d76493..e83522bd5 100644 --- a/src/main/java/view/NoteView.java +++ b/src/main/java/view/NoteView.java @@ -1,95 +1,103 @@ -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.JTextArea; - -import interface_adapter.note.NoteController; -import interface_adapter.note.NoteState; -import interface_adapter.note.NoteViewModel; - -/** - * The View for when the user is viewing a note in the program. - */ -public class NoteView extends JPanel implements ActionListener, PropertyChangeListener { - - private final NoteViewModel noteViewModel; - - private final JLabel noteName = new JLabel("note for jonathan_calver2"); - private final JTextArea noteInputField = new JTextArea(); - - private final JButton saveButton = new JButton("Save"); - private final JButton refreshButton = new JButton("Refresh"); - private NoteController noteController; - - public NoteView(NoteViewModel noteViewModel) { - - noteName.setAlignmentX(Component.CENTER_ALIGNMENT); - this.noteViewModel = noteViewModel; - this.noteViewModel.addPropertyChangeListener(this); - - final JPanel buttons = new JPanel(); - buttons.add(saveButton); - buttons.add(refreshButton); - - saveButton.addActionListener( - evt -> { - if (evt.getSource().equals(saveButton)) { - noteController.execute(noteInputField.getText()); - - } - } - ); - - refreshButton.addActionListener( - evt -> { - if (evt.getSource().equals(refreshButton)) { - noteController.execute(null); - - } - } - ); - - this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); - - this.add(noteName); - this.add(noteInputField); - this.add(buttons); - } - - /** - * 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 NoteState state = (NoteState) evt.getNewValue(); - setFields(state); - if (state.getError() != null) { - JOptionPane.showMessageDialog(this, state.getError(), - "Error", JOptionPane.ERROR_MESSAGE); - } - } - - private void setFields(NoteState state) { - noteInputField.setText(state.getNote()); - } - - public void setNoteController(NoteController controller) { - this.noteController = controller; - } -} - +//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 interface_adapter.joke.JokeController; +//import interface_adapter.joke.JokeState; +//import interface_adapter.joke.JokeViewModel; +// +///** +// * The View for displaying jokes and interacting with the Joke Application. +// */ +//public class JokeAppView extends JPanel implements ActionListener, PropertyChangeListener { +// +// private final String viewName = "joke view"; +// private final JokeViewModel jokeViewModel; +// +// private final JLabel userIdLabel = new JLabel("User ID: "); +// private final JLabel jokeDisplayArea = new JLabel("Your joke will appear here"); +// private final JButton generateJokeButton = new JButton("Generate Joke"); +// private final JButton searchJokeButton = new JButton("Search Joke"); +// private final JButton favoriteJokeButton = new JButton("Favorite Joke"); +// private JokeController jokeController; +// +// public JokeAppView(JokeViewModel jokeViewModel) { +// this.jokeViewModel = jokeViewModel; +// this.jokeViewModel.addPropertyChangeListener(this); +// +// setupButtons(); +// +// this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); +// this.add(title); +// this.add(userIdLabel); +// this.add(jokeDisplayArea); +// this.add(generateJokeButton); +// this.add(searchJokeButton); +// this.add(favoriteJokeButton); +// } +// /** +// * Configures the actions for each button. +// */ +// private void setupButtons() { +// // Generate Joke Action +// generateJokeButton.addActionListener(e -> jokeController.execute("Generate a joke", "")); +// +// // Search Joke Action +// searchJokeButton.addActionListener(e -> { +// String query = JOptionPane.showInputDialog(this, "Search for a joke:"); +// if (query != null && !query.trim().isEmpty()) { +// jokeController.execute("search", query); +// } +// }); +// favoriteJokeButton.addActionListener(e -> jokeController.execute("Favourite", "")); +// } +// +// /** +// * React to a button click that results in evt. +// * @param evt the ActionEvent to react to +// */ +// @Override +// public void actionPerformed(ActionEvent evt) { +// System.out.println("Click " + evt.getActionCommand()); +// } +// +// /** +// * Handles updates from the JokeViewModel, updating the UI display accordingly. +// */ +// @Override +// public void propertyChange(PropertyChangeEvent evt) { +// if ("state".equals(evt.getPropertyName()) && evt.getNewValue() instanceof JokeState) { +// JokeState jokeState = (JokeState) evt.getNewValue(); +// updateFields(jokeState); +// } else if ("error".equals(evt.getPropertyName())) { +// JOptionPane.showMessageDialog(this, evt.getNewValue().toString(), "Error", JOptionPane.ERROR_MESSAGE); +// } +// } +// +// /** +// * Updates the UI fields based on the current state of JokeState +// * @param state the current JokeState +// */ +// private void updateFields(JokeState state) { +// jokeDisplayArea.setText(state.getJokeText()); +// favoriteJokeButton.setText(state.isFavorite() ? "Unfavourite Joke" : "Favorite Joke"); +// userIdLabel.setText("User ID: " + state.getErrorMessage()); +// } +// +// public String getViewName() { +// return viewName; +// } +// +// public void setJokeController(JokeController jokeController) { +// this.jokeController = jokeController; +// } +//} diff --git a/src/main/java/view/SearchView.java b/src/main/java/view/SearchView.java new file mode 100644 index 000000000..186ed7767 --- /dev/null +++ b/src/main/java/view/SearchView.java @@ -0,0 +1,82 @@ +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.JPanel; +import javax.swing.JTextField; + +import use_case.search.adapter.SearchController; +import use_case.search.adapter.SearchViewModel; + +public class SearchView extends JPanel implements ActionListener, PropertyChangeListener { + + private final String viewName; + private final SearchViewModel searchViewModel; + + private final JLabel searchLabel; + private final JTextField keywordInputField = new JTextField(15); + + private final JButton searchButton; + private final JButton cancelButton; + private SearchController searchController; + + public SearchView(SearchViewModel searchViewModel) { + this.searchViewModel = searchViewModel; + this.viewName = searchViewModel.getViewName(); + this.searchViewModel.addPropertyChangeListener(this); + + final JLabel title = new JLabel(SearchViewModel.TITLE_LABEL); + title.setAlignmentX(Component.CENTER_ALIGNMENT); + + searchLabel = new JLabel(SearchViewModel.SEARCH_LABEL); + final LabelTextPanel searchBox = new LabelTextPanel(searchLabel, this.keywordInputField); + + final JPanel buttons = new JPanel(); + searchButton = new JButton(SearchViewModel.SEARCH_BUTTON_LABEL); + buttons.add(searchButton); + cancelButton = new JButton(SearchViewModel.CANCEL_BUTTON_LABEL); + buttons.add(cancelButton); + + searchButton.addActionListener( + evt -> { + if (evt.getSource().equals(searchButton)) { + searchController.execute(keywordInputField.getText()); + } + } + ); + + cancelButton.addActionListener(this); + + this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); + + this.add(title); + this.add(searchBox); + this.add(buttons); + } + + @Override + public void actionPerformed(ActionEvent e) { + + } + + @Override + public void propertyChange(PropertyChangeEvent evt) { + + } + + public void setSearchController(SearchController searchController) { + this.searchController = searchController; + } + + public String getViewName() { + return viewName; + } +} + diff --git a/src/main/java/view/ViewManager.java b/src/main/java/view/ViewManager.java new file mode 100644 index 000000000..0b3a63ae9 --- /dev/null +++ b/src/main/java/view/ViewManager.java @@ -0,0 +1,33 @@ +package view; + +import interface_adapter.ViewManagerModel; + +import javax.swing.*; +import java.awt.*; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +/** + * The View Manager for the program. It listens for property change events + * in the ViewManagerModel and updates which View should be visible. + */ +public class ViewManager implements PropertyChangeListener { + private final CardLayout cardLayout; + private final JPanel views; + private final ViewManagerModel viewManagerModel; + + public ViewManager(JPanel views, CardLayout cardLayout, ViewManagerModel viewManagerModel) { + this.views = views; + this.cardLayout = cardLayout; + this.viewManagerModel = viewManagerModel; + this.viewManagerModel.addPropertyChangeListener(this); + } + + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (evt.getPropertyName().equals("state")) { + final String viewModelName = (String) evt.getNewValue(); + cardLayout.show(views, viewModelName); + } + } +} diff --git a/src/main/java/view/ViewManagerModel.java b/src/main/java/view/ViewManagerModel.java new file mode 100644 index 000000000..6fac7568c --- /dev/null +++ b/src/main/java/view/ViewManagerModel.java @@ -0,0 +1,14 @@ +package view; + +/** + * Model for the View Manager. Its state is the name of the View which + * is currently active. An initial state of "" is used. + */ +public class ViewManagerModel extends ViewModel { + + public ViewManagerModel() { + super("view manager"); + this.setState(""); + } + +} diff --git a/src/main/java/view/ViewModel.java b/src/main/java/view/ViewModel.java new file mode 100644 index 000000000..136b800f8 --- /dev/null +++ b/src/main/java/view/ViewModel.java @@ -0,0 +1,63 @@ +package view; + +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; + +/** + * The ViewModel for our CA implementation. + * This class delegates work to a PropertyChangeSupport object for + * managing the property change events. + * + * @param The type of state object contained in the model. + */ +public class ViewModel { + + private final String viewName; + + private final PropertyChangeSupport support = new PropertyChangeSupport(this); + + private T state; + + public ViewModel(String viewName) { + this.viewName = viewName; + } + + public String getViewName() { + return this.viewName; + } + + public T getState() { + return this.state; + } + + public void setState(T state) { + this.state = state; + } + + /** + * Fires a property changed event for the state of this ViewModel. + */ + public void firePropertyChanged() { + this.support.firePropertyChange("state", null, this.state); + } + + /** + * Fires a property changed event for the state of this ViewModel, which + * allows the user to specify a different propertyName. This can be useful + * when a class is listening for multiple kinds of property changes. + *

For example, the LoggedInView listens for two kinds of property changes; + * it can use the property name to distinguish which property has changed.

+ * @param propertyName the label for the property that was changed + */ + public void firePropertyChanged(String propertyName) { + this.support.firePropertyChange(propertyName, null, this.state); + } + + /** + * Adds a PropertyChangeListener to this ViewModel. + * @param listener The PropertyChangeListener to be added + */ + public void addPropertyChangeListener(PropertyChangeListener listener) { + this.support.addPropertyChangeListener(listener); + } +} diff --git a/src/main/java/view/favourite_view/FavouriteState.java b/src/main/java/view/favourite_view/FavouriteState.java new file mode 100644 index 000000000..415f46351 --- /dev/null +++ b/src/main/java/view/favourite_view/FavouriteState.java @@ -0,0 +1,28 @@ +package view.favourite_view; + +import java.util.List; + +import entity.Joke; +import entity.User; + +public class FavouriteState { + private String keyWord; + private User user; + private List favourites; + + public List getFavourites() { + return favourites; + } + + public void setFavourites(List favourites) { + this.favourites = favourites; + } + + public String getKeyWord() { + return keyWord; + } + + public void setKeyWord(String keyWord) { + this.keyWord = keyWord; + } +} diff --git a/src/main/java/view/favourite_view/FavouriteView.java b/src/main/java/view/favourite_view/FavouriteView.java new file mode 100644 index 000000000..d36305622 --- /dev/null +++ b/src/main/java/view/favourite_view/FavouriteView.java @@ -0,0 +1,109 @@ +package view.favourite_view; + +import entity.Joke; +import use_case.fav_search.adapter.FavSearchController; +import use_case.funniest.adapter.FunniestController; +import view.LabelTextPanel; + +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.List; + +import javax.swing.*; + +import use_case.favourite.adapter.FavouriteController; + +public class FavouriteView extends JPanel implements ActionListener, PropertyChangeListener { + + private final FavouriteViewModel favouriteViewModel; + + private final String viewName; + private FavouriteView favouriteView; + + private FavouriteController favouriteController; + private FunniestController funniestController; + private FavSearchController favSearchController; + + private final JTextField searchBox = new JTextField(15); + private final JButton searchButton; + private final JButton funniestButton; + private final JButton cancelButton; + + public FavouriteView(FavouriteViewModel favouriteViewModel) { + this.favouriteViewModel = favouriteViewModel; + this.viewName = favouriteViewModel.getViewName(); + this.favouriteViewModel.addPropertyChangeListener(this); + + final JLabel title = new JLabel(FavouriteViewModel.TITLE_LABEL); + title.setAlignmentX(Component.CENTER_ALIGNMENT); + + final LabelTextPanel search = new LabelTextPanel(new JLabel(FavouriteViewModel.KEYWORD_LABEL), searchBox); + + final JPanel buttons = new JPanel(); + searchButton = new JButton(FavouriteViewModel.SEARCH_BUTTOM_LABEL); + buttons.add(searchButton); + funniestButton = new JButton(FavouriteViewModel.FUNNIEST_BUTTOM_LABEL); + buttons.add(funniestButton); + cancelButton = new JButton(FavouriteViewModel.CANCEL_BUTTOM_LABEL); + buttons.add(cancelButton); + buttons.add(search); + + final JPanel jokeListPanel = new JPanel(); + final List fav = favouriteViewModel.getState().getFavourites(); + for (Joke joke : fav) { + final String content = joke.getContent(); + jokeListPanel.add(new JLabel(content)); + } + + final JSplitPane mainPanel = new JSplitPane(JSplitPane.VERTICAL_SPLIT); + + mainPanel.setLeftComponent(buttons); + mainPanel.setRightComponent(jokeListPanel); + + this.add(title); + this.add(mainPanel); + + funniestButton.addActionListener( + evt -> { + if (evt.getSource().equals(funniestButton)) { + funniestController.execute(fav); + } + } + ); + + cancelButton.addActionListener(this); + + searchButton.addActionListener( + evt -> { + if (evt.getSource().equals(searchButton)) { + favSearchController.executeSearch(searchBox.getText()); + } + } + ); + } + + public void setFavouriteController(FavouriteController controller) { + this.favouriteController = controller; + } + + public void setFunniestController(FunniestController controller) { + this.funniestController = controller; + } + + public void setFavSearchController(FavSearchController controller) { + this.favSearchController = controller; + } + + @Override + public void actionPerformed(ActionEvent e) { + + } + + @Override + public void propertyChange(PropertyChangeEvent evt) { + + } +} \ No newline at end of file diff --git a/src/main/java/view/favourite_view/FavouriteViewModel.java b/src/main/java/view/favourite_view/FavouriteViewModel.java new file mode 100644 index 000000000..565a3681e --- /dev/null +++ b/src/main/java/view/favourite_view/FavouriteViewModel.java @@ -0,0 +1,18 @@ +package view.favourite_view; + +import view.ViewModel; + +public class FavouriteViewModel extends ViewModel { + + public static final String TITLE_LABEL = "Favourite"; + public static final String KEYWORD_LABEL = "Enter keyword to find a joke:"; + public static final String SEARCH_BUTTOM_LABEL = "search"; + public static final String FUNNIEST_BUTTOM_LABEL = "funniest"; + public static final String CANCEL_BUTTOM_LABEL = "cancel"; + + public FavouriteViewModel() { + super("Favourite"); + setState(new FavouriteState()); + } + +} diff --git a/src/main/java/view/joke_view/JokeFrameBuilder.java b/src/main/java/view/joke_view/JokeFrameBuilder.java new file mode 100644 index 000000000..7591726ba --- /dev/null +++ b/src/main/java/view/joke_view/JokeFrameBuilder.java @@ -0,0 +1,82 @@ +package view.joke_view; + +import data_access.ExplanationDataAccessObject; +import data_access.FileDataAccessObject; +import data_access.MockExplanationDataAccessObject; +import use_case.add_to_fav.AddToFavDataAccessInterface; +import use_case.add_to_fav.AddToFavInputBoundary; +import use_case.add_to_fav.AddToFavInteractor; +import use_case.add_to_fav.AddToFavOutputBoundary; +import use_case.add_to_fav.adapter.AddToFavController; +import use_case.add_to_fav.adapter.AddToFavPresenter; +import use_case.explanation.*; +import use_case.explanation.adapter.ExplanationController; +import use_case.explanation.adapter.ExplanationPresenter; + +import javax.swing.*; + +public class JokeFrameBuilder { +// public static final int HEIGHT = 300; +// public static final int WIDTH = 400; + // joke/user factory made before +// private final JokeFactory jokeFactory = new JokeFactory(); + + private JokeView jokeView; + private JokeViewModel jokeViewModel; + // view doesn't change, so don't need ViewManager(cardPanel, cardLayout, viewManagerModel); + + //TODO change mock + private final ExplanationDataAccessInterface explanationDataAccessObject = new MockExplanationDataAccessObject(); + private AddToFavDataAccessInterface addToFavDataAccessObject; + + public JokeFrameBuilder() { + } + + public JokeFrameBuilder addJokeView() { + jokeViewModel = new JokeViewModel(); + jokeView = new JokeView(jokeViewModel); + return this; + } + + public JokeFrameBuilder setJokeContent(String content) { + jokeViewModel.getState().setJokeContent(content); + jokeViewModel.firePropertyChanged(); + return this; + } + + public JokeFrameBuilder setExplanation(String explanation) { + jokeViewModel.getState().setExplanation(explanation); + jokeViewModel.firePropertyChanged(); + return this; + } + + public JokeFrameBuilder addExplanationUseCase() { + final ExplanationOutputBoundary explanationOutputBoundary = new ExplanationPresenter(jokeViewModel); + final ExplanationInputBoundary explanationInteractor = new ExplanationInteractor( + explanationDataAccessObject, explanationOutputBoundary); + + final ExplanationController explanationController = new ExplanationController(explanationInteractor); + jokeView.setExplanationController(explanationController); + return this; + } + + public JokeFrameBuilder addAddToFavUseCase(AddToFavDataAccessInterface addToFavDataAccessObject) { + final AddToFavOutputBoundary addToFavOutputBoundary = new AddToFavPresenter(jokeViewModel); + final AddToFavInputBoundary addToFavInteractor = new AddToFavInteractor( + addToFavDataAccessObject, addToFavOutputBoundary); + + final AddToFavController addToFavController = new AddToFavController(addToFavInteractor); + jokeView.setAddController(addToFavController); + return this; + } + + public JFrame build() { + final JFrame frame = new JFrame("Joke"); + + frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); + + frame.add(jokeView); + + return frame; + } +} diff --git a/src/main/java/view/joke_view/JokeState.java b/src/main/java/view/joke_view/JokeState.java new file mode 100644 index 000000000..34ed52eb8 --- /dev/null +++ b/src/main/java/view/joke_view/JokeState.java @@ -0,0 +1,41 @@ +package view.joke_view; + +import static view.joke_view.JokeViewModel.ADD_BUTTON_LABEL; + +public class JokeState { + // info that can change + private String jokeContent = ""; + private String explanation = ""; + private String addToFav = ADD_BUTTON_LABEL; + + @Override + public String toString() { + return "JokeState{" + + "jokeContent='" + getJokeContent() + '\'' + + ", explanation='" + getExplanation() + '\'' + + '}'; + } + + public String getExplanation() { + return explanation; + } + + public void setExplanation(String explanation) { + this.explanation = explanation; + } + + public String getJokeContent() { + return jokeContent; + } + + public void setJokeContent(String jokeContent) { + this.jokeContent = jokeContent; + } + + public void setAddToFavText(String added) { + this.addToFav = added; + } + + public String getAddToFav(){ + return this.addToFav; + } diff --git a/src/main/java/view/joke_view/JokeView.java b/src/main/java/view/joke_view/JokeView.java new file mode 100644 index 000000000..33bdeb6e9 --- /dev/null +++ b/src/main/java/view/joke_view/JokeView.java @@ -0,0 +1,165 @@ +package view.joke_view; + +import use_case.add_to_fav.adapter.AddToFavController; +import use_case.explanation.adapter.ExplanationController; + +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +import javax.swing.*; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; + +public class JokeView extends JPanel implements PropertyChangeListener, ActionListener { + + private final String viewName; + private final JokeViewModel jokeViewModel; + + private final JTextArea jokeContent = new JTextArea(3, 20); + private final JTextArea explanation = new JTextArea(10, 20); + + // Controllers + // each function of a controller is a button + private final JButton explain; + private final JButton addToFav; + private AddToFavController addController; + private ExplanationController explanationController; + + public JokeView(JokeViewModel jokeViewModel) { + + this.jokeViewModel = jokeViewModel; + this.jokeViewModel.addPropertyChangeListener(this); + this.viewName = jokeViewModel.getViewName(); + + final JLabel title = new JLabel(JokeViewModel.TITLE_LABEL); + title.setAlignmentX(Component.CENTER_ALIGNMENT); + final JLabel explanationLabel = new JLabel(JokeViewModel.EXPLANATION_LABEL); + explanationLabel.setAlignmentX(Component.CENTER_ALIGNMENT); + + final JPanel buttons = new JPanel(); + explain = new JButton(JokeViewModel.EXPLANATION_BUTTON_LABEL); + buttons.add(explain); + addToFav = new JButton(JokeViewModel.ADD_BUTTON_LABEL); + buttons.add(addToFav); + + explain.addActionListener( + evt -> { + if (evt.getSource().equals(explain)) { + final JokeState currentState = jokeViewModel.getState(); + + explanationController.execute( + currentState.getJokeContent() + ); + } + } + ); + + addToFav.addActionListener( + evt -> { + if (evt.getSource().equals(addToFav)) { + final JokeState currentState = jokeViewModel.getState(); + //can change depending on how addController is implemented + addController.execute( + currentState.getJokeContent(), + currentState.getExplanation()); + } + } + ); + + // a smaller (than view) observer pattern + jokeContent.getDocument().addDocumentListener(new DocumentListener() { + + private void documentListenerHelper() { + final JokeState currentState = jokeViewModel.getState(); + currentState.setJokeContent(jokeContent.getText()); + jokeViewModel.setState(currentState); + } + + @Override + public void insertUpdate(DocumentEvent e) { + documentListenerHelper(); + } + + @Override + public void removeUpdate(DocumentEvent e) { + documentListenerHelper(); + } + + @Override + public void changedUpdate(DocumentEvent e) { + documentListenerHelper(); + } + }); + + this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); + + explanation.getDocument().addDocumentListener(new DocumentListener() { + + private void documentListenerHelper() { + final JokeState currentState = jokeViewModel.getState(); + currentState.setExplanation(explanation.getText()); + jokeViewModel.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(jokeContent); + this.add(explanationLabel); + this.add(explanation); + this.add(buttons); + } + + /** + * 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 JokeState state = (JokeState) evt.getNewValue(); + setFields(state); + changeAddToFavText(state); + } + + private void setFields(JokeState state) { + jokeContent.setText(state.getJokeContent()); + explanation.setText(state.getExplanation()); + } + + public String getViewName() { + return viewName; + } + + public void setExplanationController(ExplanationController explanationController) { + this.explanationController = explanationController; + } + + public void setAddController(AddToFavController addController) { + this.addController = addController; + } + + public void changeAddToFavText(JokeState jokeState) { + addToFav.setText(jokeState.getAddToFav()); + } +} diff --git a/src/main/java/view/joke_view/JokeViewModel.java b/src/main/java/view/joke_view/JokeViewModel.java new file mode 100644 index 000000000..d853909ae --- /dev/null +++ b/src/main/java/view/joke_view/JokeViewModel.java @@ -0,0 +1,17 @@ +package view.joke_view; + +import view.ViewModel; + + +public class JokeViewModel extends ViewModel { + + public static final String TITLE_LABEL = "Joke"; + public static final String EXPLANATION_BUTTON_LABEL = "Explain"; + public static final String ADD_BUTTON_LABEL = "Add to Favorite"; + public static final String EXPLANATION_LABEL = "Explanation"; + + public JokeViewModel() { + super("Joke"); + setState(new JokeState()); + } + // a wrapper for all the info you need to build a view \ No newline at end of file diff --git a/src/main/java/view/login/LoginState.java b/src/main/java/view/login/LoginState.java new file mode 100644 index 000000000..c4852b882 --- /dev/null +++ b/src/main/java/view/login/LoginState.java @@ -0,0 +1,35 @@ +package view.login; + +/** + * The state for the Login View Model. + */ +public class LoginState { + private String username = ""; + private String loginError; + private String password = ""; + + public String getUsername() { + return username; + } + + public String getLoginError() { + return loginError; + } + + public String getPassword() { + return password; + } + + public void setUsername(String username) { + this.username = username; + } + + public void setLoginError(String usernameError) { + this.loginError = usernameError; + } + + public void setPassword(String password) { + this.password = password; + } + +} diff --git a/src/main/java/view/login/LoginView.java b/src/main/java/view/login/LoginView.java new file mode 100644 index 000000000..ed23f9770 --- /dev/null +++ b/src/main/java/view/login/LoginView.java @@ -0,0 +1,153 @@ +package view.login; + +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.JPanel; +import javax.swing.JPasswordField; +import javax.swing.JTextField; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; + +import use_case.login.adapter.LoginController; +import view.LabelTextPanel; + +/** + * The View for when the user is logging into the program. + */ +public class LoginView extends JPanel implements ActionListener, PropertyChangeListener { + + 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 JPasswordField passwordInputField = new JPasswordField(15); + private final JLabel passwordErrorField = new JLabel(); + + private final JButton logInButton = new JButton("Log In"); + private final JButton cancelButton = new JButton("Cancel"); + private LoginController loginController; + + public LoginView(LoginViewModel loginViewModel) { + this.loginViewModel = loginViewModel; + this.loginViewModel.addPropertyChangeListener(this); + + setupui(); + setupListeners(); + } + + /** + * Set up the UI components and layout. + */ + private void setupui() { + final JLabel title = new JLabel("Login Screen"); + title.setAlignmentX(Component.CENTER_ALIGNMENT); + + final LabelTextPanel usernamePanel = new LabelTextPanel(new JLabel("Username"), usernameInputField); + final LabelTextPanel passwordPanel = new LabelTextPanel(new JLabel("Password"), passwordInputField); + + final JPanel buttonPanel = new JPanel(); + buttonPanel.add(logInButton); + buttonPanel.add(cancelButton); + + this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); + this.add(title); + this.add(usernamePanel); + this.add(usernameErrorField); + this.add(passwordPanel); + this.add(passwordErrorField); + this.add(buttonPanel); + } + + /** + * Set up listeners for user interaction. + */ + private void setupListeners() { + logInButton.addActionListener(evt -> { + final LoginState currentState = loginViewModel.getState(); + loginController.execute(currentState.getUsername(), currentState.getPassword()); + }); + + cancelButton.addActionListener(this); + + usernameInputField.getDocument().addDocumentListener(new DocumentListener() { + @Override + public void insertUpdate(DocumentEvent e) { + updateUsername(); + } + + @Override + public void removeUpdate(DocumentEvent e) { + updateUsername(); + } + + @Override + public void changedUpdate(DocumentEvent e) { + updateUsername(); + } + + private void updateUsername() { + final LoginState currentState = loginViewModel.getState(); + currentState.setUsername(usernameInputField.getText()); + loginViewModel.setState(currentState); + } + }); + + passwordInputField.getDocument().addDocumentListener(new DocumentListener() { + @Override + public void insertUpdate(DocumentEvent e) { + updatePassword(); + } + + @Override + public void removeUpdate(DocumentEvent e) { + updatePassword(); + } + + @Override + public void changedUpdate(DocumentEvent e) { + updatePassword(); + } + + private void updatePassword() { + final LoginState currentState = loginViewModel.getState(); + currentState.setPassword(new String(passwordInputField.getPassword())); + loginViewModel.setState(currentState); + } + }); + } + + @Override + public void actionPerformed(ActionEvent evt) { + if (evt.getSource().equals(cancelButton)) { + System.out.println("Cancel button clicked"); + } + } + + @Override + public void propertyChange(PropertyChangeEvent evt) { + if ("state".equals(evt.getPropertyName()) && evt.getNewValue() instanceof LoginState) { + final LoginState state = (LoginState) evt.getNewValue(); + usernameInputField.setText(state.getUsername()); + passwordInputField.setText(state.getPassword()); + usernameErrorField.setText(state.getLoginError()); + passwordErrorField.setText(""); + } + } + + public String getViewName() { + return viewName; + } + + public void setLoginController(LoginController loginController) { + this.loginController = loginController; + } +} diff --git a/src/main/java/view/login/LoginViewModel.java b/src/main/java/view/login/LoginViewModel.java new file mode 100644 index 000000000..9c57cb7ab --- /dev/null +++ b/src/main/java/view/login/LoginViewModel.java @@ -0,0 +1,12 @@ +package view.login; + +import view.ViewModel; + +public class LoginViewModel extends ViewModel { + + public LoginViewModel() { + super("log in"); + setState(new LoginState()); + } + +} \ No newline at end of file diff --git a/src/main/java/view/main/MainState.java b/src/main/java/view/main/MainState.java new file mode 100644 index 000000000..5be0b8966 --- /dev/null +++ b/src/main/java/view/main/MainState.java @@ -0,0 +1,21 @@ +package view.main; + +public class MainState { + + private String username = ""; + + public String toString() { + return "MainState{" + + "username: " + + getUsername() + '\'' + + '}'; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } +} diff --git a/src/main/java/view/main/MainView.java b/src/main/java/view/main/MainView.java new file mode 100644 index 000000000..9577d2944 --- /dev/null +++ b/src/main/java/view/main/MainView.java @@ -0,0 +1,116 @@ +package view.main; + +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 use_case.favourite.adapter.FavouriteController; +import use_case.generate.adapter.GenerateController; +import use_case.logout.adapter.LogoutController; +import use_case.search.adapter.SearchController; + +import static view.main.MainViewModel.*; + +/** + * The Main View for interacting with the Joke Application. + */ +public class MainView extends JPanel implements ActionListener, PropertyChangeListener { + + private final String viewName = TITLE_LABEL; + private final MainViewModel jokeViewModel; + + private final JLabel userIdLabel = new JLabel("User ID: "); + private final JButton generateJokeButton = new JButton(GENERATE_BUTTON_LABEL); + private final JButton searchJokeButton = new JButton(SEARCH_BUTTON_LABEL); + private final JButton favouritePageButton = new JButton(FAVOURITE_BUTTON_LABEL); + private final JButton logoutButton = new JButton(LOGOUT_BUTTON_LABEL); + + private LogoutController logoutController; + private GenerateController generateController; + private FavouriteController favouriteController; + private SearchController searchController; + + public MainView(MainViewModel jokeViewModel) { + this.jokeViewModel = jokeViewModel; + + this.jokeViewModel.addPropertyChangeListener(this); + + setupButtons(); + + this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); + this.add(userIdLabel); + this.add(generateJokeButton); + this.add(searchJokeButton); + this.add(favouritePageButton); + this.add(logoutButton); + } + + /** + * Configures the actions for each button. + */ + private void setupButtons() { + generateJokeButton.addActionListener(event -> generateController.execute()); + + searchJokeButton.addActionListener(event -> { + final String query = JOptionPane.showInputDialog(this, SEARCH_BUTTON_LABEL + ":"); + if (query != null && !query.trim().isEmpty()) { + searchController.switchToSearchView(); + } + }); + + favouritePageButton.addActionListener(event -> { + // Navigate to favourites + }); + + logoutButton.addActionListener(event -> { + // Logout logic + }); + } + + /** + * React to a button click that results in evt. + * @param evt the ActionEvent to react to + */ + @Override + public void actionPerformed(ActionEvent evt) { + System.out.println("Click " + evt.getActionCommand()); + } + + /** + * Handles updates from the JokeViewModel. + */ + @Override + public void propertyChange(PropertyChangeEvent evt) { + if ("error".equals(evt.getPropertyName())) { + JOptionPane.showMessageDialog(this, evt.getNewValue().toString(), ERROR_DIALOG_TITLE, + JOptionPane.ERROR_MESSAGE); + } + } + + public String getViewName() { + return viewName; + } + + public void setFavouriteController(FavouriteController favouriteController) { + this.favouriteController = favouriteController; + } + + public void setGenerateController(GenerateController generateController) { + this.generateController = generateController; + } + + public void setSearchController(SearchController searchController) { + this.searchController = searchController; + } + + public void setLogoutController(LogoutController logoutController) { + this.logoutController = logoutController; + } +} diff --git a/src/main/java/view/main/MainViewModel.java b/src/main/java/view/main/MainViewModel.java new file mode 100644 index 000000000..f1265cb43 --- /dev/null +++ b/src/main/java/view/main/MainViewModel.java @@ -0,0 +1,18 @@ +package view.main; + +import view.ViewModel; + +public class MainViewModel extends ViewModel { + // Constants for UI labels + public static final String TITLE_LABEL = "Joke Application"; + public static final String GENERATE_BUTTON_LABEL = "Generate Joke"; + public static final String SEARCH_BUTTON_LABEL = "Search Joke"; + public static final String FAVOURITE_BUTTON_LABEL = "Go to Favourites"; + public static final String LOGOUT_BUTTON_LABEL = "Log out"; + public static final String ERROR_DIALOG_TITLE = "Error"; + + public MainViewModel() { + super("Main"); + setState(new MainState()); + } +} diff --git a/src/main/java/view/signup/SignupState.java b/src/main/java/view/signup/SignupState.java new file mode 100644 index 000000000..969f77cd8 --- /dev/null +++ b/src/main/java/view/signup/SignupState.java @@ -0,0 +1,70 @@ +package view.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/view/signup/SignupView.java b/src/main/java/view/signup/SignupView.java new file mode 100644 index 000000000..62d24b485 --- /dev/null +++ b/src/main/java/view/signup/SignupView.java @@ -0,0 +1,191 @@ +package view.signup; + +import use_case.signup.adapter.SignupController; +import view.LabelTextPanel; + +import javax.swing.*; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +/** + * 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/signup/SignupViewModel.java b/src/main/java/view/signup/SignupViewModel.java new file mode 100644 index 000000000..91e2acb98 --- /dev/null +++ b/src/main/java/view/signup/SignupViewModel.java @@ -0,0 +1,25 @@ +package view.signup; + +import view.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/visitor/JokeVisitor.java b/src/main/java/visitor/JokeVisitor.java new file mode 100644 index 000000000..48603e143 --- /dev/null +++ b/src/main/java/visitor/JokeVisitor.java @@ -0,0 +1,15 @@ +package visitor; + +import data_access.FileDataAccessObject; +import use_case.generate.GenerateOutputData; +import use_case.search.SearchOutputData; + +import javax.swing.*; + +public interface JokeVisitor { + void visit(GenerateOutputData generateOutputData); + + void visit(SearchOutputData searchOutputData); + + void visit(FavSearchOutputData favSearchOutputData); +} diff --git a/src/main/java/visitor/Visitor.java b/src/main/java/visitor/Visitor.java new file mode 100644 index 000000000..9ee712f85 --- /dev/null +++ b/src/main/java/visitor/Visitor.java @@ -0,0 +1,66 @@ +package visitor; + +import data_access.FileDataAccessObject; +import use_case.generate.GenerateOutputData; +import use_case.search.SearchOutputData; +import view.joke_view.JokeFrameBuilder; + +import javax.swing.*; + +public class Visitor implements JokeVisitor{ + + private final FileDataAccessObject fileDataAccessObject; + + public Visitor(FileDataAccessObject dataAccess) { + this.fileDataAccessObject = dataAccess; + } + + + public void visit(GenerateOutputData generateOutputData) { + + JokeFrameBuilder frameBuilder = new JokeFrameBuilder(); + + final JFrame frame = frameBuilder + .addJokeView() + .setJokeContent(generateOutputData.getJokeContent()) + .addExplanationUseCase() + .addAddToFavUseCase(fileDataAccessObject) + .build(); + + frame.pack(); + frame.setVisible(true); + } + + @Override + public void visit(SearchOutputData searchOutputData) { + JokeFrameBuilder frameBuilder = new JokeFrameBuilder(); + + final JFrame frame = frameBuilder + .addJokeView() + .setJokeContent(searchOutputData.getJokeContent()) + .addExplanationUseCase() + .addAddToFavUseCase(fileDataAccessObject) + .build(); + + frame.pack(); + frame.setVisible(true); + } + + @Override + public void visit(FavSearchOutputData favSearchOutputData) { + JokeFrameBuilder frameBuilder = new JokeFrameBuilder(); + + final JFrame frame = frameBuilder + .addJokeView() + .setJokeContent( favSearchOutputData.getJokeContent()) + .setExplanation(favSearchOutputData.getExplanation()) + .addExplanationUseCase() + .addAddToFavUseCase(fileDataAccessObject) + .build(); + + frame.pack(); + frame.setVisible(true); + } + + +} diff --git a/src/main/resources/Users.json b/src/main/resources/Users.json new file mode 100644 index 000000000..395fb2f11 --- /dev/null +++ b/src/main/resources/Users.json @@ -0,0 +1,3 @@ +[{"Username": "Mika", "Password": "12345678", + "favorites": [{"content": "123", "explanation": "456", "score": 0}, + {"content": "789", "explanation": "101112", "score": 1}]}] \ No newline at end of file diff --git a/src/test/java/use_case/generate/DemoTest.java b/src/test/java/use_case/generate/DemoTest.java new file mode 100644 index 000000000..4442dcdf6 --- /dev/null +++ b/src/test/java/use_case/generate/DemoTest.java @@ -0,0 +1,10 @@ +package use_case.generate; + +import org.junit.Test; +import use_case.generate.adapter.DemoBuilder; + +public class DemoTest { + public static void main(String[] args) { + DemoBuilder.build(); + } +} diff --git a/src/test/java/use_case/generate/GenerateTest.java b/src/test/java/use_case/generate/GenerateTest.java new file mode 100644 index 000000000..eb4ed17a7 --- /dev/null +++ b/src/test/java/use_case/generate/GenerateTest.java @@ -0,0 +1,14 @@ +package use_case.generate; + +import data_access.JokeDataAccessObject; +import use_case.generate.adapter.GenerateController; +import use_case.generate.adapter.GeneratePresenter; +import view.joke_view.JokeFrameBuilder; + +public class GenerateTest { + public static void main(String[] args) { + //pretend that the generate button is pressed + // this is not a unit test + new GenerateController(new GenerateInteractor(new JokeDataAccessObject(), new GeneratePresenter(new JokeFrameBuilder()))).execute(); + } +} diff --git a/src/test/java/use_case/search/SearchJokeTest.java b/src/test/java/use_case/search/SearchJokeTest.java new file mode 100644 index 000000000..4907dd248 --- /dev/null +++ b/src/test/java/use_case/search/SearchJokeTest.java @@ -0,0 +1,12 @@ +package use_case.search; + +import data_access.JokeDataAccessObject; + +public class SearchJokeTest { + + public static void main(String[] args) { + JokeDataAccessObject jokeDataAccessObject = new JokeDataAccessObject(); + String joke = jokeDataAccessObject.searchJoke("bees"); + System.out.println(joke); + } +}