diff --git a/.gitignore b/.gitignore index 650c91720..de35550b8 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ out/ !**/src/main/**/out/ !**/src/test/**/out/ .idea/easycode.ignore +.env target/ diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml index 712ab9d98..7f6fdadd1 100644 --- a/.idea/jarRepositories.xml +++ b/.idea/jarRepositories.xml @@ -1,11 +1,21 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 5e1d8b77c..853dea7e5 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,116 @@ -# Note Application +# ZenTask -This is a minimal example demonstrating usage of the -password-protected user part of the API used in lab 5. +## Authors of the Project +1. Iguehi Akhimien - GitHub username: iguehi.akhimien +2. Jenna Zhang - GitHub username: stickycorner +3. Prisha Patel - GitHub username: PrishaPatel24 +4. Liza Kochel - GitHub username: lizakochel -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). +## Summary of the Project +This project focuses on building a productivity app that allows the user to take notes, make a checklist list, and add events to their calendar. +This project was made to serve students, working professionals or anyone who want to better manage life. +Life can be hectic. Our goal is to create peace for the user, allowing them to manage life from one point. +From the onset of the project, the vision was to find a way to make a student's productivity convenient and all in one place. +So, the authors of the project created a "one-stop" life-management dashboard. -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. +### User Benefits: +* As a user, you can create a note within the program (although not save it permanently) to probably note down things you do not want to forget. +To help you, we have included a couple of features, such as translating text to a preferred language and completing your notes using Artificial Intelligence. +* As a user, you can also make a checklist of all your important tasks or things you do not want to procrastinate. +To keep track of things, you can tick off tasks as you go about completing them. +* As a user, you can see your upcoming events at a view while planning to complete your tasks on your checklist or when you are planning time to revisit what you noted down. -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. +**ZenTask brings everything together!** -You can see the documentation in the various files for more information. +## Table of Contents +1. Features of the Software ([Jump to Features of the Software](#features-of-the-software)) +2. Installation Instructions ([Jump to Installation Instructions](#installation-instructions)) +3. Usage Guide ([Jump to Usage Guide](#usage-guide)) +4. License ([Jump to License](#license)) +5. Feedback ([Jump to Feedback](#feedback)) +6. Contributions ([Jump to Contributions](#contributions)) -## Testing -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 of the Software +There are three major components of this software: Calendar, Checklist and Notes. +So, upon running the program, the user first sees "Welcome to your Dashboard" +__// TODO: add a screenshot__ +On the left of the Dashboard, the user sees three tabs: Calendar, Notes and Checklist. -## Project Starter Code +### Calendar +The user can easily migrate to the Calendar tab by clicking on it. +__// TODO: add a screenshot__ +Now, the user is on the Calendar tab. They are prompted to enter their __Google Email__. +__// TODO: add a screenshot__ +This displays the upcoming events the user has added in on their Google calendar. +The details displayed are the event summary, location, start time, and description. -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. +__Note regarding Calendar Usage:__ Since we are unable to release our private project credentials, you should add your +own service account credentials in a json file somewhere in the project and add the path to the file in the CalendarRequest section. +Change the SERVICE_ACCOUNT_KEY_FILE attribute to the path and the calender should be properly +integrated to use your new own Google project credentials. -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. +### Notes +The user can also go to the Notes tab by clicking the menu on the left of their view. +__// TODO: add a screenshot__ +Now, the user is on the Notes tab. +On the left side of the user's view, they can create a new note by simply typing into the provided area that says "*_Create Note_*" +The user also has the option of uploading text from an already existing file. +__// I am thinking of any style functions and undo, and redo...//__ +As additional functionalities, the user can translate existing text on their notes to either French, Spanish, Russian. +Also, the user can prompt AI to complete the note based on what the user has written. + +### Checklist +The user can move to the Checklist tab by clicking the menu on the left of their view. +__// TODO: add a screenshot__ +Now, the user is on the Checklist tab. +On the user's view, the user will see add task. +__// TODO: add a screenshot__ +This will allow the user to make a new checklist or add new tasks to an already existing checklist. + + +## Installation Instructions +As the user, you should ensure that their IDE is working and already setup. If you need any help setting up, +[visit here](https://www.jetbrains.com/help/idea/getting-started.html). The program can be run on any OS. +To run the program, there are two options: +1. __Through the IDE:__ + 1. First, you have to clone the repo, by using [this URL](https://github.com/PrishaPatel24/ZenTask.git). + 2. Then, run the MainNoteApplication found in the src/main/java/app of the project through the IDE. +2. __Through the terminal:__ + 1. First, you have to clone the repo, by using [this URL](https://github.com/PrishaPatel24/ZenTask.git). + 2. Navigate to the directory where the MainNoteApplication is on, in your File Manager + (ensure the directory is like .../ZenTask/src/main/java/app). + 3. Then, open the terminal there. + 4. In you terminal, compile the program by using `javac MainNoteApplication.java` + 5. Then, run the application, using `java MainNoteApplication` + 6. You should see your Dashboard. __// TODO: add a screenshot__ + +## Usage Guide +To use the software, the user should navigate the tabs using the menu found on the left of the view. +__// TODO: add a screenshot__ + +## License +To view the License consistent with the details and use of this program, please refer to the file name **LICENSE** in the project directory. + +## Feedback +As a user, you can give feedback on the software, using [this link](https://forms.gle/mA2Q9dh3G84v8RdW7). +Please, take your time to carefully fill out the form and should expect a follow-up with 2-3 business days. + +## Contributions +All contributions to the project are welcome. +To contribute to the project, you have to follow these steps: +1. Make a fork of the [project](https://github.com/PrishaPatel24/ZenTask) on GitHub by right-clicking _Fork_ found on the top right of the GitHub page. +Then, create a new fork. __// TODO: add a screenshot__ +2. Then, clone the repo and work from your IDE. +3. Do not forget to add a branch protection rule, especially if you are working with other people. Ensure everyone has their branch. +Then, they will need to make a pull request on your own fork of the repository ([do this through the IDE](https://www.jetbrains.com/help/idea/work-with-github-pull-requests.html#create-pull-request)), +explaining in detail what changes they have made, and perform a [code review](https://swimm.io/learn/code-reviews/ultimate-10-step-code-review-checklist). +4. To add your contributions to this project, you have to make a merge request. +5. To make a merge request, first navigate to the back to [this project's repository](https://github.com/PrishaPatel24/ZenTask). +6. Then, on top of the GitHub page, click on _Pull Requests_. __// TODO: add a screenshot__ +7. Click on the green button on the left of the GitHub page to create a _New pull request_. +8. Then, set the base repository to [this project's repository](https://github.com/PrishaPatel24/ZenTask) and set base to _main_. +9. Similarly, set the head repository to be your fork of the repository and set base to main. +10. Then, click on the green button, _Create pull request_. __// TODO: add a screenshot__ +11. In the pull request, add a title that properly summarizes the content of the "contribution". +Also, add a proper description that contain what changes you made or things you added, what files your worked on and why this contribution is meaningful and beneficial. diff --git a/accessibility-report.md b/accessibility-report.md new file mode 100644 index 000000000..05f09c34d --- /dev/null +++ b/accessibility-report.md @@ -0,0 +1,39 @@ +## 1. Principles of Universal Design +- Equitable Use: The translation feature allows users who speak other languages to interact with our program. Since the + interface is easy to use and straightforward, users with varying levels of technical knowledge can use the program. +- Flexibility in Use: The program has multiple functionalities, including note-taking, checklists for tasks, a calendar + for event management, language translation, and AI-assisted completion. Users can choose how they interact with the + program, whether they organize tasks, translate content, or write notes. This accommodates different workflows and + user needs. +- Simple and Intuitive Use: The program has clearly-labeled buttons like "Translate", "Complete Notes With AI", and "Add + Task" so that users can intuitively navigate the app. The checklist and calendar are straightforward and allow users to + add tasks or events without much effort. +- Perceptible Information: Information is presented visually through text areas, dropdown menus, and buttons, which makes + the program easy to understand. For example, the dropdown menu for language selection is simple and easy to locate, + and the translation output is displayed in a separate area so that users can distinguish between the original text and + the translated text. +- Tolerance for Error: If users attempt to translate notes that are too long, the program provides feedback ("Exceeded + character limit") instead of crashing. The checklist and calendar features allow users to update or remove entries so + that mistakes can be easily fixed. +- Low Physical Effort: Tasks such as adding events to the calendar, updating the checklist, or translating text are + simplified and made more efficient for the user. The AI-assisted note completion feature helps users save time and effort + by automatically filling in ideas or suggestions, so that users don’t have to type it all out themselves. +- Size and Space for Approach and Use: The program can be used on standard desktop or laptop screens and has well-spaced + buttons and input fields that are easy to access. The dropdown menu and text areas are appropriately sized so that it's + user-friendly. + +## 2. Marketing +If we were to sell or license ZenTask to customers, we would primarily market our program to students and professionals. +Since it has note-taking and translation features, our program would be particularly useful for multilingual users, +exchange students or students studying foreign languages, and professionals who are working in multicultural environments +or abroad. The "Complete Notes with AI" feature is helpful for students and writers needing additional assistance, and +the checklist and calendar features can be used by anyone with a busy schedule that could benefit from organizing their +tasks, deadlines, and scheduled events. + +## 3. Demographic Challenges +This program may be less likely to be used by those who are visually impaired since this program does not have +screen readers or voice navigation to make it more suitable for those users. Implementing a text-to-speech feature +would help reach a wider audience of users and make it more accessible. This program is also less likely to be +used by those with dyslexia or reading impairments since it does not include spell-checking and the size of the text cannot +be increased. To accommodate for these users, in the future we could implement a feature that allows people to create +text by speaking instead of typing. diff --git a/pom.xml b/pom.xml index 527f61e36..322ba7eb1 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,98 @@ note-application 1.0-SNAPSHOT + + + central + https://repo.maven.apache.org/maven2 + + + google-maven-repository + https://maven-central.storage-download.googleapis.com/maven2/ + + + dynomakeRepository + https://maven.dynomake.space/releases + + + + + io.github.cdimascio + dotenv-java + 3.0.0 + + + + com.cohere + cohere-java + 1.4.1 + + + + com.fasterxml.jackson.core + jackson-databind + 2.13.0 + + + + com.fasterxml.jackson.core + jackson-core + 2.13.0 + + + com.fasterxml.jackson.core + jackson-annotations + 2.13.0 + + + + com.google.oauth-client + google-oauth-client + 1.34.1 + + + + com.google.api-client + google-api-client + 1.34.0 + + + + com.google.oauth-client + google-oauth-client-java6 + 1.23.0 + + + + com.google.oauth-client + google-oauth-client-jetty + 1.34.1 + + + + com.google.apis + google-api-services-calendar + v3-rev20240705-2.0.0 + + + + com.google.http-client + google-http-client-jackson2 + 1.34.0 + + + + org.mortbay.jetty + jetty + 6.1.26 + + + + com.google.cloud + google-cloud-aiplatform + 3.35.0 + org.json @@ -27,6 +118,11 @@ 4.13.1 test + + space.dynomake + libretranslate-java + 1.0.9 + diff --git a/src/main/java/app/MainNoteApplication.java b/src/main/java/app/MainNoteApplication.java index c37860156..04b6fa36b 100644 --- a/src/main/java/app/MainNoteApplication.java +++ b/src/main/java/app/MainNoteApplication.java @@ -1,56 +1,163 @@ package app; -import data_access.DBNoteDataAccessObject; -import use_case.note.NoteDataAccessInterface; +import data_access.InMemoryNoteDataAccessObject; +import interface_adapter.add_task.AddTaskController; +import interface_adapter.add_task.AddTaskPresenter; +import interface_adapter.add_task.TaskViewModel; + +import interface_adapter.note.NoteController; +import interface_adapter.note.NotePresenter; +import interface_adapter.note.NoteViewModel; + +import interface_adapter.ai.AiController; +import interface_adapter.ai.AiPresenter; + +import interface_adapter.calendar.CalendarController; +import interface_adapter.calendar.CalendarPresenter; +import interface_adapter.translation.TranslationController; +import interface_adapter.translation.TranslationPresenter; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; +import java.awt.*; +import java.io.IOException; +import java.security.GeneralSecurityException; + +import use_cases.add_task.AddTaskInputBoundary; +import use_cases.add_task.AddTaskInteractor; +import use_cases.add_task.AddTaskOutputBoundary; + +import use_cases.note.NoteInputBoundary; +import use_cases.note.NoteInteractor; +import use_cases.note.NoteOutputBoundary; + +import use_cases.ai.AiInputBoundary; +import use_cases.ai.AiInteractor; +import use_cases.ai.AiOutputBoundary; +import use_cases.ai.AiRequest; + +import use_cases.calendar.CalendarInputBoundary; +import use_cases.calendar.CalendarInteractor; +import use_cases.calendar.CalendarOutputBoundary; +import use_cases.calendar.CalendarRequest; + +import use_cases.note.TranslationInputBoundary; +import use_cases.note.TranslationInteractor; +import use_cases.note.TranslationOutputBoundary; + +import view.CalendarView; +import view.ChecklistView; +import view.DashboardView; +import view.NotesView; /** - * An application where we can view and add to a note stored by a user. - *

