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..5eb93025a 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,74 @@ -# 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 +* User Stories +* Entities +* Proposed API +* Setup and Installation +* Usage +* Contributing +* License +* Contact -## 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 +## User Stories +* Bob generates a joke. He then keeps generating new jokes with different specifications until he is tired. +* Bob was told a joke but he doesn't understand it. He runs the joke explanation program and the program explains to him what is funny about the joke. +* Bob finds some jokes funny and saves them as favorite. He later wants to revisit the jokes that were favorited, so he revisits his list of saved jokes. +* Bob remembers a joke vaguely, he only remembers that it was about 911. He searches “911” and a few different jokes pop up. +* Bob wants to find a specific saved joke, he doesn’t remember what the joke said but he remembers the explanation. He then goes into his list of saved jokes and searches by keywords in the explanation. +* Bob wants to find some really funny jokes. He presses the “Funniest” button in the saved view, then the program returns the joke with the highest score. -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. +## Entities +### User + Attributes: + name: User's name for login. + password: User’s password. + favorite: A list of saved jokes marked as favorites. +### Joke + Attributes: + content: The joke text. + explanation: The joke's explanation or humorous insight. + score: Humor rating to indicate how funny the joke is. + +## Proposed API +* Google Gemini: Used for natural language understanding and generation. Assists in explaining user-provided jokes. +* JokeAPI: Used for joke retrieval and generation, allowing for a diverse and randomized joke collection. -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. +## Setup and Installation +* Clone the Repository +* Install Dependencies: +* Run the Application: + +## 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. 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/JokeDataAccessObject.java b/src/main/java/data_access/JokeDataAccessObject.java new file mode 100644 index 000000000..36f82f4f0 --- /dev/null +++ b/src/main/java/data_access/JokeDataAccessObject.java @@ -0,0 +1,96 @@ +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")) { + 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); + } + } +} 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..4d63ad1ea --- /dev/null +++ b/src/main/java/entity/Joke.java @@ -0,0 +1,27 @@ +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; + } +} 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..e8fcdb71e --- /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 + */ + User create(String name, String password, List favorites) { + return new User(name, password, favorites); + } + + 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/JokeViewModel.java b/src/main/java/interface_adapter/joke/JokeViewModel.java new file mode 100644 index 000000000..2e036a31e --- /dev/null +++ b/src/main/java/interface_adapter/joke/JokeViewModel.java @@ -0,0 +1,14 @@ +package interface_adapter.joke; + +import interface_adapter.ViewModel; + +/** + * The View Model for the Joke View. + */ +public class JokeViewModel extends ViewModel { + + public JokeViewModel() { + super("joke view"); + setState(new JokeState()); // Initialize with a new JokeState + } +} 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/SearchFavouritesController.java b/src/main/java/interface_adapter/search_favourites/SearchFavouritesController.java new file mode 100644 index 000000000..e2067102f --- /dev/null +++ b/src/main/java/interface_adapter/search_favourites/SearchFavouritesController.java @@ -0,0 +1,16 @@ +package interface_adapter.search_favourites; + +import use_case.search_favourites.SearchFavouritesInputBoundary; + +public class SearchFavouritesController { + + private final SearchFavouritesInputBoundary interactor; + + public SearchFavouritesController(SearchFavouritesInputBoundary interactor) { + this.interactor = interactor; + } + + public void executeSearch(String keyword) { + interactor.searchFavourites(keyword); + } +} 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/AddController.java b/src/main/java/use_case/add_to_fav/AddController.java new file mode 100644 index 000000000..7df089bd3 --- /dev/null +++ b/src/main/java/use_case/add_to_fav/AddController.java @@ -0,0 +1,10 @@ +package use_case.add_to_fav; + +import entity.User; + +public class AddController { + public void execute(String jokeContent, String explanation, User user) { + //TODO cheyl implement this + } +} + diff --git a/src/main/java/use_case/add_to_fav/AddInteractor.java b/src/main/java/use_case/add_to_fav/AddInteractor.java new file mode 100644 index 000000000..645a062c4 --- /dev/null +++ b/src/main/java/use_case/add_to_fav/AddInteractor.java @@ -0,0 +1,4 @@ +package use_case.add_to_fav; + +public class AddInteractor { +} 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..1fb0f19f1 --- /dev/null +++ b/src/main/java/use_case/add_to_fav/AddToFavDataAccessInterface.java @@ -0,0 +1,33 @@ +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 getUser(String username); + + /** + * Retrieves a joke by ID. + * + * @param jokeId the ID of the joke + * @return the Joke object, or null if not found + */ + Joke getJoke(String jokeId); + + /** + * Saves the updated user to the data storage. + * + * @param user the User object to save + */ + void saveUser(User user); +} 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..71f8c6e76 --- /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 + */ + AddToFavOutputData addToFavorites(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..0a65d4c00 --- /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 username; + private final String jokeId; + + /** + * Constructs an AddToFavInputData with the specified username and joke ID. + * + * @param username the username of the user + * @param jokeId the ID of the joke to add to favorites + */ + public AddToFavInputData(String username, String jokeId) { + this.username = username; + this.jokeId = jokeId; + } + + /** + * Gets the username of the user. + * + * @return the username of the user + */ + public String getUsername() { + return username; + } + + /** + * Gets the ID of the joke. + * + * @return the joke ID + */ + public String getJokeId() { + return jokeId; + } +} 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..091f112b6 --- /dev/null +++ b/src/main/java/use_case/add_to_fav/AddToFavInteractor.java @@ -0,0 +1,63 @@ +package use_case.add_to_fav; + +import entity.User; +import entity.Joke; + +/** + * 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 + * @return a response model indicating the result of the operation + */ + @Override + public AddToFavOutputData addToFavorites(AddToFavInputData inputData) { + // Retrieve the user by username + final User user = dataAccess.getUser(inputData.getUsername()); + if (user == null) { + return outputBoundary.prepareFailResponse("User not found."); + } + + // Retrieve the joke by joke ID + final Joke joke = dataAccess.getJoke(inputData.getJokeId()); + if (joke == null) { + return outputBoundary.prepareFailResponse("Joke not found."); + } + + // Attempt to add the joke to the user's favorites + if (user.addToFavorites()) { + // Save the updated user in the data store + dataAccess.saveUser(user); + + // Prepare a success response + return outputBoundary.prepareSuccessResponse( + new AddToFavOutputData("Joke added to favorites successfully!") + ); + } + else { + // Joke was already in the favorites list + return outputBoundary.prepareFailResponse("Joke is already in the favorites list."); + } + } +} 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..e6dcf02c1 --- /dev/null +++ b/src/main/java/use_case/add_to_fav/AddToFavOutputBoundary.java @@ -0,0 +1,23 @@ +package use_case.add_to_fav; + +/** + * The AddToFavOutputBoundary interface defines the output boundary + * for the Add to Favorites use case. + */ +public interface AddToFavOutputBoundary { + /** + * Prepares a successful response for the operation. + * + * @param outputData the data to include in the success response + * @return a formatted response model + */ + AddToFavOutputData prepareSuccessResponse(AddToFavOutputData outputData); + + /** + * Prepares a failure response for the operation. + * + * @param errorMessage the error message to include in the response + * @return a formatted response model + */ + AddToFavOutputData prepareFailResponse(String errorMessage); +} diff --git a/src/main/java/use_case/add_to_fav/AddToFavOutputData.java b/src/main/java/use_case/add_to_fav/AddToFavOutputData.java new file mode 100644 index 000000000..7930b5baa --- /dev/null +++ b/src/main/java/use_case/add_to_fav/AddToFavOutputData.java @@ -0,0 +1,26 @@ +package use_case.add_to_fav; + +/** + * Represents the output data for the Add to Favorites use case. + */ +public class AddToFavOutputData { + private final String message; + + /** + * Constructs an AddToFavOutputData with the specified message. + * + * @param message the message describing the result of the operation + */ + public AddToFavOutputData(String message) { + this.message = message; + } + + /** + * Gets the message describing the result of the operation. + * + * @return the result message + */ + public String getMessage() { + return message; + } +} diff --git a/src/main/java/use_case/add_to_fav/AddToFavRequestModel.java b/src/main/java/use_case/add_to_fav/AddToFavRequestModel.java new file mode 100644 index 000000000..2089858f1 --- /dev/null +++ b/src/main/java/use_case/add_to_fav/AddToFavRequestModel.java @@ -0,0 +1,29 @@ +package use_case.add_to_fav; + +/** + * The AddToFavRequestModel class represents the input data + * for the Add to Favorites use case. + */ +public class AddToFavRequestModel { + private final String username; + private final String jokeId; + + /** + * Constructs an AddToFavRequestModel with the specified username and joke ID. + * + * @param username the username of the user + * @param jokeId the ID of the joke to add to favorites + */ + public AddToFavRequestModel(String username, String jokeId) { + this.username = username; + this.jokeId = jokeId; + } + + public String getUsername() { + return username; + } + + public String getJokeId() { + return jokeId; + } +} diff --git a/src/main/java/use_case/add_to_fav/AddToFavResponseModel.java b/src/main/java/use_case/add_to_fav/AddToFavResponseModel.java new file mode 100644 index 000000000..db1d699b7 --- /dev/null +++ b/src/main/java/use_case/add_to_fav/AddToFavResponseModel.java @@ -0,0 +1,22 @@ +package use_case.add_to_fav; + +/** + * The AddToFavResponseModel class represents the output data + * for the Add to Favorites use case. + */ +public class AddToFavResponseModel { + private final String message; + + /** + * Constructs an AddToFavResponseModel with the specified message. + * + * @param message the message describing the result of the operation + */ + public AddToFavResponseModel(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } +} 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..33d12fa03 --- /dev/null +++ b/src/main/java/use_case/add_to_fav/adapter/AddToFavController.java @@ -0,0 +1,44 @@ +package use_case.add_to_fav.adapter; + +import use_case.add_to_fav.AddToFavInputBoundary; +import use_case.add_to_fav.AddToFavInputData; +import use_case.add_to_fav.AddToFavOutputData; +import use_case.add_to_fav.AddToFavResponseModel; + +/** + * 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 username the username of the user + * @param jokeId the ID of the joke to add to favorites + * @return a response model containing the result of the operation + */ + public AddToFavResponseModel addToFavorites(String username, String jokeId) { + // Create the input data object for the interactor + final AddToFavInputData inputData = new AddToFavInputData(username, jokeId); + + // Pass the input data to the interactor + final AddToFavOutputData outputData = interactor.addToFavorites(inputData); + + // Return the formatted response + return new AddToFavResponseModel(outputData.getMessage()); + } +} + + 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..4c33ce530 --- /dev/null +++ b/src/main/java/use_case/add_to_fav/adapter/AddToFavPresenter.java @@ -0,0 +1,35 @@ +package use_case.add_to_fav.adapter; + +import use_case.add_to_fav.AddToFavOutputBoundary; +import use_case.add_to_fav.AddToFavOutputData; + +/** + * The AddToFavPresenter class formats the output of the Add to Favorites use case + * for the view layer. + */ +public class AddToFavPresenter implements AddToFavOutputBoundary { + + /** + * Prepares a success response for the Add to Favorites use case. + * + * @param outputData the output data containing the success message + * @return the formatted output data + */ + @Override + public AddToFavOutputData prepareSuccessResponse(AddToFavOutputData outputData) { + // Simply return the output data for success cases (can format further if needed) + return outputData; + } + + /** + * Prepares a failure response for the Add to Favorites use case. + * + * @param errorMessage the error message describing the failure + * @return the formatted output data with the error message + */ + @Override + public AddToFavOutputData prepareFailResponse(String errorMessage) { + // Return an output data object with the error message + return new AddToFavOutputData(errorMessage); + } +} 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/FavSearchInteractor.java b/src/main/java/use_case/fav_search/FavSearchInteractor.java new file mode 100644 index 000000000..e0d87365b --- /dev/null +++ b/src/main/java/use_case/fav_search/FavSearchInteractor.java @@ -0,0 +1,4 @@ +package use_case.fav_search; + +public class FavSearchInteractor { +} 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..bd26d63ec --- /dev/null +++ b/src/main/java/use_case/favourite/FavouriteInputBoundary.java @@ -0,0 +1,15 @@ +package use_case.favourite; + +public interface FavouriteInputBoundary { + + /** + * Executes the favourite use case. + * @param favouriteInputData the input data + */ + void execute(FavouriteInputData favouriteInputData); + + /** + * Executes the switch to login view use case. + */ + void switchToFavouriteView(); +} 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..857a5c28d --- /dev/null +++ b/src/main/java/use_case/favourite/FavouriteInputData.java @@ -0,0 +1,4 @@ +package use_case.favourite; + +public class FavouriteInputData { +} 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..ec5b9874f --- /dev/null +++ b/src/main/java/use_case/favourite/FavouriteInteractor.java @@ -0,0 +1,4 @@ +package use_case.favourite; + +public class FavouriteInteractor { +} 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..cd33b5d66 --- /dev/null +++ b/src/main/java/use_case/favourite/FavouriteOutputBoundary.java @@ -0,0 +1,4 @@ +package use_case.favourite; + +public interface FavouriteOutputBoundary { +} 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..6faf05ff0 --- /dev/null +++ b/src/main/java/use_case/favourite/FavouriteOutputData.java @@ -0,0 +1,4 @@ +package use_case.favourite; + +public class FavouriteOutputData { +} 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..7b3e7b4f2 --- /dev/null +++ b/src/main/java/use_case/favourite/FavouriteUserDataAccessInterface.java @@ -0,0 +1,4 @@ +package use_case.favourite; + +public interface FavouriteUserDataAccessInterface { +} diff --git a/src/main/java/use_case/favourite/adapter/FavouriteBuilder.java b/src/main/java/use_case/favourite/adapter/FavouriteBuilder.java new file mode 100644 index 000000000..0cbddba98 --- /dev/null +++ b/src/main/java/use_case/favourite/adapter/FavouriteBuilder.java @@ -0,0 +1,24 @@ +package use_case.favourite.adapter; + +import view.FavouriteView; + +import javax.swing.*; + +public class FavouriteBuilder { + public static final int HEIGHT = 300; + public static final int WIDTH = 400; + + public static JFrame build() { + + final JFrame frame = new JFrame(); + final FavouriteViewModel viewModel = new FavouriteViewModel(); + final FavouriteController controller = new FavouriteController(); + frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); + frame.setTitle("Search"); + frame.setSize(WIDTH, HEIGHT); + + frame.add(new FavouriteView(viewModel, controller)); + + return frame; + } +} 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..e54b9bd93 --- /dev/null +++ b/src/main/java/use_case/favourite/adapter/FavouriteController.java @@ -0,0 +1,4 @@ +package use_case.favourite.adapter; + +public class FavouriteController { +} 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..8426f3996 --- /dev/null +++ b/src/main/java/use_case/favourite/adapter/FavouritePresenter.java @@ -0,0 +1,4 @@ +package use_case.favourite.adapter; + +public class FavouritePresenter { +} diff --git a/src/main/java/use_case/favourite/adapter/FavouriteState.java b/src/main/java/use_case/favourite/adapter/FavouriteState.java new file mode 100644 index 000000000..18962f73d --- /dev/null +++ b/src/main/java/use_case/favourite/adapter/FavouriteState.java @@ -0,0 +1,4 @@ +package use_case.favourite.adapter; + +public class FavouriteState { +} diff --git a/src/main/java/use_case/favourite/adapter/FavouriteViewModel.java b/src/main/java/use_case/favourite/adapter/FavouriteViewModel.java new file mode 100644 index 000000000..13bf01d57 --- /dev/null +++ b/src/main/java/use_case/favourite/adapter/FavouriteViewModel.java @@ -0,0 +1,19 @@ +package use_case.favourite.adapter; + +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/use_case/funniest/FunniestInteractor.java b/src/main/java/use_case/funniest/FunniestInteractor.java new file mode 100644 index 000000000..5c4622185 --- /dev/null +++ b/src/main/java/use_case/funniest/FunniestInteractor.java @@ -0,0 +1,4 @@ +package use_case.funniest; + +public class FunniestInteractor { +} 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..b0ed6e798 --- /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..2dc854d21 --- /dev/null +++ b/src/main/java/use_case/generate/adapter/GeneratePresenter.java @@ -0,0 +1,50 @@ +package use_case.generate.adapter; + +import entity.Joke; +import use_case.generate.GenerateOuputBoundary; +import use_case.generate.GenerateOutputData; +import use_case.note.NoteOutputBoundary; +import view.joke_view.JokeFrameBuilder; +import view.joke_view.JokeViewModel; + +import javax.swing.*; + +public class GeneratePresenter implements GenerateOuputBoundary { + // not a typical presenter, more similar to main + + private final JokeFrameBuilder jokeFrameBuilder; + + public GeneratePresenter(JokeFrameBuilder jokeFrameBuilder) { + this.jokeFrameBuilder = jokeFrameBuilder; + } + + /** + * Prepares the success view for the Note related Use Cases. + * + * @param generateOutputData the output data + */ + @Override + public void prepareSuccessView(GenerateOutputData generateOutputData) { + final JFrame frame = jokeFrameBuilder + .addJokeView() + .setJokeContent(generateOutputData.getJokeContent()) + .addExplanationUseCase() + .addAddToFavUseCase() + .build(); + + frame.pack(); + frame.setVisible(true); + } + + /** + * Prepares the failure view for the Note related Use Cases. + * + * @param errorMessage the explanation of the failure + */ + //TODO implement + @Override + public void prepareFailView(String errorMessage) { +// jokeViewModel.getState().setError(errorMessage); +// jokeViewModel.firePropertyChanged(); + } +} 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..917fc0767 --- /dev/null +++ b/src/main/java/use_case/logout/LogoutInputData.java @@ -0,0 +1,16 @@ +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..90ca7f559 --- /dev/null +++ b/src/main/java/use_case/logout/LogoutInteractor.java @@ -0,0 +1,24 @@ +package use_case.logout; + +/** + * The Logout Interactor. + */ +public class LogoutInteractor implements LogoutInputBoundary { + private LogoutUserDataAccessInterface userDataAccessObject; + private LogoutOutputBoundary logoutPresenter; + + public LogoutInteractor(LogoutUserDataAccessInterface userDataAccessInterface, + LogoutOutputBoundary logoutOutputBoundary) { + this.userDataAccessObject = userDataAccessInterface; + this.logoutPresenter = logoutOutputBoundary; + } + + @Override + public void execute(LogoutInputData logoutInputData) { + final String username = logoutInputData.getUsername(); + userDataAccessObject.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 100644 index 000000000..4ff527a45 --- /dev/null +++ b/src/main/java/use_case/search/SearchInputBoundary.java @@ -0,0 +1,9 @@ +package use_case.search; + +public interface SearchInputBoundary { + /** + * Search jokes by keyword. + * @param keyword the keyword that wants to be searched. + */ + void executeSearch(String keyword); +} diff --git a/src/main/java/use_case/search/SearchInteractor.java b/src/main/java/use_case/search/SearchInteractor.java new file mode 100644 index 000000000..5989a8b3a --- /dev/null +++ b/src/main/java/use_case/search/SearchInteractor.java @@ -0,0 +1,25 @@ +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) { + try { + final String jokeContent = searchDataAccessObject.searchJoke(keyword); + final SearchOutputData searchOutputData = new SearchOutputData(jokeContent); + searchPresenter.prepareSuccessView(searchOutputData); + } + catch (RuntimeException ex) { + final String message = "failed to find a joke"; + searchPresenter.prepareFailureView(message); + } + } +} diff --git a/src/main/java/use_case/search/SearchOutputBoundary.java b/src/main/java/use_case/search/SearchOutputBoundary.java new file mode 100644 index 000000000..96c824383 --- /dev/null +++ b/src/main/java/use_case/search/SearchOutputBoundary.java @@ -0,0 +1,16 @@ +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 +} 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..0e825881e --- /dev/null +++ b/src/main/java/use_case/search/SearchOutputData.java @@ -0,0 +1,14 @@ +package use_case.search; + +public class SearchOutputData { + + private final String jokeContent; + + public SearchOutputData(String jokeContent) { + this.jokeContent = jokeContent; + } + + public String getJokeContent() { + return jokeContent; + } +} \ No newline at end of file diff --git a/src/main/java/use_case/search/adapter/SearchBuilder.java b/src/main/java/use_case/search/adapter/SearchBuilder.java new file mode 100644 index 000000000..1ee3294b3 --- /dev/null +++ b/src/main/java/use_case/search/adapter/SearchBuilder.java @@ -0,0 +1,25 @@ +package use_case.search.adapter; + +import view.search_view.SearchView; +import view.search_view.SearchViewModel; + +import javax.swing.*; + +public class SearchBuilder { + public static final int HEIGHT = 300; + public static final int WIDTH = 400; + + public static JFrame build() { + + final JFrame frame = new JFrame(); + final SearchViewModel viewModel = new SearchViewModel(); + frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); + frame.setTitle("Search"); + frame.setSize(WIDTH, HEIGHT); + + frame.add(new SearchView(viewModel)); + + return frame; + + } +} 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 100644 index 000000000..07348ebf7 --- /dev/null +++ b/src/main/java/use_case/search/adapter/SearchController.java @@ -0,0 +1,16 @@ +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); + } +} \ 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 100644 index 000000000..9d63283cb --- /dev/null +++ b/src/main/java/use_case/search/adapter/SearchPresenter.java @@ -0,0 +1,28 @@ +package use_case.search.adapter; + +import use_case.search.SearchOutputBoundary; +import use_case.search.SearchOutputData; +import view.search_view.SearchState; +import view.search_view.SearchViewModel; + +public class SearchPresenter implements SearchOutputBoundary { + + private final SearchViewModel searchViewModel; + + public SearchPresenter(SearchViewModel searchViewModel) { + this.searchViewModel = searchViewModel; + } + + @Override + public void prepareSuccessView(SearchOutputData searchOutputData) { + final SearchState searchState = searchViewModel.getState(); + searchState.setExplanation(searchOutputData.getJokeContent()); + searchViewModel.setState(searchState); + searchViewModel.firePropertyChanged(); + } + + @Override + public void prepareFailureView(String errormessage) { + + } +} \ No newline at end of file diff --git a/src/main/java/use_case/search_favourites/SearchFavouritesInputBoundary.java b/src/main/java/use_case/search_favourites/SearchFavouritesInputBoundary.java new file mode 100644 index 000000000..d36b44a50 --- /dev/null +++ b/src/main/java/use_case/search_favourites/SearchFavouritesInputBoundary.java @@ -0,0 +1,8 @@ +package use_case.search_favourites; + +/** + * Input boundary for the Search Favourites use case. + */ +public interface SearchFavouritesInputBoundary { + void searchFavourites(String query); +} 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..b24a4110e --- /dev/null +++ b/src/main/java/use_case/search_favourites/SearchFavouritesInteractor.java @@ -0,0 +1,39 @@ +package use_case.search_favourites; + +import data_access.JokeDataAccessObject; +import entity.Joke; + +import java.util.List; +import java.util.stream.Collectors; + +public class SearchFavouritesInteractor implements SearchFavouritesInputBoundary { + + private final JokeDataAccessObject jokeDataAccessObject; + private final SearchFavouritesOutputBoundary outputBoundary; + + public SearchFavouritesInteractor(JokeDataAccessObject jokeDataAccessObject, + SearchFavouritesOutputBoundary outputBoundary) { + this.jokeDataAccessObject = jokeDataAccessObject; + this.outputBoundary = outputBoundary; + } + + @Override + public void searchFavourites(String keyword) { + try { + List favourites = jokeDataAccessObject.getFavorites(); + List matchingFavourites = favourites.stream() + .filter(joke -> joke.getText().toLowerCase().contains(keyword.toLowerCase())) + .collect(Collectors.toList()); + if (matchingFavourites.isEmpty()) { + outputBoundary.presentFavouritesSearchResult("No matching jokes found in favourites."); + } else { + String result = matchingFavourites.stream() + .map(Joke::getText) + .collect(Collectors.joining("\n")); + outputBoundary.presentFavouritesSearchResult(result); + } + } catch (Exception e) { + outputBoundary.presentFavouritesSearchError("An error occurred while searching favourites."); + } + } +} diff --git a/src/main/java/use_case/search_favourites/SearchFavouritesOutputBoundary.java b/src/main/java/use_case/search_favourites/SearchFavouritesOutputBoundary.java new file mode 100644 index 000000000..8969a439a --- /dev/null +++ b/src/main/java/use_case/search_favourites/SearchFavouritesOutputBoundary.java @@ -0,0 +1,9 @@ +package use_case.search_favourites; + +/** + * Output boundary for the Search Favourites use case. + */ +public interface SearchFavouritesOutputBoundary { + void presentFavouritesSearchResult(String result); + void presentFavouritesSearchError(String error); +} diff --git a/src/main/java/use_case/signup/SignupInputBoundary.java b/src/main/java/use_case/signup/SignupInputBoundary.java new file mode 100644 index 000000000..1cb69e02e --- /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 100644 index 000000000..3fd6560c7 --- /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 userDataAccessObject; + private final SignupOutputBoundary userPresenter; + private final UserFactory userFactory; + + public SignupInteractor(SignupUserDataAccessInterface signupDataAccessInterface, + SignupOutputBoundary signupOutputBoundary, + UserFactory userFactory) { + this.userDataAccessObject = signupDataAccessInterface; + this.userPresenter = signupOutputBoundary; + this.userFactory = userFactory; + } + + @Override + public void execute(SignupInputData signupInputData) { + if (userDataAccessObject.existsByName(signupInputData.getUsername())) { + userPresenter.prepareFailView("User already exists."); + } + else if (!signupInputData.getPassword().equals(signupInputData.getRepeatPassword())) { + userPresenter.prepareFailView("Passwords don't match."); + } + else { + final User user = userFactory.create(signupInputData.getUsername(), signupInputData.getPassword()); + userDataAccessObject.save(user); + + final SignupOutputData signupOutputData = new SignupOutputData(user.getName(), false); + userPresenter.prepareSuccessView(signupOutputData); + } + } + + @Override + public void switchToLoginView() { + userPresenter.switchToLoginView(); + } +} diff --git a/src/main/java/use_case/signup/SignupOutputBoundary.java b/src/main/java/use_case/signup/SignupOutputBoundary.java new file mode 100644 index 000000000..314376b93 --- /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 100644 index 000000000..7f81a5963 --- /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 100644 index 000000000..bf7dfa08e --- /dev/null +++ b/src/main/java/use_case/signup/adapter/SignupPresenter.java @@ -0,0 +1,50 @@ +package use_case.signup.adapter; + +import interface_adapter.ViewManagerModel; +import interface_adapter.login.LoginState; +import interface_adapter.login.LoginViewModel; +import use_case.signup.SignupOutputBoundary; +import use_case.signup.SignupOutputData; + +/** + * The Presenter for the Signup Use Case. + */ +public class SignupPresenter implements SignupOutputBoundary { + + private final SignupViewModel signupViewModel; + private final LoginViewModel loginViewModel; + private final ViewManagerModel viewManagerModel; + + public SignupPresenter(ViewManagerModel viewManagerModel, + SignupViewModel signupViewModel, + LoginViewModel loginViewModel) { + this.viewManagerModel = viewManagerModel; + this.signupViewModel = signupViewModel; + this.loginViewModel = loginViewModel; + } + + @Override + public void prepareSuccessView(SignupOutputData response) { + // On success, switch to the login view. + final LoginState loginState = loginViewModel.getState(); + loginState.setUsername(response.getUsername()); + this.loginViewModel.setState(loginState); + loginViewModel.firePropertyChanged(); + + viewManagerModel.setState(loginViewModel.getViewName()); + viewManagerModel.firePropertyChanged(); + } + + @Override + public void prepareFailView(String error) { + final SignupState signupState = signupViewModel.getState(); + signupState.setUsernameError(error); + signupViewModel.firePropertyChanged(); + } + + @Override + public void switchToLoginView() { + viewManagerModel.setState(loginViewModel.getViewName()); + viewManagerModel.firePropertyChanged(); + } +} diff --git a/src/main/java/use_case/signup/adapter/SignupState.java b/src/main/java/use_case/signup/adapter/SignupState.java new file mode 100644 index 000000000..a7c746149 --- /dev/null +++ b/src/main/java/use_case/signup/adapter/SignupState.java @@ -0,0 +1,70 @@ +package use_case.signup.adapter; + +/** + * 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/use_case/signup/adapter/SignupViewModel.java b/src/main/java/use_case/signup/adapter/SignupViewModel.java new file mode 100644 index 000000000..a096ea6e1 --- /dev/null +++ b/src/main/java/use_case/signup/adapter/SignupViewModel.java @@ -0,0 +1,25 @@ +package use_case.signup.adapter; + +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/view/FavouriteView.java b/src/main/java/view/FavouriteView.java new file mode 100644 index 000000000..19ed4922c --- /dev/null +++ b/src/main/java/view/FavouriteView.java @@ -0,0 +1,95 @@ +package view; + +import use_case.favourite.adapter.FavouriteViewModel; +import view.helper_functions.LabelTextPanel; + +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 use_case.favourite.adapter.FavouriteController; +import use_case.favourite.adapter.FavouriteState; + +public class FavouriteView extends JPanel { + + private final FavouriteController favouriteController; + private final FavouriteViewModel favouriteViewModel; + + private final String viewName = "Favourite"; + private FavouriteView favouriteView; + + private final JTextArea keywordInputField = new JTextArea(); + + private final JTextField searchBox = new JTextField(15); + private final JButton searchButton; + private final JButton funniestButton; + private final JButton cancelButton; + + public FavouriteView(FavouriteViewModel favouriteViewModel, FavouriteController controller) { + this.favouriteController = controller; + this.favouriteViewModel = favouriteViewModel; +// 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); + + this.add(title); + this.add(search); + this.add(buttons); + +// funniestButton.addActionListener( +// evt -> { +// if (evt.getSource().equals(funniestButton)) { +// favouriteController.execute(favouriteInputField.getText()); +// +// } +// } +// ); +// +// refreshButton.addActionListener( +// evt -> { +// if (evt.getSource().equals(refreshButton)) { +// favouriteController.execute(null); +// +// } +// } +// ); +// } +// +// /** +// * 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()); +// } +// +// public void propertyChange(PropertyChangeEvent evt) { +// final FavouriteState state = (FavouriteState) evt.getNewValue(); +// setFields(state); +// usernameErrorField.setText(state.getFavouriteError()); +// } +// +// private void setFields(FavouriteState state) { +// usernameInputField.setText(state.getUsername()); +// } +// +// public String getViewName() { +// return viewName; +// } + } +} \ No newline at end of file diff --git a/src/main/java/view/JokeView.java b/src/main/java/view/JokeView.java new file mode 100644 index 000000000..b3537b3a7 --- /dev/null +++ b/src/main/java/view/JokeView.java @@ -0,0 +1,111 @@ +package view; + +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 JButton generateJokeButton = new JButton("Generate Joke"); + private final JButton searchJokeButton = new JButton("Search Joke"); + private final JButton favoriteJokeButton = new JButton("Favourite Joke"); + private final JButton logoutButton = new JButton("Log out"); + private JokeController jokeController; + + public JokeAppView(JokeViewModel jokeViewModel) { + this.jokeViewModel = jokeViewModel; + this.jokeViewModel.addPropertyChangeListener(this); + + setupButtons(); + + // Set up layout + this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); + this.add(userIdLabel); + this.add(generateJokeButton); + this.add(searchJokeButton); + this.add(favoriteJokeButton); + this.add(logoutButton); + } + + /** + * Configures the actions for each button. + */ + private void setupButtons() { + generateJokeButton.addActionListener(e -> jokeController.execute("Generate", "")); + + searchJokeButton.addActionListener(e -> { + String query = JOptionPane.showInputDialog(this, "Search Joke:"); + if (query != null && !query.trim().isEmpty()) { + jokeController.execute("search", query); + } + }); + + favoriteJokeButton.addActionListener(e -> jokeController.execute("Favourite", "")); + logoutButton.addActionListener(e -> System.out.println("Logout action triggered")); // Placeholder for logout action + } + + /** + * 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(); + displayJoke(jokeState.getJokeText()); + updateFavoriteButton(jokeState.isFavorite()); + } else if ("error".equals(evt.getPropertyName())) { + JOptionPane.showMessageDialog(this, evt.getNewValue().toString(), "Error", JOptionPane.ERROR_MESSAGE); + } + } + + /** + * Displays the joke in a pop-up dialog. + * @param jokeText the joke text to display + */ + private void displayJoke(String jokeText) { + JOptionPane.showMessageDialog(this, jokeText, "Here's Your Joke", JOptionPane.INFORMATION_MESSAGE); + } + + /** + * Updates the favorite button text based on the favorite status. + * @param isFavorite the favorite status + */ + private void updateFavoriteButton(boolean isFavorite) { + favoriteJokeButton.setText(isFavorite ? "Unfavourite Joke" : "Favourite Joke"); + } + + public String getViewName() { + return viewName; + } + + public void setJokeController(JokeController jokeController) { + this.jokeController = jokeController; + } +} diff --git a/src/main/java/view/NoteView.java b/src/main/java/view/NoteView.java index 331d76493..81e52e1a6 100644 --- a/src/main/java/view/NoteView.java +++ b/src/main/java/view/NoteView.java @@ -5,91 +5,99 @@ 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; +import interface_adapter.joke.JokeController; +import interface_adapter.joke.JokeState; +import interface_adapter.joke.JokeViewModel; /** - * The View for when the user is viewing a note in the program. + * The View for displaying jokes and interacting with the Joke Application. */ -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) { +public class JokeAppView extends JPanel implements ActionListener, PropertyChangeListener { - noteName.setAlignmentX(Component.CENTER_ALIGNMENT); - this.noteViewModel = noteViewModel; - this.noteViewModel.addPropertyChangeListener(this); + private final String viewName = "joke view"; + private final JokeViewModel jokeViewModel; - final JPanel buttons = new JPanel(); - buttons.add(saveButton); - buttons.add(refreshButton); + 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; - saveButton.addActionListener( - evt -> { - if (evt.getSource().equals(saveButton)) { - noteController.execute(noteInputField.getText()); + public JokeAppView(JokeViewModel jokeViewModel) { + this.jokeViewModel = jokeViewModel; + this.jokeViewModel.addPropertyChangeListener(this); - } - } - ); - - refreshButton.addActionListener( - evt -> { - if (evt.getSource().equals(refreshButton)) { - noteController.execute(null); - - } - } - ); + setupButtons(); this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); - - this.add(noteName); - this.add(noteInputField); - this.add(buttons); + 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) { - final NoteState state = (NoteState) evt.getNewValue(); - setFields(state); - if (state.getError() != null) { - JOptionPane.showMessageDialog(this, state.getError(), - "Error", JOptionPane.ERROR_MESSAGE); + 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); } } - private void setFields(NoteState state) { - noteInputField.setText(state.getNote()); + /** + * 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 void setNoteController(NoteController controller) { - this.noteController = controller; + public String getViewName() { + return viewName; } -} + public void setJokeController(JokeController jokeController) { + this.jokeController = jokeController; + } +} diff --git a/src/main/java/view/SignupView.java b/src/main/java/view/SignupView.java new file mode 100644 index 000000000..4edae975e --- /dev/null +++ b/src/main/java/view/SignupView.java @@ -0,0 +1,193 @@ +package view; + +import use_case.signup.adapter.SignupController; +import use_case.signup.adapter.SignupState; +import use_case.signup.adapter.SignupViewModel; +import view.helper_functions.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/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/helper_functions/LabelTextPanel.java b/src/main/java/view/helper_functions/LabelTextPanel.java new file mode 100644 index 000000000..0b74cc6cb --- /dev/null +++ b/src/main/java/view/helper_functions/LabelTextPanel.java @@ -0,0 +1,15 @@ +package view.helper_functions; + +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/joke_view/JokeFrameBuilder.java b/src/main/java/view/joke_view/JokeFrameBuilder.java new file mode 100644 index 000000000..d3ff33ab4 --- /dev/null +++ b/src/main/java/view/joke_view/JokeFrameBuilder.java @@ -0,0 +1,64 @@ +package view.joke_view; + +import data_access.ExplanationDataAccessObject; +import data_access.MockExplanationDataAccessObject; +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 ExplanationDataAccessObject(); + + 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 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; + } + + //TODO do this + public JokeFrameBuilder addAddToFavUseCase() { + return this; + } + + public JFrame build() { + final JFrame frame = new JFrame("Joke"); + + //TODO may need to change + 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..4b9a4b4f2 --- /dev/null +++ b/src/main/java/view/joke_view/JokeState.java @@ -0,0 +1,31 @@ +package view.joke_view; + +public class JokeState { + // info that can change + private String jokeContent = ""; + private String explanation = ""; + + @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; + } +} 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..011abcdb7 --- /dev/null +++ b/src/main/java/view/joke_view/JokeView.java @@ -0,0 +1,161 @@ +package view.joke_view; + +import use_case.add_to_fav.AddController; +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 AddController 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(), + null); + } + } + ); + + // 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); + } + + 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(AddController addController) { + this.addController = addController; + } +} 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..24b37f21f --- /dev/null +++ b/src/main/java/view/joke_view/JokeViewModel.java @@ -0,0 +1,18 @@ +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 +} diff --git a/src/main/java/view/search_view/SearchState.java b/src/main/java/view/search_view/SearchState.java new file mode 100644 index 000000000..61a6f8590 --- /dev/null +++ b/src/main/java/view/search_view/SearchState.java @@ -0,0 +1,31 @@ +package view.search_view; + +public class SearchState { + // info that can change + private String jokeContent = ""; + private String explanation = ""; + + @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; + } +} \ No newline at end of file diff --git a/src/main/java/view/search_view/SearchView.java b/src/main/java/view/search_view/SearchView.java new file mode 100644 index 000000000..b468e9caf --- /dev/null +++ b/src/main/java/view/search_view/SearchView.java @@ -0,0 +1,75 @@ +package view.search_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.SearchInteractor; +import use_case.search.adapter.SearchController; +import view.helper_functions.LabelTextPanel; + +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) { + + } +} + diff --git a/src/main/java/view/search_view/SearchViewModel.java b/src/main/java/view/search_view/SearchViewModel.java new file mode 100644 index 000000000..6ef48d0d8 --- /dev/null +++ b/src/main/java/view/search_view/SearchViewModel.java @@ -0,0 +1,16 @@ +package view.search_view; + +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/test/java/use_case/favourite/DemoFavouriteTest.java b/src/test/java/use_case/favourite/DemoFavouriteTest.java new file mode 100644 index 000000000..a5dbd69d1 --- /dev/null +++ b/src/test/java/use_case/favourite/DemoFavouriteTest.java @@ -0,0 +1,9 @@ +package use_case.favourite; + +import use_case.favourite.adapter.FavouriteBuilder; + +public class DemoFavouriteTest { + public static void main(String[] args) { + FavouriteBuilder.build().setVisible(true); + } +} 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/DemoSearchTest.java b/src/test/java/use_case/search/DemoSearchTest.java new file mode 100644 index 000000000..cf1f89e27 --- /dev/null +++ b/src/test/java/use_case/search/DemoSearchTest.java @@ -0,0 +1,10 @@ +package use_case.search; + +import org.junit.Test; +import use_case.search.adapter.SearchBuilder; + +public class DemoSearchTest { + public static void main(String[] args) { + SearchBuilder.build().setVisible(true); + } +} 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); + } +}