- * This is a minimal example of using the password-protected user API from lab 5, - * but demonstrating the endpoint allowing you to store an arbitrary JSON object. - * This functionality could be used in any project where your team wants to persist - * data which is then accessible across devices.

- *

The code is intentionally somewhat incomplete to leave work to be done if your - * team were to choose to work on a project which would require similar functionality. - * For example, we have intentionally not created a full "Note" entity here, but - * rather just represented a note as a string. - *

- * The ViewManager code has also been removed, since this minimal program only requires a single - * view. Your team may wish to bring back the ViewManager or make your own implementation of supporting - * switching between views depending on your project. + * The main application to boot the program. */ public class MainNoteApplication { + static final int WIDTH = 1100; + static final int HEIGHT = 500; - /** - * The main entry point of the application. - *

- * The program will show you the note currently saved in the system. - * You are able to edit it and then save it to the system. You can refresh - * to update the note to reflect what was saved most recently. This - * uses the API from lab, so there is one database storing the note, - * which means that if anyone updates the note, that is what you will - * see when you refresh. - *

- * You can generalize the code to allow you to - * specify which "user" to save the note for, which will allow your team - * to store information specific to your team which is password-protected. - * The username and password used in this application are currently for - * user jonathan_calver2, but you can change that. As you did in lab 3, - * you will likely want to store password information locally rather than - * in your repo. Or you can require the user to enter their credentials - * in your application; it just depends on what your program's main - * functionality. - *

- * @param args commandline arguments are ignored - */ public static void main(String[] args) { + SwingUtilities.invokeLater(() -> { + final CardLayout cardLayout = new CardLayout(); + final JPanel cardPanel = new JPanel(cardLayout); + + final JPanel dashboardPanel = new DashboardView(); + final JPanel notesPanel = createNotes(); + final JPanel calendarPanel; + try { + calendarPanel = createCalendar(); + } + catch (GeneralSecurityException | IOException exception) { + throw new RuntimeException(exception); + } + final JPanel checklistPanel = createChecklist(); + + cardPanel.add(dashboardPanel, "Dashboard"); + cardPanel.add(notesPanel, "Notes"); + cardPanel.add(calendarPanel, "Calendar"); + cardPanel.add(checklistPanel, "Checklist"); - // create the data access and inject it into our builder! - final NoteDataAccessInterface noteDataAccess = new DBNoteDataAccessObject(); + final JPanel buttonPanel = getButtonPanel(cardLayout, cardPanel); + + final JPanel mainPanel = new JPanel(new BorderLayout()); + mainPanel.add(buttonPanel, BorderLayout.WEST); + mainPanel.add(cardPanel, BorderLayout.CENTER); + + final JFrame frame = new JFrame("ZenTask"); + frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); + frame.setSize(WIDTH, HEIGHT); + frame.setContentPane(mainPanel); + frame.setVisible(true); + }); + } + + @NotNull + private static JPanel getButtonPanel(CardLayout cardLayout, JPanel cardPanel) { + final JButton showCalendarButton = new JButton("Calendar"); + showCalendarButton.addActionListener(event -> cardLayout.show(cardPanel, "Calendar")); + + final JButton showNotesButton = new JButton("Notes"); + showNotesButton.addActionListener(event -> cardLayout.show(cardPanel, "Notes")); + + final JButton showChecklistButton = new JButton("Checklist"); + showChecklistButton.addActionListener(event -> cardLayout.show(cardPanel, "Checklist")); + + final JPanel buttonPanel = new JPanel(); + buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.Y_AXIS)); + + buttonPanel.add(showCalendarButton); + buttonPanel.add(showNotesButton); + buttonPanel.add(showChecklistButton); + return buttonPanel; + } + + private static JPanel createDashboard() { + final JPanel dashboardPanel = new JPanel(); + dashboardPanel.add(new JLabel("Welcome to your Dashboard")); + return dashboardPanel; + } + + private static JPanel createCalendar() throws GeneralSecurityException, IOException { + final CalendarView calendarView = new CalendarView(); + final CalendarOutputBoundary calendarPresenter = new CalendarPresenter(calendarView); + final CalendarInputBoundary calendarInteractor = new CalendarInteractor(calendarPresenter, new CalendarRequest()); + final CalendarController calendarController = new CalendarController(calendarInteractor); + calendarView.setCalendarController(calendarController); + return calendarView; + } + + private static JPanel createNotes() { + final NoteViewModel noteViewModel = new NoteViewModel(); + final NoteOutputBoundary notePresenter = new NotePresenter(noteViewModel); + final InMemoryNoteDataAccessObject inMemoryNoteDAO = new InMemoryNoteDataAccessObject(); + final NoteInputBoundary noteInteractor = new NoteInteractor(notePresenter, inMemoryNoteDAO); + final NoteController controller = new NoteController(noteInteractor); + final NotesView notesView = new NotesView(noteViewModel); + notesView.setNoteController(controller); + + final AiOutputBoundary aiPresenter = new AiPresenter(notesView); + final AiRequest aiRequest = new AiRequest(); + final AiInputBoundary aiInteractor = new AiInteractor(aiPresenter, aiRequest); + final AiController aiController = new AiController(aiInteractor); + notesView.setAiController(aiController); + + final TranslationOutputBoundary translationPresenter = new TranslationPresenter(notesView); + final TranslationInputBoundary translationInteractor = new TranslationInteractor(translationPresenter); + final TranslationController translationController = new TranslationController(translationInteractor); + notesView.setTranslationController(translationController); + + return notesView; + } - final NoteAppBuilder builder = new NoteAppBuilder(); - builder.addNoteDAO(noteDataAccess) - .addNoteView() - .addNoteUseCase().build().setVisible(true); + private static JPanel createChecklist() { + final TaskViewModel taskViewModel = new TaskViewModel(); + final AddTaskOutputBoundary addTaskPresenter = new AddTaskPresenter(taskViewModel); + final AddTaskInputBoundary addTaskUseCaseInteractor = new AddTaskInteractor(addTaskPresenter); + final AddTaskController controller = new AddTaskController(addTaskUseCaseInteractor); + final ChecklistView checklistView = new ChecklistView(taskViewModel); + checklistView.setTaskController(controller); + taskViewModel.firePropertyChanged(); + return checklistView; } } diff --git a/src/main/java/app/NoteAppBuilder.java b/src/main/java/app/NoteAppBuilder.java deleted file mode 100644 index a68cb9ad6..000000000 --- a/src/main/java/app/NoteAppBuilder.java +++ /dev/null @@ -1,83 +0,0 @@ -package app; - -import javax.swing.JFrame; -import javax.swing.WindowConstants; - -import interface_adapter.note.NoteController; -import interface_adapter.note.NotePresenter; -import interface_adapter.note.NoteViewModel; -import use_case.note.NoteDataAccessInterface; -import use_case.note.NoteInteractor; -import use_case.note.NoteOutputBoundary; -import view.NoteView; - -/** - * Builder for the Note Application. - */ -public class NoteAppBuilder { - public static final int HEIGHT = 300; - public static final int WIDTH = 400; - private NoteDataAccessInterface noteDAO; - private NoteViewModel noteViewModel = new NoteViewModel(); - private NoteView noteView; - private NoteInteractor noteInteractor; - - /** - * Sets the NoteDAO to be used in this application. - * @param noteDataAccess the DAO to use - * @return this builder - */ - public NoteAppBuilder addNoteDAO(NoteDataAccessInterface noteDataAccess) { - noteDAO = noteDataAccess; - return this; - } - - /** - * Creates the objects for the Note Use Case and connects the NoteView to its - * controller. - *

This method must be called after addNoteView!

- * @return this builder - * @throws RuntimeException if this method is called before addNoteView - */ - public NoteAppBuilder addNoteUseCase() { - final NoteOutputBoundary noteOutputBoundary = new NotePresenter(noteViewModel); - noteInteractor = new NoteInteractor( - noteDAO, noteOutputBoundary); - - final NoteController controller = new NoteController(noteInteractor); - if (noteView == null) { - throw new RuntimeException("addNoteView must be called before addNoteUseCase"); - } - noteView.setNoteController(controller); - return this; - } - - /** - * Creates the NoteView and underlying NoteViewModel. - * @return this builder - */ - public NoteAppBuilder addNoteView() { - noteViewModel = new NoteViewModel(); - noteView = new NoteView(noteViewModel); - return this; - } - - /** - * Builds the application. - * @return the JFrame for the application - */ - public JFrame build() { - final JFrame frame = new JFrame(); - frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); - frame.setTitle("Note Application"); - frame.setSize(WIDTH, HEIGHT); - - frame.add(noteView); - - // refresh so that the note will be visible when we start the program - noteInteractor.executeRefresh(); - - return frame; - - } -} diff --git a/src/main/java/data_access/DBNoteDataAccessObject.java b/src/main/java/data_access/DBNoteDataAccessObject.java deleted file mode 100644 index dadb0cab0..000000000 --- a/src/main/java/data_access/DBNoteDataAccessObject.java +++ /dev/null @@ -1,107 +0,0 @@ -package data_access; - -import java.io.IOException; - -import org.json.JSONException; -import org.json.JSONObject; - -import entity.User; -import okhttp3.MediaType; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.Response; -import use_case.note.DataAccessException; -import use_case.note.NoteDataAccessInterface; - -/** - * The DAO for accessing notes stored in the database. - *

This class demonstrates how your group can use the password-protected user - * endpoints of the API used in lab 5 to store persistent data in your program. - *

- *

You can also refer to the lab 5 code for signing up a new user and other use cases. - *

- * See - * - * the documentation - * of the API for more details. - */ -public class DBNoteDataAccessObject implements NoteDataAccessInterface { - private static final int SUCCESS_CODE = 200; - private static final int CREDENTIAL_ERROR = 401; - private static final String CONTENT_TYPE_LABEL = "Content-Type"; - private static final String CONTENT_TYPE_JSON = "application/json"; - private static final String STATUS_CODE_LABEL = "status_code"; - private static final String USERNAME = "username"; - private static final String PASSWORD = "password"; - private static final String MESSAGE = "message"; - - @Override - public String saveNote(User user, String note) throws DataAccessException { - final OkHttpClient client = new OkHttpClient().newBuilder() - .build(); - - // POST METHOD - final MediaType mediaType = MediaType.parse(CONTENT_TYPE_JSON); - final JSONObject requestBody = new JSONObject(); - requestBody.put(USERNAME, user.getName()); - requestBody.put(PASSWORD, user.getPassword()); - final JSONObject extra = new JSONObject(); - extra.put("note", note); - requestBody.put("info", extra); - final RequestBody body = RequestBody.create(requestBody.toString(), mediaType); - final Request request = new Request.Builder() - .url("http://vm003.teach.cs.toronto.edu:20112/modifyUserInfo") - .method("PUT", body) - .addHeader(CONTENT_TYPE_LABEL, CONTENT_TYPE_JSON) - .build(); - try { - final Response response = client.newCall(request).execute(); - - final JSONObject responseBody = new JSONObject(response.body().string()); - - if (responseBody.getInt(STATUS_CODE_LABEL) == SUCCESS_CODE) { - return loadNote(user); - } - else if (responseBody.getInt(STATUS_CODE_LABEL) == CREDENTIAL_ERROR) { - throw new DataAccessException("message could not be found or password was incorrect"); - } - else { - throw new DataAccessException("database error: " + responseBody.getString(MESSAGE)); - } - } - catch (IOException | JSONException ex) { - throw new DataAccessException(ex.getMessage()); - } - } - - @Override - public String loadNote(User user) throws DataAccessException { - // Make an API call to get the user object. - final String username = user.getName(); - final OkHttpClient client = new OkHttpClient().newBuilder().build(); - final Request request = new Request.Builder() - .url(String.format("http://vm003.teach.cs.toronto.edu:20112/user?username=%s", username)) - .addHeader("Content-Type", CONTENT_TYPE_JSON) - .build(); - try { - final Response response = client.newCall(request).execute(); - - final JSONObject responseBody = new JSONObject(response.body().string()); - - if (responseBody.getInt(STATUS_CODE_LABEL) == SUCCESS_CODE) { - final JSONObject userJSONObject = responseBody.getJSONObject("user"); - final JSONObject data = userJSONObject.getJSONObject("info"); - return data.getString("note"); - } - else { - throw new DataAccessException(responseBody.getString(MESSAGE)); - } - } - catch (IOException | JSONException ex) { - throw new RuntimeException(ex); - } - } -} diff --git a/src/main/java/data_access/InMemoryNoteDataAccessObject.java b/src/main/java/data_access/InMemoryNoteDataAccessObject.java new file mode 100644 index 000000000..15958b748 --- /dev/null +++ b/src/main/java/data_access/InMemoryNoteDataAccessObject.java @@ -0,0 +1,31 @@ +package data_access; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import entity.Note; +import use_cases.note.NoteDataAccessInterface; + +/** + * This class implements storage of notes within a single run of the program. + * This does not persist data between runs of the program. + */ +public class InMemoryNoteDataAccessObject implements NoteDataAccessInterface { + private final Map notes; + + public InMemoryNoteDataAccessObject() { + this.notes = new HashMap<>(); + } + + @Override + public void saveNote(String title, String note) { + this.notes.put(title, note); + } + + @Override + public List getNotes() { + return new ArrayList<>(this.notes.keySet()); + } +} diff --git a/src/main/java/entity/Events.java b/src/main/java/entity/Events.java new file mode 100644 index 000000000..8104bd544 --- /dev/null +++ b/src/main/java/entity/Events.java @@ -0,0 +1,37 @@ +package entity; + +import com.google.api.services.calendar.model.EventDateTime; + +/** + * The representation of a calendar event for our program. + */ +public class Events { + + private final String name; + private final EventDateTime datetime; + private final String location; + private final String notes; + + public Events(String name, EventDateTime datetime, String location, String notes) { + this.name = name; + this.datetime = datetime; + this.location = location; + this.notes = notes; + } + + public String getEventName() { + return name; + } + + public String getEventDatetime() { + return datetime.getDateTime().toString(); + } + + public String getEventLocation() { + return location; + } + + public String getEventNotes() { + return notes; + } +} diff --git a/src/main/java/entity/Note.java b/src/main/java/entity/Note.java new file mode 100644 index 000000000..1e90e7415 --- /dev/null +++ b/src/main/java/entity/Note.java @@ -0,0 +1,32 @@ +package entity; + +/** + * The representation of a note file for this program. + */ +public class Note { + + private String content; + private String title; + + public Note(String content, String title) { + this.content = content; + this.title = title; + } + + public String getContent() { + return content; + } + + public String getTitle() { + return title; + } + + public void setContent(String newContent) { + this.content = newContent; + } + + public void setTitle(String newTitle) { + this.title = newTitle; + } + +} diff --git a/src/main/java/entity/Task.java b/src/main/java/entity/Task.java new file mode 100644 index 000000000..39076e8f3 --- /dev/null +++ b/src/main/java/entity/Task.java @@ -0,0 +1,18 @@ +package entity; + +/** + * The representation of a task for this program. + */ +public class Task { + + private final String description; + + public Task(String description) { + this.description = description; + } + + public String getDescription() { + return description; + } + +} diff --git a/src/main/java/entity/User.java b/src/main/java/entity/User.java deleted file mode 100644 index e0c57e9a6..000000000 --- a/src/main/java/entity/User.java +++ /dev/null @@ -1,24 +0,0 @@ -package entity; - -/** - * The representation of a password-protected user for our program. - */ -public class User { - - private final String name; - private final String password; - - public User(String name, String password) { - this.name = name; - this.password = password; - } - - public String getName() { - return name; - } - - public String getPassword() { - return password; - } - -} diff --git a/src/main/java/interface_adapter/add_task/AddTaskController.java b/src/main/java/interface_adapter/add_task/AddTaskController.java new file mode 100644 index 000000000..f9c41b674 --- /dev/null +++ b/src/main/java/interface_adapter/add_task/AddTaskController.java @@ -0,0 +1,24 @@ +package interface_adapter.add_task; + +import use_cases.add_task.AddTaskInputBoundary; +import use_cases.add_task.AddTaskInputData; + +/** + * The controller for the add_task Use Case. + */ +public class AddTaskController { + private final AddTaskInputBoundary addTaskUseCaseInteractor; + + public AddTaskController(AddTaskInputBoundary addTaskUseCaseInteractor) { + this.addTaskUseCaseInteractor = addTaskUseCaseInteractor; + } + + /** + * Executes the add_task Use Case. + * @param description the task description + */ + public void execute(String description) { + final AddTaskInputData addTaskInputData = new AddTaskInputData(description); + addTaskUseCaseInteractor.execute(addTaskInputData); + } +} diff --git a/src/main/java/interface_adapter/add_task/AddTaskPresenter.java b/src/main/java/interface_adapter/add_task/AddTaskPresenter.java new file mode 100644 index 000000000..4596fbb99 --- /dev/null +++ b/src/main/java/interface_adapter/add_task/AddTaskPresenter.java @@ -0,0 +1,34 @@ +package interface_adapter.add_task; + +import use_cases.add_task.AddTaskOutputBoundary; +import use_cases.add_task.AddTaskOutputData; + +/** + * The presenter for the add_task use case. + */ +public class AddTaskPresenter implements AddTaskOutputBoundary { + private final TaskViewModel taskViewModel; + + public AddTaskPresenter(TaskViewModel taskViewModel) { + this.taskViewModel = taskViewModel; + } + + @Override + public void prepareSuccessView(AddTaskOutputData response) { + // On success, add task to the view. + + final TaskState taskState = taskViewModel.getState(); + taskState.addTask(response.getDescription()); + taskState.setError(null); + this.taskViewModel.setState(taskState); + taskViewModel.firePropertyChanged(); + } + + // Will not need fail view (there's no way to fail adding a task) + @Override + public void prepareFailView(String error) { + final TaskState taskState = taskViewModel.getState(); + taskState.setError(error); + taskViewModel.firePropertyChanged(); + } +} diff --git a/src/main/java/interface_adapter/add_task/TaskState.java b/src/main/java/interface_adapter/add_task/TaskState.java new file mode 100644 index 000000000..5c93ad91e --- /dev/null +++ b/src/main/java/interface_adapter/add_task/TaskState.java @@ -0,0 +1,38 @@ +package interface_adapter.add_task; + +import java.util.ArrayList; +import java.util.List; + +import entity.Task; + +/** + * The State for a task. + */ +public class TaskState { + private List tasks; + private String error; + + public TaskState() { + tasks = new ArrayList<>(); + } + + public List getList() { + return tasks; + } + + /** + * Adds new Task object to the list of tasks. + * @param description of the task to be added + */ + public void addTask(String description) { + this.tasks.add(new Task(description)); + } + + public void setError(String errorMessage) { + this.error = errorMessage; + } + + public String getError() { + return error; + } +} diff --git a/src/main/java/interface_adapter/add_task/TaskViewModel.java b/src/main/java/interface_adapter/add_task/TaskViewModel.java new file mode 100644 index 000000000..378a969bc --- /dev/null +++ b/src/main/java/interface_adapter/add_task/TaskViewModel.java @@ -0,0 +1,13 @@ +package interface_adapter.add_task; + +import interface_adapter.ViewModel; + +/** + * The ViewModel for the TaskView. + */ +public class TaskViewModel extends ViewModel { + public TaskViewModel() { + super("task"); + setState(new TaskState()); + } +} \ No newline at end of file diff --git a/src/main/java/interface_adapter/ai/AiController.java b/src/main/java/interface_adapter/ai/AiController.java new file mode 100644 index 000000000..764d0581a --- /dev/null +++ b/src/main/java/interface_adapter/ai/AiController.java @@ -0,0 +1,23 @@ +package interface_adapter.ai; + +import entity.Note; +import use_cases.ai.AiInputBoundary; + +/** + * The controller for the AI use case. + */ +public class AiController { + private final AiInputBoundary aiInteractor; + + public AiController(AiInputBoundary aiInputBoundary) { + this.aiInteractor = aiInputBoundary; + } + + /** + * Generates the AI response from a given note. + * @param note The note in the JTextArea written by the user. + */ + public void generateResponse(Note note) { + aiInteractor.generateResponse(note); + } +} diff --git a/src/main/java/interface_adapter/ai/AiPresenter.java b/src/main/java/interface_adapter/ai/AiPresenter.java new file mode 100644 index 000000000..e9137d935 --- /dev/null +++ b/src/main/java/interface_adapter/ai/AiPresenter.java @@ -0,0 +1,24 @@ +package interface_adapter.ai; + +import entity.Note; +import use_cases.ai.AiOutputBoundary; +import view.NotesView; + +/** + * The stupid AiPresenter for this use case, intended to update the view. + */ +public class AiPresenter implements AiOutputBoundary { + private final NotesView notesView; + + public AiPresenter(NotesView notesView) { + this.notesView = notesView; + } + + /** + * Tasked with updating the view of the program with the response. + * @param note The final output from the AI. + */ + public void updateNote(Note note) { + notesView.displayNewNote(note); + } +} diff --git a/src/main/java/interface_adapter/calendar/CalendarController.java b/src/main/java/interface_adapter/calendar/CalendarController.java new file mode 100644 index 000000000..0513939df --- /dev/null +++ b/src/main/java/interface_adapter/calendar/CalendarController.java @@ -0,0 +1,27 @@ +package interface_adapter.calendar; + +import use_cases.calendar.CalendarInputBoundary; + +import java.io.IOException; +import java.security.GeneralSecurityException; + +/** + * The controller for the calendar use case. + */ +public class CalendarController { + private final CalendarInputBoundary calendarUseCaseInteractor; + + public CalendarController(CalendarInputBoundary calendarUseCaseInteractor) { + this.calendarUseCaseInteractor = calendarUseCaseInteractor; + } + + /** + * Executes the calendar Use Case. + * @param email the email of user. + * @throws GeneralSecurityException for Google account. + * @throws IOException for Google login. + */ + public void execute(String email) throws GeneralSecurityException, IOException { + calendarUseCaseInteractor.execute(email); + } +} diff --git a/src/main/java/interface_adapter/calendar/CalendarPresenter.java b/src/main/java/interface_adapter/calendar/CalendarPresenter.java new file mode 100644 index 000000000..f8bd4ced4 --- /dev/null +++ b/src/main/java/interface_adapter/calendar/CalendarPresenter.java @@ -0,0 +1,22 @@ +package interface_adapter.calendar; + +import entity.Events; +import use_cases.calendar.CalendarOutputBoundary; +import view.CalendarView; + +import java.util.List; + +/** + * The presenter for the calendar use case. + */ +public class CalendarPresenter implements CalendarOutputBoundary { + private CalendarView calendarView; + + public CalendarPresenter(CalendarView calendarView) { + this.calendarView = calendarView; + } + + public void prepareSuccessView(List events) { + calendarView.displayEvents(events); + } +} diff --git a/src/main/java/interface_adapter/note/NoteController.java b/src/main/java/interface_adapter/note/NoteController.java index e3e5dfb32..86dde9d7f 100644 --- a/src/main/java/interface_adapter/note/NoteController.java +++ b/src/main/java/interface_adapter/note/NoteController.java @@ -1,6 +1,7 @@ package interface_adapter.note; -import use_case.note.NoteInputBoundary; +import use_cases.note.NoteInputBoundary; +import use_cases.note.NoteInputData; /** * Controller for our Note related Use Cases. @@ -15,14 +16,12 @@ public NoteController(NoteInputBoundary noteInteractor) { /** * Executes the Note related Use Cases. - * @param note the note to be recorded + * @param content the content of the note being worked on + * @param title the title of the note */ - public void execute(String note) { - if (note != null) { - noteInteractor.executeSave(note); - } - else { - noteInteractor.executeRefresh(); - } + public void execute(String content, String title) { + final NoteInputData newNote = new NoteInputData(content, title); + noteInteractor.execute(newNote); } + } diff --git a/src/main/java/interface_adapter/note/NotePresenter.java b/src/main/java/interface_adapter/note/NotePresenter.java index d4e416165..8dac7c53e 100644 --- a/src/main/java/interface_adapter/note/NotePresenter.java +++ b/src/main/java/interface_adapter/note/NotePresenter.java @@ -1,6 +1,6 @@ package interface_adapter.note; -import use_case.note.NoteOutputBoundary; +import use_cases.note.NoteOutputBoundary; /** * The presenter for our Note viewing and editing program. diff --git a/src/main/java/interface_adapter/note/NoteState.java b/src/main/java/interface_adapter/note/NoteState.java index c5b2234d6..a44b799f4 100644 --- a/src/main/java/interface_adapter/note/NoteState.java +++ b/src/main/java/interface_adapter/note/NoteState.java @@ -2,7 +2,6 @@ /** * The State for a note. - *

For this example, a note is simplay a string.

*/ public class NoteState { private String note = ""; diff --git a/src/main/java/interface_adapter/translation/TranslationController.java b/src/main/java/interface_adapter/translation/TranslationController.java new file mode 100644 index 000000000..63b3e4b61 --- /dev/null +++ b/src/main/java/interface_adapter/translation/TranslationController.java @@ -0,0 +1,25 @@ +package interface_adapter.translation; + +import use_cases.note.TranslationInputData; +import use_cases.note.TranslationInputBoundary; + +/** + * Controller for translation use case. + */ +public class TranslationController { + private final TranslationInputBoundary translationInteractor; + + public TranslationController(TranslationInputBoundary translationInteractor) { + this.translationInteractor = translationInteractor; + } + + /** + * Handles the user clicking the translate button. + * @param textToTranslate text to be translated + * @param selectedLanguage language to translate to + */ + public void translateNote(String textToTranslate, String selectedLanguage) { + final TranslationInputData inputData = new TranslationInputData(textToTranslate, selectedLanguage); + translationInteractor.translateNote(inputData); + } +} diff --git a/src/main/java/interface_adapter/translation/TranslationPresenter.java b/src/main/java/interface_adapter/translation/TranslationPresenter.java new file mode 100644 index 000000000..b18171e24 --- /dev/null +++ b/src/main/java/interface_adapter/translation/TranslationPresenter.java @@ -0,0 +1,25 @@ +package interface_adapter.translation; + +import use_cases.note.TranslationOutputData; +import use_cases.note.TranslationOutputBoundary; +import view.NotesView; + +/** + * Presenter for translation use case. + */ +public class TranslationPresenter implements TranslationOutputBoundary { + private final NotesView notesView; + + public TranslationPresenter(NotesView notesView) { + this.notesView = notesView; + } + + /** + * updates translation. + * @param outputData translated content + */ + public void updateTranslation(TranslationOutputData outputData) { + final String translatedText = outputData.getTranslatedText(); + notesView.updateTranslation(translatedText); + } +} diff --git a/src/main/java/use_case/note/DataAccessException.java b/src/main/java/use_case/note/DataAccessException.java deleted file mode 100644 index b8c17920d..000000000 --- a/src/main/java/use_case/note/DataAccessException.java +++ /dev/null @@ -1,10 +0,0 @@ -package use_case.note; - -/** - * Exception thrown when there is an error with accessing data. - */ -public class DataAccessException extends Exception { - public DataAccessException(String string) { - super(string); - } -} diff --git a/src/main/java/use_case/note/NoteDataAccessInterface.java b/src/main/java/use_case/note/NoteDataAccessInterface.java deleted file mode 100644 index b71597828..000000000 --- a/src/main/java/use_case/note/NoteDataAccessInterface.java +++ /dev/null @@ -1,30 +0,0 @@ -package use_case.note; - -import entity.User; - -/** - * Interface for the NoteDAO. It consists of methods for - * both loading and saving a note. - */ -public interface NoteDataAccessInterface { - - /** - * Saves a note for a given user. This will replace any existing note. - *

The password of the user must match that of the user saved in the system.

- * @param user the user information associated with the note - * @param note the note to be saved - * @return the contents of the note - * @throws DataAccessException if the user's note can not be saved for any reason - */ - String saveNote(User user, String note) throws DataAccessException; - - /** - * Returns the note associated with the user. The password - * is not checked, so anyone can read the information. - * @param user the user information associated with the note - * @return the contents of the note - * @throws DataAccessException if the user's note can not be loaded for any reason - */ - String loadNote(User user) throws DataAccessException; - -} diff --git a/src/main/java/use_case/note/NoteInteractor.java b/src/main/java/use_case/note/NoteInteractor.java deleted file mode 100644 index 369e9309a..000000000 --- a/src/main/java/use_case/note/NoteInteractor.java +++ /dev/null @@ -1,59 +0,0 @@ -package use_case.note; - -import entity.User; - -/** - * The "Use Case Interactor" for our two note-related use cases of refreshing - * the contents of the note and saving the contents of the note. Since they - * are closely related, we have combined them here for simplicity. - */ -public class NoteInteractor implements NoteInputBoundary { - - private final NoteDataAccessInterface noteDataAccessInterface; - private final NoteOutputBoundary noteOutputBoundary; - // Note: this program has it hardcoded which user object it is getting data for; - // you could change this if you wanted to generalize the code. For example, - // you might allow a user of the program to create a new note, which you - // could store as a "user" through the API OR you might maintain all notes - // in a JSON object stored in one common "user" stored through the API. - private final User user = new User("jonathan_calver2", "abc123"); - - public NoteInteractor(NoteDataAccessInterface noteDataAccessInterface, - NoteOutputBoundary noteOutputBoundary) { - this.noteDataAccessInterface = noteDataAccessInterface; - this.noteOutputBoundary = noteOutputBoundary; - } - - /** - * Executes the refresh note use case. - * - */ - @Override - public void executeRefresh() { - try { - - final String note = noteDataAccessInterface.loadNote(user); - noteOutputBoundary.prepareSuccessView(note); - } - catch (DataAccessException ex) { - noteOutputBoundary.prepareFailView(ex.getMessage()); - } - } - - /** - * Executes the save note use case. - * - * @param note the input data - */ - @Override - public void executeSave(String note) { - try { - - final String updatedNote = noteDataAccessInterface.saveNote(user, note); - noteOutputBoundary.prepareSuccessView(updatedNote); - } - catch (DataAccessException ex) { - noteOutputBoundary.prepareFailView(ex.getMessage()); - } - } -} diff --git a/src/main/java/use_cases/add_task/AddTaskInputBoundary.java b/src/main/java/use_cases/add_task/AddTaskInputBoundary.java new file mode 100644 index 000000000..6ce1c7d97 --- /dev/null +++ b/src/main/java/use_cases/add_task/AddTaskInputBoundary.java @@ -0,0 +1,12 @@ +package use_cases.add_task; + +/** + * Input Boundary for actions which are related to adding tasks. + */ +public interface AddTaskInputBoundary { + /** + * Executes the add_task use case. + * @param addTaskInputData the input data + */ + void execute(AddTaskInputData addTaskInputData); +} diff --git a/src/main/java/use_cases/add_task/AddTaskInputData.java b/src/main/java/use_cases/add_task/AddTaskInputData.java new file mode 100644 index 000000000..9bd23a36f --- /dev/null +++ b/src/main/java/use_cases/add_task/AddTaskInputData.java @@ -0,0 +1,13 @@ +package use_cases.add_task; + +public class AddTaskInputData { + private final String description; + + public AddTaskInputData(String description) { + this.description = description; + } + + String getDescription() { + return description; + } +} diff --git a/src/main/java/use_cases/add_task/AddTaskInteractor.java b/src/main/java/use_cases/add_task/AddTaskInteractor.java new file mode 100644 index 000000000..b35c20ef7 --- /dev/null +++ b/src/main/java/use_cases/add_task/AddTaskInteractor.java @@ -0,0 +1,19 @@ +package use_cases.add_task; + +/** + * The add_task Interactor. + */ +public class AddTaskInteractor implements AddTaskInputBoundary { + private final AddTaskOutputBoundary addTaskPresenter; + + public AddTaskInteractor(AddTaskOutputBoundary addTaskOutputBoundary) { + this.addTaskPresenter = addTaskOutputBoundary; + } + + @Override + public void execute(AddTaskInputData addTaskInputData) { + final String description = addTaskInputData.getDescription(); + final AddTaskOutputData addTaskOutputData = new AddTaskOutputData(description, false); + addTaskPresenter.prepareSuccessView(addTaskOutputData); + } +} diff --git a/src/main/java/use_cases/add_task/AddTaskOutputBoundary.java b/src/main/java/use_cases/add_task/AddTaskOutputBoundary.java new file mode 100644 index 000000000..f17bf92e0 --- /dev/null +++ b/src/main/java/use_cases/add_task/AddTaskOutputBoundary.java @@ -0,0 +1,18 @@ +package use_cases.add_task; + +/** + * The output boundary for the add_task Use Case. + */ +public interface AddTaskOutputBoundary { + /** + * Prepares the success view for the add_task Use Case. + * @param outputData the output data + */ + void prepareSuccessView(AddTaskOutputData outputData); + + /** + * Prepares the failure view for the add_task Use Case. + * @param errorMessage the explanation of the failure + */ + void prepareFailView(String errorMessage); +} diff --git a/src/main/java/use_cases/add_task/AddTaskOutputData.java b/src/main/java/use_cases/add_task/AddTaskOutputData.java new file mode 100644 index 000000000..723894028 --- /dev/null +++ b/src/main/java/use_cases/add_task/AddTaskOutputData.java @@ -0,0 +1,22 @@ +package use_cases.add_task; + +import entity.Task; + +/** + * Output Data for the add_task Use Case. + */ +public class AddTaskOutputData { + + private final Task task; + private final boolean useCaseFailed; + + public AddTaskOutputData(String description, boolean useCaseFailed) { + this.task = new Task(description); + this.useCaseFailed = useCaseFailed; + } + + public String getDescription() { + return task.getDescription(); + } + +} diff --git a/src/main/java/use_cases/ai/AiInputBoundary.java b/src/main/java/use_cases/ai/AiInputBoundary.java new file mode 100644 index 000000000..e6b0a8149 --- /dev/null +++ b/src/main/java/use_cases/ai/AiInputBoundary.java @@ -0,0 +1,15 @@ +package use_cases.ai; + +import entity.Note; + +/** + * AiInputBoundary involved in the AI prompt use case. + */ +public interface AiInputBoundary { + + /** + * Generates AI response. + * @param note The modified prompt going into the AI." + */ + void generateResponse(Note note); +} diff --git a/src/main/java/use_cases/ai/AiInteractor.java b/src/main/java/use_cases/ai/AiInteractor.java new file mode 100644 index 000000000..1ccae8ae0 --- /dev/null +++ b/src/main/java/use_cases/ai/AiInteractor.java @@ -0,0 +1,29 @@ +package use_cases.ai; + +import entity.Note; + +/** + * The class that will implement the different functions of AI in the program. + * It will generate a completed written version of the current note. + */ +public class AiInteractor implements AiInputBoundary { + private final AiOutputBoundary aiOutputBoundary; + private final AiRequest aiRequest; + + // take in note panel in attribute or in parameter to the method, defining interfaces, etc + // decide on functions for the calendar, load events, oauth 2 login, edit/modify events? + + public AiInteractor(AiOutputBoundary aiOutputBoundary, AiRequest aiRequest) { + this.aiOutputBoundary = aiOutputBoundary; + this.aiRequest = aiRequest; + } + + /** + * Auto-complete the current notes written by the user with AI. + * @param currNote The call to the AI to complete the current note. + */ + public void generateResponse(Note currNote) { + final String newNoteContent = aiRequest.generateNotes(currNote.getContent()); + aiOutputBoundary.updateNote(new Note(newNoteContent, "NA")); + } +} diff --git a/src/main/java/use_cases/ai/AiOutputBoundary.java b/src/main/java/use_cases/ai/AiOutputBoundary.java new file mode 100644 index 000000000..5e0db4c9f --- /dev/null +++ b/src/main/java/use_cases/ai/AiOutputBoundary.java @@ -0,0 +1,14 @@ +package use_cases.ai; + +import entity.Note; + +/** + * The output boundary for the AI use case. + */ +public interface AiOutputBoundary { + /** + * Add the AI generated completed note into the current note. + * @param note The final output from the AI. + */ + void updateNote(Note note); +} diff --git a/src/main/java/use_cases/ai/AiRequest.java b/src/main/java/use_cases/ai/AiRequest.java new file mode 100644 index 000000000..b10532fa9 --- /dev/null +++ b/src/main/java/use_cases/ai/AiRequest.java @@ -0,0 +1,30 @@ +package use_cases.ai; + +import java.util.List; + +import com.cohere.api.Cohere; +import com.cohere.api.requests.ChatRequest; +import com.cohere.api.types.NonStreamedChatResponse; + +/** + * This class handles the content in and out of the Cohere AI. + */ +public class AiRequest { + /** + * The method meant to polish user notes. + * @param prompt The current notes. + * @return The text result from the Cohere AI. + */ + public String generateNotes(String prompt) { + final String apiKey = "UBKSlAcJBhvwitbUnMrhGkutx3SkV2ZpSpMssN8h"; + final Cohere cohere = Cohere.builder().token(apiKey).clientName("snippet").build(); + + final NonStreamedChatResponse response = cohere.chat( + ChatRequest.builder() + .message("Improve these notes by editing for concision and " + + "adding more related content " + prompt) + .chatHistory( + List.of()).build()); + return response.getText(); + } +} diff --git a/src/main/java/use_cases/calendar/CalendarInputBoundary.java b/src/main/java/use_cases/calendar/CalendarInputBoundary.java new file mode 100644 index 000000000..cf88b1917 --- /dev/null +++ b/src/main/java/use_cases/calendar/CalendarInputBoundary.java @@ -0,0 +1,15 @@ +package use_cases.calendar; + +import java.io.IOException; +import java.security.GeneralSecurityException; + +/** + * The input boundary taking in an event. + */ +public interface CalendarInputBoundary { + /** + * Executes the calendar use case. + * @param email to be executed + */ + void execute(String email) throws GeneralSecurityException, IOException; +} diff --git a/src/main/java/use_cases/calendar/CalendarInteractor.java b/src/main/java/use_cases/calendar/CalendarInteractor.java new file mode 100644 index 000000000..d2c5d60f8 --- /dev/null +++ b/src/main/java/use_cases/calendar/CalendarInteractor.java @@ -0,0 +1,33 @@ +package use_cases.calendar; + +import entity.Events; +import com.google.api.services.calendar.model.Event; + +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.util.ArrayList; +import java.util.List; + +public class CalendarInteractor implements CalendarInputBoundary { + private final CalendarOutputBoundary calendarPresenter; + private final CalendarRequest calendarRequest; + private final List events; + + public CalendarInteractor(CalendarOutputBoundary calendarOutputBoundary, CalendarRequest calendarRequest) { + this.calendarPresenter = calendarOutputBoundary; + this.calendarRequest = calendarRequest; + this.events = new ArrayList(); + } + + public void execute(String email) throws GeneralSecurityException, IOException { + final List retResult = calendarRequest.getCalendarEvents(email); + for (Event value : retResult) { + final Events event = new Events(value.getSummary(), + value.getStart(), value.getLocation(), value.getDescription() + ); + events.add(event); + } + System.out.println("finished processing API"); + calendarPresenter.prepareSuccessView(events); + } +} diff --git a/src/main/java/use_cases/calendar/CalendarOutputBoundary.java b/src/main/java/use_cases/calendar/CalendarOutputBoundary.java new file mode 100644 index 000000000..3ad6f88cc --- /dev/null +++ b/src/main/java/use_cases/calendar/CalendarOutputBoundary.java @@ -0,0 +1,16 @@ +package use_cases.calendar; + +import java.util.List; + +import entity.Events; + +/** + * The output boundary for the calendar use case. + */ +public interface CalendarOutputBoundary { + /** + * Prepares the success view for the calendar Use Case. + * @param events the output data as a list of events + */ + void prepareSuccessView(List events); +} diff --git a/src/main/java/use_cases/calendar/CalendarRequest.java b/src/main/java/use_cases/calendar/CalendarRequest.java new file mode 100644 index 000000000..d26614809 --- /dev/null +++ b/src/main/java/use_cases/calendar/CalendarRequest.java @@ -0,0 +1,96 @@ +package use_cases.calendar; + +import java.io.*; +import java.security.GeneralSecurityException; +import java.util.Collections; +import java.util.List; + +import com.google.api.client.auth.oauth2.Credential; +import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp; +import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver; +import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow; +import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets; +import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport; +import com.google.api.client.http.javanet.NetHttpTransport; +import com.google.api.client.json.JsonFactory; +import com.google.api.client.json.gson.GsonFactory; +import com.google.api.client.util.DateTime; +import com.google.api.client.util.store.FileDataStoreFactory; +import com.google.api.services.calendar.Calendar; +import com.google.api.services.calendar.CalendarScopes; +import com.google.api.services.calendar.model.Event; +import com.google.api.services.calendar.model.Events; +import com.google.auth.http.HttpCredentialsAdapter; +import com.google.auth.oauth2.GoogleCredentials; + +// keep the unused imports for now, this whole import api stuff for google is so +// finicky that i almost had a shed a tear when i realized all the imports were finally in order + +/** + * A sample request to the calendar. + */ +public class CalendarRequest { + /** + * Application name. + */ + private static final String APPLICATION_NAME = "Google Calendar API Java Quickstart"; + /** + * Global instance of the JSON factory. + */ + private static final JsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance(); + /** + * Directory to store authorization tokens for this application. + */ + private static final String SERVICE_ACCOUNT_KEY_FILE = "YOUR_KEY_HERE"; + + /** + * Creates credentials. + * + * @return Calendar. + * @throws GeneralSecurityException exception. + * @throws IOException exception. + */ + public static Calendar getCalendarService() throws GeneralSecurityException, IOException { + // Load the service account key JSON file + final FileInputStream serviceAccountStream = new FileInputStream(SERVICE_ACCOUNT_KEY_FILE); + + // Build the service account credentials + final GoogleCredentials credentials = GoogleCredentials.fromStream(serviceAccountStream) + .createScoped(List.of("https://www.googleapis.com/auth/calendar")); + + // Construct the Calendar service object + return new Calendar.Builder(GoogleNetHttpTransport.newTrustedTransport(), JSON_FACTORY, + new HttpCredentialsAdapter(credentials)).setApplicationName(APPLICATION_NAME).build(); + } + + /** + * Retrieves calendar events, first 10 of them. + * + * @param email The email address of Google account. + * @return List of events. + * @throws GeneralSecurityException exception. + * @throws IOException exception. + */ + public List getCalendarEvents(String email) throws GeneralSecurityException, IOException { + final Calendar service = getCalendarService(); + + // List the next 10 events from the primary calendar. + final DateTime now = new DateTime(System.currentTimeMillis()); + final Events events = service.events().list(email).setMaxResults(10).setTimeMin(now) + .setOrderBy("startTime").setSingleEvents(true).execute(); + final List items = events.getItems(); + if (items.isEmpty()) { + System.out.println("No upcoming events found."); + } else { + System.out.println("Upcoming events:"); + for (Event event : items) { + DateTime start = event.getStart().getDateTime(); + if (start == null) { + start = event.getStart().getDate(); + } + System.out.printf("%s (%s)\n", event.getSummary(), start); + } + } + return items; + } +} diff --git a/src/main/java/use_cases/note/NoteDataAccessInterface.java b/src/main/java/use_cases/note/NoteDataAccessInterface.java new file mode 100644 index 000000000..bd89b6b78 --- /dev/null +++ b/src/main/java/use_cases/note/NoteDataAccessInterface.java @@ -0,0 +1,25 @@ +package use_cases.note; + +import java.util.List; + +import entity.Note; + +/** + * Interface for the NoteDAO. It consists of methods for + * both loading and saving a note. + */ +public interface NoteDataAccessInterface { + + /** + * Saves a note. + * @param title the title associated with the note. + * @param note the note to be saved. + */ + void saveNote(String title, String note); + + /** + * Creates an "in-memory" database of all notes saved in the program. + * @return a list of the titles of all notes that have been saved. + */ + List getNotes(); +} diff --git a/src/main/java/use_case/note/NoteInputBoundary.java b/src/main/java/use_cases/note/NoteInputBoundary.java similarity index 58% rename from src/main/java/use_case/note/NoteInputBoundary.java rename to src/main/java/use_cases/note/NoteInputBoundary.java index b41da9bf5..5b8cdbb67 100644 --- a/src/main/java/use_case/note/NoteInputBoundary.java +++ b/src/main/java/use_cases/note/NoteInputBoundary.java @@ -1,4 +1,4 @@ -package use_case.note; +package use_cases.note; /** * The Input Boundary for our note-related use cases. Since they are closely related, @@ -6,14 +6,10 @@ */ public interface NoteInputBoundary { - /** - * Executes the refresh note use case. - */ - void executeRefresh(); - /** * Executes the save note use case. - * @param message the input data + * @param noteInputData the note being worked on. */ - void executeSave(String message); + void execute(NoteInputData noteInputData); + } diff --git a/src/main/java/use_cases/note/NoteInputData.java b/src/main/java/use_cases/note/NoteInputData.java new file mode 100644 index 000000000..b4ca76c1d --- /dev/null +++ b/src/main/java/use_cases/note/NoteInputData.java @@ -0,0 +1,22 @@ +package use_cases.note; + +/** + * The input data for the Notes Use Case. + */ +public class NoteInputData { + private final String title; + private final String content; + + public NoteInputData(String title, String content) { + this.title = title; + this.content = content; + } + + public String getTitle() { + return title; + } + + public String getContent() { + return content; + } +} diff --git a/src/main/java/use_cases/note/NoteInteractor.java b/src/main/java/use_cases/note/NoteInteractor.java new file mode 100644 index 000000000..3552e149e --- /dev/null +++ b/src/main/java/use_cases/note/NoteInteractor.java @@ -0,0 +1,26 @@ +package use_cases.note; + +/** + * The "Use Case Interactor" for our two note-related use cases of refreshing + * the contents of the note and saving the contents of the note. Since they + * are closely related, we have combined them here for simplicity. + */ +public class NoteInteractor implements NoteInputBoundary { + + private final NoteOutputBoundary notePresenter; + private final NoteDataAccessInterface inMemoryNoteDataAccessObject; + + public NoteInteractor(NoteOutputBoundary noteOutputBoundary, NoteDataAccessInterface inMemoryNoteDAO) { + this.notePresenter = noteOutputBoundary; + this.inMemoryNoteDataAccessObject = inMemoryNoteDAO; + } + + @Override + public void execute(NoteInputData noteInputData) { + this.inMemoryNoteDataAccessObject.saveNote(noteInputData.getTitle(), noteInputData.getContent()); + + final NoteOutputData noteOutputData = new NoteOutputData(noteInputData.getTitle(), false); + this.notePresenter.prepareSuccessView(noteOutputData.getContent()); + } +} + diff --git a/src/main/java/use_case/note/NoteOutputBoundary.java b/src/main/java/use_cases/note/NoteOutputBoundary.java similarity index 94% rename from src/main/java/use_case/note/NoteOutputBoundary.java rename to src/main/java/use_cases/note/NoteOutputBoundary.java index c0c2bb1d0..2e00e5442 100644 --- a/src/main/java/use_case/note/NoteOutputBoundary.java +++ b/src/main/java/use_cases/note/NoteOutputBoundary.java @@ -1,4 +1,4 @@ -package use_case.note; +package use_cases.note; /** * The output boundary for the Login Use Case. diff --git a/src/main/java/use_cases/note/NoteOutputData.java b/src/main/java/use_cases/note/NoteOutputData.java new file mode 100644 index 000000000..59d4d5487 --- /dev/null +++ b/src/main/java/use_cases/note/NoteOutputData.java @@ -0,0 +1,23 @@ +package use_cases.note; + +/** + * The Output data for the Notes Use Case. + */ +public class NoteOutputData { + + private final String content; + private final boolean useCaseFailed; + + public NoteOutputData(String title, boolean useCaseFailed) { + this.content = title; + this.useCaseFailed = useCaseFailed; + } + + public String getContent() { + return content; + } + + public boolean isUseCaseFailed() { + return useCaseFailed; + } +} diff --git a/src/main/java/use_cases/note/TranslationFactory.java b/src/main/java/use_cases/note/TranslationFactory.java new file mode 100644 index 000000000..0953c8461 --- /dev/null +++ b/src/main/java/use_cases/note/TranslationFactory.java @@ -0,0 +1,47 @@ +package use_cases.note; + +import space.dynomake.libretranslate.Language; +import space.dynomake.libretranslate.Translator; + +/** + * Class for note translation. + */ +public class TranslationFactory { + + /** + * Translates text from english to other language. + * + * @param inputData the input data containing the text to translate and the selected language + * @return NoteOutputData containing the translation + */ + public static TranslationOutputData translate(TranslationInputData inputData) { + final int characterLimit = 2000; + final String textToTranslate = inputData.getTextToTranslate(); + final String selectedLanguage = inputData.getSelectedLanguage(); + + // checking if length of text exceeds char limit + if (textToTranslate.length() > characterLimit) { + return new TranslationOutputData("Exceeded character limit"); + } + + final String translation; + switch (selectedLanguage) { + case "Russian": + translation = Translator.translate(Language.ENGLISH, Language.RUSSIAN, textToTranslate); + break; + case "French": + translation = Translator.translate(Language.ENGLISH, Language.FRENCH, textToTranslate); + break; + case "Spanish": + translation = Translator.translate(Language.ENGLISH, Language.SPANISH, textToTranslate); + break; + case "Arabic": + translation = Translator.translate(Language.ENGLISH, Language.ARABIC, textToTranslate); + break; + default: + translation = textToTranslate; + } + + return new TranslationOutputData(translation); + } +} diff --git a/src/main/java/use_cases/note/TranslationInputBoundary.java b/src/main/java/use_cases/note/TranslationInputBoundary.java new file mode 100644 index 000000000..a4b23e010 --- /dev/null +++ b/src/main/java/use_cases/note/TranslationInputBoundary.java @@ -0,0 +1,13 @@ +package use_cases.note; + +/** + * InputBoundary for translation use case. + */ +public interface TranslationInputBoundary { + /** + * Translates note. + * + * @param inputData contains text to be translated and selected language + */ + void translateNote(TranslationInputData inputData); +} diff --git a/src/main/java/use_cases/note/TranslationInputData.java b/src/main/java/use_cases/note/TranslationInputData.java new file mode 100644 index 000000000..f1a8f1da3 --- /dev/null +++ b/src/main/java/use_cases/note/TranslationInputData.java @@ -0,0 +1,22 @@ +package use_cases.note; + +/** + * The Note Input Data contains the text to translate and the selected language to translate to. + */ +public class TranslationInputData { + private final String textToTranslate; + private final String selectedLanguage; + + public TranslationInputData(String textToTranslate, String selectedLanguage) { + this.textToTranslate = textToTranslate; + this.selectedLanguage = selectedLanguage; + } + + public String getTextToTranslate() { + return textToTranslate; + } + + public String getSelectedLanguage() { + return selectedLanguage; + } +} diff --git a/src/main/java/use_cases/note/TranslationInteractor.java b/src/main/java/use_cases/note/TranslationInteractor.java new file mode 100644 index 000000000..ad6533794 --- /dev/null +++ b/src/main/java/use_cases/note/TranslationInteractor.java @@ -0,0 +1,23 @@ +package use_cases.note; + +/** + * Interactor for translation use case. + */ +public class TranslationInteractor implements TranslationInputBoundary { + private final TranslationOutputBoundary outputBoundary; + + public TranslationInteractor(TranslationOutputBoundary outputBoundary) { + + this.outputBoundary = outputBoundary; + } + + /** + * Translates input data. + * + * @param inputData contains text to be translated and selected language + */ + public void translateNote(TranslationInputData inputData) { + final TranslationOutputData outputData = TranslationFactory.translate(inputData); + outputBoundary.updateTranslation(outputData); + } +} diff --git a/src/main/java/use_cases/note/TranslationOutputBoundary.java b/src/main/java/use_cases/note/TranslationOutputBoundary.java new file mode 100644 index 000000000..f67f66146 --- /dev/null +++ b/src/main/java/use_cases/note/TranslationOutputBoundary.java @@ -0,0 +1,12 @@ +package use_cases.note; + +/** + * OutputBoundary for translation use case. + */ +public interface TranslationOutputBoundary { + /** + * presents the translated text. + * @param outputData translated content + */ + void updateTranslation(TranslationOutputData outputData); +} diff --git a/src/main/java/use_cases/note/TranslationOutputData.java b/src/main/java/use_cases/note/TranslationOutputData.java new file mode 100644 index 000000000..a8efa5735 --- /dev/null +++ b/src/main/java/use_cases/note/TranslationOutputData.java @@ -0,0 +1,16 @@ +package use_cases.note; + +/** + * The Note Output Data contains the translated text. + */ +public class TranslationOutputData { + private final String translatedText; + + public TranslationOutputData(String translatedText) { + this.translatedText = translatedText; + } + + public String getTranslatedText() { + return translatedText; + } +} diff --git a/src/main/java/view/CalendarView.java b/src/main/java/view/CalendarView.java new file mode 100644 index 000000000..08870276c --- /dev/null +++ b/src/main/java/view/CalendarView.java @@ -0,0 +1,73 @@ +package view; + +import javax.swing.*; + +import entity.Events; +import interface_adapter.calendar.CalendarController; + +import java.awt.*; +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * The view for the calendar use case. + */ +public class CalendarView extends JPanel { + private final JLabel promptLabel; + private final JButton loginButton; + private final JPanel weekPanel; + private CalendarController calendarController; + + public CalendarView() throws GeneralSecurityException, IOException { + this.promptLabel = new JLabel("Please access your Google account:"); + this.loginButton = new JButton("Enter Email"); + this.weekPanel = new JPanel(); + + loginButton.addActionListener( + evt -> { + if (evt.getSource().equals(loginButton)) { + loginButton.setVisible(false); + final String email = JOptionPane.showInputDialog(this, + "Enter Email:", "Google Account", JOptionPane.PLAIN_MESSAGE); + if (email != null && !email.trim().isEmpty()) { + try { + calendarController.execute(email); + } + catch (GeneralSecurityException | IOException exception) { + throw new RuntimeException(exception); + } + } + } + } + ); + this.add(this.promptLabel); + this.add(this.loginButton); + } + + /** + * Displays events in the view based on the data from the API. + * @param events list of events from the API + */ + public void displayEvents(List events) { + weekPanel.setLayout(new BoxLayout(weekPanel, BoxLayout.Y_AXIS)); + for (Events event : events) { + final JPanel eventPanel = new JPanel(); + eventPanel.add(new JLabel(event.getEventName())); + eventPanel.add(new JLabel(event.getEventLocation())); + eventPanel.add(new JLabel(event.getEventDatetime())); + eventPanel.add(new JLabel(event.getEventNotes())); + weekPanel.add(eventPanel); + } + this.promptLabel.setText("Please access your Google account:"); + this.add(this.weekPanel); + promptLabel.setVisible(false); + } + + public void setCalendarController(CalendarController calendarController) { + this.calendarController = calendarController; + } +} diff --git a/src/main/java/view/ChecklistView.java b/src/main/java/view/ChecklistView.java new file mode 100644 index 000000000..ff323885d --- /dev/null +++ b/src/main/java/view/ChecklistView.java @@ -0,0 +1,72 @@ +package view; + +import interface_adapter.add_task.AddTaskController; +import interface_adapter.add_task.TaskState; +import interface_adapter.add_task.TaskViewModel; + +import javax.swing.*; +import java.awt.*; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + + +/** + * The View for when the user is viewing a checklist in the program. + */ +public class ChecklistView extends JPanel implements PropertyChangeListener { + + private final TaskViewModel taskViewModel; + private AddTaskController addTaskController; + + private final JPanel checklistPanel; + private final JButton addTaskButton; + + public ChecklistView(TaskViewModel taskViewModel) { + this.taskViewModel = taskViewModel; + this.taskViewModel.addPropertyChangeListener(this); + + this.checklistPanel = new JPanel(); + this.addTaskButton = new JButton("Add Task +"); + + checklistPanel.setLayout(new BoxLayout(checklistPanel, BoxLayout.Y_AXIS)); + checklistPanel.add(new JLabel("Tasks:")); + checklistPanel.add(addTaskButton); + + this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); + this.add(checklistPanel); + + addTaskButton.addActionListener(evt -> { + if (evt.getSource().equals(addTaskButton)) { + final String taskDescription = JOptionPane.showInputDialog(this, + "Enter Task Description:", "New Task", JOptionPane.PLAIN_MESSAGE); + if (taskDescription != null && !taskDescription.trim().isEmpty()) { + addTaskController.execute(taskDescription.trim()); + } + } + }); + } + + @Override + public void propertyChange(PropertyChangeEvent evt) { + final TaskState state = (TaskState) evt.getNewValue(); + + if (state.getError() != null) { + JOptionPane.showMessageDialog(this, state.getError(), + "Error", JOptionPane.ERROR_MESSAGE); + } + final JPanel taskPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + if (!state.getList().isEmpty()) { + final JCheckBox taskCheckBox = new JCheckBox((state.getList().get(state.getList().size() - 1)) + .getDescription()); + taskPanel.add(taskCheckBox); + } + checklistPanel.add(taskPanel); + + checklistPanel.revalidate(); + checklistPanel.repaint(); + } + + public void setTaskController(AddTaskController controller) { + this.addTaskController = controller; + } +} diff --git a/src/main/java/view/DashboardView.java b/src/main/java/view/DashboardView.java new file mode 100644 index 000000000..2a2c36fc7 --- /dev/null +++ b/src/main/java/view/DashboardView.java @@ -0,0 +1,15 @@ +package view; + +import javax.swing.*; + +/** + * Default open screen for the program. + */ +public class DashboardView extends JPanel { + private final JLabel welcomeText; + + public DashboardView() { + welcomeText = new JLabel("Welcome to your Dashboard."); + this.add(welcomeText); + } +} diff --git a/src/main/java/view/NoteView.java b/src/main/java/view/NoteView.java deleted file mode 100644 index 331d76493..000000000 --- a/src/main/java/view/NoteView.java +++ /dev/null @@ -1,95 +0,0 @@ -package view; - -import java.awt.Component; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; - -import javax.swing.BoxLayout; -import javax.swing.JButton; -import javax.swing.JLabel; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JTextArea; - -import interface_adapter.note.NoteController; -import interface_adapter.note.NoteState; -import interface_adapter.note.NoteViewModel; - -/** - * The View for when the user is viewing a note in the program. - */ -public class NoteView extends JPanel implements ActionListener, PropertyChangeListener { - - private final NoteViewModel noteViewModel; - - private final JLabel noteName = new JLabel("note for jonathan_calver2"); - private final JTextArea noteInputField = new JTextArea(); - - private final JButton saveButton = new JButton("Save"); - private final JButton refreshButton = new JButton("Refresh"); - private NoteController noteController; - - public NoteView(NoteViewModel noteViewModel) { - - noteName.setAlignmentX(Component.CENTER_ALIGNMENT); - this.noteViewModel = noteViewModel; - this.noteViewModel.addPropertyChangeListener(this); - - final JPanel buttons = new JPanel(); - buttons.add(saveButton); - buttons.add(refreshButton); - - saveButton.addActionListener( - evt -> { - if (evt.getSource().equals(saveButton)) { - noteController.execute(noteInputField.getText()); - - } - } - ); - - refreshButton.addActionListener( - evt -> { - if (evt.getSource().equals(refreshButton)) { - noteController.execute(null); - - } - } - ); - - this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); - - this.add(noteName); - this.add(noteInputField); - this.add(buttons); - } - - /** - * React to a button click that results in evt. - * @param evt the ActionEvent to react to - */ - public void actionPerformed(ActionEvent evt) { - System.out.println("Click " + evt.getActionCommand()); - } - - @Override - public void propertyChange(PropertyChangeEvent evt) { - final NoteState state = (NoteState) evt.getNewValue(); - setFields(state); - if (state.getError() != null) { - JOptionPane.showMessageDialog(this, state.getError(), - "Error", JOptionPane.ERROR_MESSAGE); - } - } - - private void setFields(NoteState state) { - noteInputField.setText(state.getNote()); - } - - public void setNoteController(NoteController controller) { - this.noteController = controller; - } -} - diff --git a/src/main/java/view/NotesView.java b/src/main/java/view/NotesView.java new file mode 100644 index 000000000..39a8aeb0a --- /dev/null +++ b/src/main/java/view/NotesView.java @@ -0,0 +1,281 @@ +package view; + +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; + +import javax.swing.JComboBox; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JSplitPane; +import javax.swing.JTextArea; + +import entity.Note; +import org.jetbrains.annotations.NotNull; + +import interface_adapter.ai.AiController; +import interface_adapter.translation.TranslationController; + +import interface_adapter.note.NoteController; +import interface_adapter.note.NoteState; +import interface_adapter.note.NoteViewModel; + +/** + * This class sets up the view of the Notes Use Case. + */ +public class NotesView extends JPanel implements ActionListener, PropertyChangeListener { + static final int DIVIDER = 400; + + private final NoteViewModel noteViewModel; + private AiController aiController; + private NoteController noteController; + private TranslationController translationController; + + private JLabel noteName; + private JTextArea noteInputField; + private JTextArea outputArea; + + private JButton saveNoteButton; + private JButton uploadButton; + private JButton clearButton; + private JButton deleteButton; + + private JTextArea notesTextArea; + + private JPanel notePanel; + private JPanel editPanel; + private JPanel savePanel; + + private JSplitPane splitPane; + + public NotesView(NoteViewModel noteViewModel) { + this.noteViewModel = noteViewModel; + this.noteViewModel.addPropertyChangeListener(this); + + this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); + + setUpUi(); + + } + + private void setUpUi() { + initializeComponents(); + setUpActionListeners(); + buildUi(); + } + + private void initializeComponents() { + notePanel = new JPanel(); + editPanel = new JPanel(); + savePanel = new JPanel(); + + noteName = new JLabel("New Note"); + + noteInputField = new JTextArea(); + noteInputField.setText("Create New Note..."); + + saveNoteButton = new JButton("Save Note"); + uploadButton = new JButton("Upload"); + clearButton = new JButton("Clear"); + deleteButton = new JButton("Delete"); + + } + + private void setUpActionListeners() { + saveNoteButton.addActionListener(this::saveNote); + uploadButton.addActionListener(this::uploadText); + clearButton.addActionListener(this::clearText); + deleteButton.addActionListener(this::deleteNote); + + } + + private void saveNote(ActionEvent actionEvent) { + if (actionEvent.getSource().equals(saveNoteButton)) { + final String newNoteName = JOptionPane.showInputDialog("Enter new note name"); + noteName.setText(newNoteName); + noteController.execute(noteInputField.getText(), noteName.getText()); + + } + + } + + private void uploadText(ActionEvent actionEvent) { + if (actionEvent.getSource().equals(uploadButton)) { + final JFileChooser fileChooser = new JFileChooser(); + fileChooser.setDialogTitle("Upload"); + final int returnVal = fileChooser.showOpenDialog(this); + final File selectedFile = fileChooser.getSelectedFile(); + if (returnVal == JFileChooser.APPROVE_OPTION) { + try (BufferedReader reader = new BufferedReader(new FileReader(selectedFile))) { + noteInputField.setText(""); + String line; + while ((line = reader.readLine()) != null) { + noteInputField.append(line); + noteInputField.append("\n"); + } + } + catch (IOException exception) { + JOptionPane.showMessageDialog(this, exception.getMessage(), + "Error", JOptionPane.ERROR_MESSAGE); + } + } + + } + + } + + private void clearText(ActionEvent evt) { + if (evt.getSource().equals(clearButton)) { + noteController.execute(noteInputField.getText(), noteName.getText()); + } + + } + + private void deleteNote(ActionEvent evt) { + if (evt.getSource().equals(deleteButton)) { + noteController.execute(noteInputField.getText(), noteName.getText()); + noteName.setText("New Note"); + } + + } + + private void buildUi() { + notePanel.setLayout(new BoxLayout(notePanel, BoxLayout.Y_AXIS)); + + noteName.setAlignmentX(Component.CENTER_ALIGNMENT); + notePanel.add(noteName); + + noteInputField.setLineWrap(true); + noteInputField.setWrapStyleWord(true); + notePanel.add(noteInputField); + + editPanel.add(uploadButton); + editPanel.add(clearButton); + editPanel.add(deleteButton); + notePanel.add(editPanel); + + savePanel.add(saveNoteButton); + notePanel.add(savePanel); + + final JPanel functionalityPanel = getjPanel(); + + splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, notePanel, functionalityPanel); + splitPane.setDividerLocation(DIVIDER); + + + this.add(splitPane); + + } + + @NotNull + private JPanel getjPanel() { + final JPanel panel = new JPanel(); + panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); + + final JPanel tools = new JPanel(); + + final JButton aiButton = new JButton("Complete Notes With AI"); + aiButton.addActionListener( + evt -> { + if (evt.getSource().equals(aiButton)) { + aiController.generateResponse(new Note(noteInputField.getText(), "NA")); + // Jenna, you may need to call some method since you cannot create an instance of Note here. + + } + } + ); + tools.add(aiButton); + + // Dropdown menu + final String[] languages = {"Russian", "French", "Spanish", "Arabic"}; + final JComboBox languageDropdown = new JComboBox<>(languages); + languageDropdown.setVisible(false); + tools.add(languageDropdown); + + final JButton languageButton = new JButton("Translate"); + languageButton.addActionListener( + evt -> { + if (evt.getSource().equals(languageButton)) { + if (!languageDropdown.isVisible()) { + languageDropdown.setVisible(true); + } + else { + final String selectedLanguage = (String) languageDropdown.getSelectedItem(); + final String textInput = notesTextArea.getText(); + translationController.translateNote(textInput, selectedLanguage); + languageDropdown.setVisible(false); + } + } + } + ); + tools.add(languageButton); + + outputArea = new JTextArea(); + outputArea.setEditable(false); + + panel.add(outputArea); + panel.add(tools); + return panel; + } + + /** + * React to a button click that results in evt. + * @param evt the ActionEvent to react to + */ + public void actionPerformed(ActionEvent evt) { + System.out.println("Click " + evt.getActionCommand()); + } + + @Override + public void propertyChange(PropertyChangeEvent evt) { + final NoteState state = (NoteState) evt.getNewValue(); + setFields(state); + if (state.getError() != null) { + JOptionPane.showMessageDialog(this, state.getError(), + "Error", JOptionPane.ERROR_MESSAGE); + } + } + + private void setFields(NoteState state) { + noteInputField.setText(state.getNote()); + } + + public void setNoteController(NoteController controller) { + this.noteController = controller; + } + + /** + * updates the translation. + * @param translatedText text that has been translated + */ + public void updateTranslation(String translatedText) { + outputArea.setText(translatedText); + } + + public void setTranslationController(TranslationController translationController) { + this.translationController = translationController; + } + + public void setAiController(AiController aiController) { + this.aiController = aiController; + } + + /** + * Updates the display of the un-editable text area. + * @param newNote The new note to display. + */ + public void displayNewNote(Note newNote) { + outputArea.setText(newNote.getContent()); + } + // add methods for other controllers here +} diff --git a/src/test/java/app/MainNoteApplicationTest.java b/src/test/java/app/MainNoteApplicationTest.java deleted file mode 100644 index 025d970e2..000000000 --- a/src/test/java/app/MainNoteApplicationTest.java +++ /dev/null @@ -1,112 +0,0 @@ -package app; - -import entity.User; -import org.junit.Before; -import org.junit.Test; -import use_case.note.NoteDataAccessInterface; - -import javax.swing.*; -import java.awt.*; - -import static java.lang.Thread.sleep; -import static org.junit.Assert.*; - -public class MainNoteApplicationTest { - - private JFrame app; - - @Before - public void setUp() { - - // create the data access and inject it into our builder! - final NoteDataAccessInterface noteDataAccess = new NoteDataAccessInterface() { - - private String note = "test"; - - @Override - public String saveNote(User user, String note) { - this.note = note; - return note; - } - - @Override - public String loadNote(User user) { - return note; - } - }; - - final NoteAppBuilder builder = new NoteAppBuilder(); - app = builder.addNoteDAO(noteDataAccess) - .addNoteView() - .addNoteUseCase().build(); - - app.setVisible(true); - - } - - /** - * This is an example of an end-to-end test with a mocked database. - *

The code creates the application and directly tests to see that the GUI - * is updated as expected when the buttons and UI elements are interacted with. - *

- * You can run the test to visually see what happens. - */ - @Test - public void testEndToEnd() { - - Component[] components = ((JPanel)app.getRootPane().getContentPane().getComponents()[0]).getComponents(); - JTextArea textArea = null; - for (Component component : components) { - if (component instanceof JTextArea) { - textArea = (JTextArea) component; - assertEquals("test", textArea.getText()); - - } - } - - textArea.setText("test test"); - - - JButton save = null; - JButton load = null; - for (Component component : components) { - if (component instanceof JPanel) { - for (Component c : ((JPanel) component).getComponents()) { - if (c instanceof JButton) { - if (save != null) { - load = (JButton) c; - } - else { - save = (JButton) c; - } - } - } - } - } - - save.doClick(); - assertEquals("test test", textArea.getText()); - textArea.setText(""); - - System.out.println("cleared text; about to refresh..."); - // pause execution for a bit so we can visually see the changes on the screen - try { - sleep(1500); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - - load.doClick(); - assertEquals("test test", textArea.getText()); - - System.out.println("after refresh!"); - - // pause execution for a bit so we can visually see the changes on the screen - try { - sleep(1500); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - - } -} \ No newline at end of file diff --git a/src/test/java/entity/EventsTest.java b/src/test/java/entity/EventsTest.java new file mode 100644 index 000000000..2219a71c3 --- /dev/null +++ b/src/test/java/entity/EventsTest.java @@ -0,0 +1,35 @@ +package entity; + +import com.google.api.client.util.DateTime; +import com.google.api.services.calendar.model.EventDateTime; +import org.junit.Test; + +public class EventsTest { + @Test + public void getEventNameTest() { + final Events testEvents = new Events("test name", new EventDateTime().setDateTime(new DateTime("2024-12-02T09:00:00.000-05:00")), + "test location", "extra notes go here"); + assert testEvents.getEventName().equals("test name"); + } + + @Test + public void getEventLocationTest() { + final Events testEvents = new Events("test name", new EventDateTime().setDateTime(new DateTime("2024-12-02T09:00:00.000-05:00")), + "test location", "extra notes go here"); + assert testEvents.getEventLocation().equals("test location"); + } + + @Test + public void getDateTimeTest() { + final Events testEvents = new Events("test name", new EventDateTime().setDateTime(new DateTime("2024-12-02T09:00:00.000-05:00")), + "test location", "extra notes go here"); + assert testEvents.getEventDatetime().equals("2024-12-02T09:00:00.000-05:00"); + } + + @Test + public void getNoteTest() { + final Events testEvents = new Events("test name", new EventDateTime().setDateTime(new DateTime("2024-12-02T09:00:00.000-05:00")), + "test location", "extra notes go here"); + assert testEvents.getEventNotes().equals("extra notes go here"); + } +} diff --git a/src/test/java/entity/NoteTest.java b/src/test/java/entity/NoteTest.java new file mode 100644 index 000000000..42405b940 --- /dev/null +++ b/src/test/java/entity/NoteTest.java @@ -0,0 +1,24 @@ +package entity; + +import org.junit.Test; + +public class NoteTest { + @Test + public void getContentTest() { + final Note testNote = new Note("content of note is here", "2024-11-18"); + assert testNote.getContent().equals("content of note is here"); + } + + @Test + public void setContentTest() { + final Note testNote = new Note("content of note is here", "2024-11-18"); + testNote.setContent("new note content"); + assert testNote.getContent().equals("new note content"); + } + + @Test + public void getDateTest() { + final Note testNote = new Note("content of note is here", "2024-11-18"); + assert testNote.getTitle().equals("2024-11-18"); + } +} diff --git a/src/test/java/entity/TaskTest.java b/src/test/java/entity/TaskTest.java new file mode 100644 index 000000000..f4b77d938 --- /dev/null +++ b/src/test/java/entity/TaskTest.java @@ -0,0 +1,11 @@ +package entity; + +import org.junit.Test; + +public class TaskTest { + @Test + public void getTaskDescriptionTest() { + final Task testTask = new Task("title of task"); + assert testTask.getDescription().equals("title of task"); + } +} diff --git a/src/test/java/use_case/note/NoteInteractorTest.java b/src/test/java/use_case/note/NoteInteractorTest.java deleted file mode 100644 index a3ed466b6..000000000 --- a/src/test/java/use_case/note/NoteInteractorTest.java +++ /dev/null @@ -1,46 +0,0 @@ -package use_case.note; - -import entity.User; -import org.junit.Test; - -import static org.junit.Assert.*; - -public class NoteInteractorTest { - - @Test - public void testExecuteRefreshSuccess() { - - NoteDataAccessInterface noteDAO = new NoteDataAccessInterface() { - - - @Override - public String saveNote(User user, String note) { - return ""; - } - - - @Override - public String loadNote(User user) { - return "test"; - } - }; - - NoteOutputBoundary noteOB = new NoteOutputBoundary() { - @Override - public void prepareSuccessView(String message) { - assertEquals("test", message); - } - - @Override - public void prepareFailView(String errorMessage) { - fail(errorMessage); - } - }; - - NoteInteractor noteInteractor = new NoteInteractor(noteDAO, noteOB); - - noteInteractor.executeRefresh(); - - - } -} \ No newline at end of file diff --git a/src/test/java/use_cases/add_task/AddTaskInteractorTest.java b/src/test/java/use_cases/add_task/AddTaskInteractorTest.java new file mode 100644 index 000000000..3d607f51e --- /dev/null +++ b/src/test/java/use_cases/add_task/AddTaskInteractorTest.java @@ -0,0 +1,30 @@ +package use_cases.add_task; + +import entity.Task; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +public class AddTaskInteractorTest { + + @Test + public void successTest() { + + final AddTaskInputData inputData = new AddTaskInputData("test description"); + + AddTaskOutputBoundary addTaskOB = new AddTaskOutputBoundary() { + @Override + public void prepareSuccessView(AddTaskOutputData outputData) { + assertEquals("test description", outputData.getDescription()); + } + + @Override + public void prepareFailView(String errorMessage) { + fail("Use case failure is unexpected."); + } + }; + AddTaskInteractor addTaskInteractor = new AddTaskInteractor(addTaskOB); + addTaskInteractor.execute(inputData); + } +} diff --git a/src/test/java/use_cases/ai/AiInteractorTest.java b/src/test/java/use_cases/ai/AiInteractorTest.java new file mode 100644 index 000000000..e34941543 --- /dev/null +++ b/src/test/java/use_cases/ai/AiInteractorTest.java @@ -0,0 +1,25 @@ +package use_cases.ai; + +import entity.Note; +import org.junit.Test; +import use_cases.add_task.AddTaskInputData; +import use_cases.add_task.AddTaskInteractor; +import use_cases.add_task.AddTaskOutputBoundary; +import use_cases.add_task.AddTaskOutputData; + +import static org.junit.Assert.*; + +public class AiInteractorTest { + + @Test + public void successTest() { + AiOutputBoundary aiOutputBoundary = new AiOutputBoundary() { + @Override + public void updateNote(Note note) { + assertNotNull(note); + } + }; + AiInteractor aiInteractor = new AiInteractor(aiOutputBoundary, new AiRequest()); + aiInteractor.generateResponse(new Note("testing Note!", "Note title")); + } +} diff --git a/src/test/java/use_cases/calendar/CalendarInteractorTest.java b/src/test/java/use_cases/calendar/CalendarInteractorTest.java new file mode 100644 index 000000000..4b6f1e37f --- /dev/null +++ b/src/test/java/use_cases/calendar/CalendarInteractorTest.java @@ -0,0 +1,31 @@ +package use_cases.calendar; + +import entity.Note; +import org.junit.Test; +import entity.Events; +import use_cases.ai.AiInteractor; +import use_cases.ai.AiOutputBoundary; +import use_cases.ai.AiRequest; + +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.util.List; + +import static org.junit.Assert.*; + + +public class CalendarInteractorTest { + + @Test + public void successTest() throws GeneralSecurityException, IOException { + CalendarOutputBoundary calendarOutputBoundary = new CalendarOutputBoundary() { + @Override + public void prepareSuccessView(List events) { + assertNotNull(events); + } + }; + CalendarInteractor calendarInteractor = new CalendarInteractor(calendarOutputBoundary, new CalendarRequest()); + calendarInteractor.execute("jennazhang.jz@gmail.com"); + } + +} diff --git a/src/test/java/use_cases/note/NoteInteractorTest.java b/src/test/java/use_cases/note/NoteInteractorTest.java new file mode 100644 index 000000000..9c9ed6960 --- /dev/null +++ b/src/test/java/use_cases/note/NoteInteractorTest.java @@ -0,0 +1,32 @@ +package use_cases.note; + +import data_access.InMemoryNoteDataAccessObject; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class NoteInteractorTest { + + @Test + public void testExecuteSaveSuccess() { + NoteInputData inputData = new NoteInputData("Test note", "This is a sample note! " + + "This tests to see if the note is properly saved. "); + NoteDataAccessInterface noteDataAccess = new InMemoryNoteDataAccessObject(); + noteDataAccess.saveNote(inputData.getTitle(), inputData.getContent()); + + NoteOutputBoundary successPresenter = new NoteOutputBoundary() { + @Override + public void prepareSuccessView(String message) { + assertEquals("Use case success is expected","This is a sample note! This tests to see if the note is properly saved. ", inputData.getContent()); + } + + @Override + public void prepareFailView(String errorMessage) { + fail("Use case failure is unexpected."); + } + }; + + NoteInputBoundary noteInteractor = new NoteInteractor(successPresenter, noteDataAccess); + noteInteractor.execute(inputData); + } +} \ No newline at end of file diff --git a/src/test/java/use_cases/note/TranslationInteractorTest.java b/src/test/java/use_cases/note/TranslationInteractorTest.java new file mode 100644 index 000000000..b198cd34b --- /dev/null +++ b/src/test/java/use_cases/note/TranslationInteractorTest.java @@ -0,0 +1,109 @@ +package use_cases.note; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class TranslationInteractorTest { + + @Test + public void testTranslate2Russian() { + final TranslationInputData inputData = new TranslationInputData("Hello world", "Russian"); + + TranslationOutputBoundary outputBoundary = new TranslationOutputBoundary() { + @Override + public void updateTranslation(TranslationOutputData outputData) { + assertEquals("Привет мир", outputData.getTranslatedText()); + } + }; + TranslationInteractor translationInteractor = new TranslationInteractor(outputBoundary); + translationInteractor.translateNote(inputData); + } + + @Test + public void testTranslate2Spanish() { + final TranslationInputData inputData = new TranslationInputData("Welcome everyone", "Spanish"); + + TranslationOutputBoundary outputBoundary = new TranslationOutputBoundary() { + @Override + public void updateTranslation(TranslationOutputData outputData) { + assertEquals("Bienvenido a todos", outputData.getTranslatedText()); + } + }; + TranslationInteractor translationInteractor = new TranslationInteractor(outputBoundary); + translationInteractor.translateNote(inputData); + } + + @Test + public void testTranslate2French() { + final TranslationInputData inputData = new TranslationInputData("Hello everyone", "French"); + + TranslationOutputBoundary outputBoundary = new TranslationOutputBoundary() { + @Override + public void updateTranslation(TranslationOutputData outputData) { + assertEquals("Bonjour à tous", outputData.getTranslatedText()); + } + }; + TranslationInteractor translationInteractor = new TranslationInteractor(outputBoundary); + translationInteractor.translateNote(inputData); + } + + @Test + public void testTranslate2Arabic() { + final TranslationInputData inputData = new TranslationInputData("Please and thank you", "Arabic"); + + TranslationOutputBoundary outputBoundary = new TranslationOutputBoundary() { + @Override + public void updateTranslation(TranslationOutputData outputData) { + assertEquals("من فضلك وشكرا لكم", outputData.getTranslatedText()); + } + }; + TranslationInteractor translationInteractor = new TranslationInteractor(outputBoundary); + translationInteractor.translateNote(inputData); + } + + @Test + public void testLanguageNotInDropdown() { + // If selected language is not part of the dropdown list, should output original text + final TranslationInputData inputData = new TranslationInputData("Hello everyone", "Italian"); + + TranslationOutputBoundary outputBoundary = new TranslationOutputBoundary() { + @Override + public void updateTranslation(TranslationOutputData outputData) { + assertEquals("Hello everyone", outputData.getTranslatedText()); + } + }; + TranslationInteractor translationInteractor = new TranslationInteractor(outputBoundary); + translationInteractor.translateNote(inputData); + } + + @Test + public void testTranslateEmptyString() { + // Empty string should return empty string + final TranslationInputData inputData = new TranslationInputData(" ", "French"); + + TranslationOutputBoundary outputBoundary = new TranslationOutputBoundary() { + @Override + public void updateTranslation(TranslationOutputData outputData) { + assertEquals("", outputData.getTranslatedText()); + } + }; + TranslationInteractor translationInteractor = new TranslationInteractor(outputBoundary); + translationInteractor.translateNote(inputData); + } + + @Test + public void testExceededCharLimit() { + String text = "hello ".repeat(400); + final TranslationInputData inputData = new TranslationInputData(text, "Spanish"); + + TranslationOutputBoundary outputBoundary = new TranslationOutputBoundary() { + @Override + public void updateTranslation(TranslationOutputData outputData) { + assertEquals("Exceeded character limit", outputData.getTranslatedText()); + } + }; + TranslationInteractor translationInteractor = new TranslationInteractor(outputBoundary); + translationInteractor.translateNote(inputData); + } +} \ No newline at end of file