diff --git a/.gitignore b/.gitignore index 650c91720..43b7d5ed6 100644 --- a/.gitignore +++ b/.gitignore @@ -2,8 +2,11 @@ out/ !**/src/main/**/out/ !**/src/test/**/out/ +.idea .idea/easycode.ignore - +homework-5.iml +/src/main/java/data_access/portfolio.txt +/src/main/java/data_access/watchlist.txt target/ ### Eclipse ### @@ -29,4 +32,4 @@ bin/ .vscode/ ### Mac OS ### -.DS_Store \ No newline at end of file +.DS_Store diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 000000000..919ce1f1f --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 000000000..a55e7a179 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 000000000..69d41be32 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 000000000..aa00ffab7 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/material_theme_project_new.xml b/.idea/material_theme_project_new.xml new file mode 100644 index 000000000..16918dd4e --- /dev/null +++ b/.idea/material_theme_project_new.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 000000000..0abcc97ca --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 000000000..35eb1ddfb --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 8c4770018..3875069a5 100644 --- a/README.md +++ b/README.md @@ -1,181 +1,62 @@ -# Lab 5: Logout - -## Preamble - -In the current homework, you added code to the login use case to save the currently-logged-in -user by saving the user in the Data Access Layer. You also added a unit test for this. - -In this lab, you will complete a logout use case as a team. You will also begin to discuss your project -and the use cases that need to be implemented. - -We have created all the Clean Architecture classes necessary for the logout use case. - -By Friday, your team will submit: -- your completed lab code [for credit] -- a draft of your project blueprint proposal. [required, but not for credit] - -# Phase 2 [for credit] -_(recall, Phase 1 was your solo task of adding the storage of the currently-logged-in user)_ - -## Task 0: Fork this repo on GitHub -**To get started, one team member should fork this repo on GitHub and share it with the team. -All of you should then clone it.** - -* * * - -Suggested logistics: One of you should invite the others to collaborate on their fork of the -original repo on GitHub. You can do this in your repo on GitHub under `Settings -> Collaborators`. -This will allow you to push branches to a common repo and then use pull requests to contribute -your code and review. To prevent others from pushing directly to the main branch, -we recommend you set branch protection rules on GitHub. Below are how the settings might look if you -add branch protection rules: - -![image of branch protection rules for main with the -requirement of two approvers to merge in pull requests. -](images/branch_protection_rules.png) - -* * * - -Open the project in IntelliJ and make sure you can successfully run `app/Main.java`. -Note: you may need to set the Project SDK in the `Project Structure...` menu, and possibly -also manually link the Maven project, as you did in Phase 1. - -## Task 1: Understanding the Program - -You may notice that we have refactored the CA engine code _slightly_ since Phase 1, but the -way we build the engine is drastically different: we have switched from using Factories to -using the Builder design pattern, which we'll be discussing in lecture soon. - -Open up `app.Main` and read it as a team. -- What are the Views and what are the current Use Cases? -- Which Uses Cases are triggered from each View? -- Which version of the DAO is `app.Main` using? - -The major change since Phase 1 is that we have added the `app.AppBuilder` class which makes -it easier to understand how our CA engine is being constructed — it also makes `app.Main` nice and concise! -- Why do all those `addX` methods end in `return this;`? - -Run the program and make sure the signup and login Use Cases work. - -Currently, you'll notice that the "Log Out" button still doesn't actually log you out. It's time to fix -that button, which is part of the `LoggedInView`. -We have created all the classes for you, but some of the code is missing. -As a team, your task is to fill in the missing code so that the Logout Use Case is implemented. -**The next part of the readme describes how your team will do this.** - -* * * - -**Your team will know when you are done when:** - -- Clicking the "Log Out" button takes the user back to the Login View when you use the program. -- The provided `LogoutInteractorTest` test passes. - -The "Log Out" button is an instance variable in class `LoggedInVew`. Go find it. -Also look at the `interface_adapter.change_password.LoggedInViewModel`, which contains any -data showing on the `LoggedInVew`. - -* * * - -## Task 2: Dividing up the work - -There are `TODO` comments left in the files -Recall that you can use the TODO tool window to conveniently pull up a complete list. - -Once the TODOs are all complete, the "Log Out" button _should_ work! - -As a team, split up the TODOs (see below) between the members of your team. - -There are TODOs in seven of the files. -Make sure each member has at least one TODO which they will be responsible for completing. -If your team prefers to work in pairs, that is fine too. Your individual branches -will not be graded for this — only the final, working version. - -The TODOs are summarized below (by file) to help your team decide how to split them up: - -* * * - -- `Main.java` - - - [ ] TODO: add the Logout Use Case to the app using the appBuilder - -* * * - -- `LoggedInView.java` (tip: refer to the other views for similar code) - - - [ ] TODO: save the logout controller in the instance variable. - - [ ] TODO: execute the logout use case through the Controller - -* * * - -- `LogoutController.java` (tip: refer to the other controllers for similar code) - - - [ ] TODO: Save the interactor in the instance variable. - - [ ] TODO: run the use case interactor for the logout use case - -* * * - -- `LogoutInputData.java` (should be done with the LogoutInteractor TODOs below) - - - [ ] TODO: save the current username in an instance variable and add a getter. - -- `LogoutInteractor.java` (tip: refer to `ChangePasswordInteractor.java` for similar code) - - - [ ] TODO: save the DAO and Presenter in the instance variables. - - [ ] TODO: implement the logic of the Logout Use Case - -* * * - -- `LogoutOutputData.java` - - - [ ] TODO: save the parameters in the instance variables. - -* * * - -- `LogoutPresenter.java` (tip: refer to `SignupPresenter.java` for similar code) - - - [ ] TODO: assign to the three instance variables. - - [ ] TODO: have prepareSuccessView update the LoggedInState - - [ ] TODO: have prepareSuccessView update the LoginState - -* * * - -1. Make a branch named the first part of your UofT email address, everything before the `@`. -For example, if your email address is `paul.gries@mail.utoronto.ca`, then the branch name would -be `paul.gries`. - -Make sure you switch to the new branch. - -In the terminal, this would look like below, but replaced with your own information: -``` -git branch paul.gries -git switch paul.gries -``` - -2. Complete your assigned TODOs and make a pull request on GitHub. In your pull request, - briefly describe what your TODOs were and how you implemented them. If you aren't sure - about part of it, include this in your pull request so everyone knows what to look - for when reviewing — or you can of course discuss with your team before making your - pull request since you are physically working in the same space. - - **Important: don't push any changes to the `.idea` folder, as that - may cause issues for your other teammates, as some files contain - configurations specific to your individual IntelliJ projects.** - -3. Review each other's pull requests to ensure each TODO is correctly implemented and that - there are no Checkstyle issues in the files that were modified. - -4. Once all TODOs are completed, your team should debug as needed to ensure the - correctness of the code. Setting a breakpoint where the log-out use case - interactor starts its work will likely be a great place to start when debugging. - -And that's it; you now have a working Logout Use Case! Instructions for -how to submit your work on MarkUs will be posted later. - -Your team should spend the rest of the lab working on your project blueprint. - -* * * - -# Project Blueprint - -See Quercus for details about the project blueprint! By the end of the week, -the goal is for your team to have a fully drafted blueprint so that your team -will be ready to get started on your project after Reading Week. +# Stock Marketplace + +## Contents +- [Authors and Contributors](#authors-and-contributors) +- [Project Description](#project-description) +- [Features](#features) +- [Installation and Usage Instructions](#installation-and-usage-instructions) +- [Feedbacks](#feedbacks) +- [Contributions](#contributions) +- [License](#license) + +## Authors and Contributors +- Kairan Zhai +- Ziming Huang +- Zifan Guo +- Yitao Huang +- Ryan Jin + +## Project Description +Introducing our Stock Marketplace Application, a user-friendly platform designed for both casual viewers and active investors. With this app, users can quickly look up stock information by entering ticker symbols without needing to log in, providing immediate access to essential market data. For personalized experience, users can create an account to build a watchlist of their favorite stocks, making it easy to track preferred investments. Additionally, the application features a simulated holdings section where logged-in users can add stocks along with their purchase amounts and prices, allowing for real-time calculations of day gains and total gains. + +## Features +- Users can search stock information by entering stock symbol. +- Users can add favourite stock to their watchlists. +- Users can view their favourite stocks in their watchlists. +- Users can add simulated purchase to their portfolio list. +- Users can check their performance in portfolio list view. +- Users can sign up by entering their usernames and passwords. +- Users can log in by entering their usernames and passwords. + +## Installation and Usage Instructions +To run this project, follow these steps: + +1. Clone the repository to your local machine. +2. Navigate to the `src/main/java/app` directory: +3. Click the green arrow on the top right section in your IntelliJ.![image](https://github.com/user-attachments/assets/a31de5dc-624e-4e24-938c-1cc258b78c23) +4. Due to API usage limitation, we are currently using mock data for our application. If you want to use real-time data, please go to `src/main/java/entity/DebugMode` and change debugMode to **false** + +## Screenshots + + + + + +
+ 截屏2024-12-01 18 31 51 +

Homepage

+
+ 截屏2024-12-01 18 32 04 +

Purchase Page

+
+ +## Feedbacks +You are welcomed to submit an issue! + +## Contributions +Any pull requests are welcomed! 💖 + +## License +Stock Marketplace is under CC0-1.0 license. See the LICENSE file for more info. + +[Back to Top](#contents) diff --git a/homework-5.iml b/homework-5.iml new file mode 100644 index 000000000..9e3449c9d --- /dev/null +++ b/homework-5.iml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/src/main/java/app/AppBuilder.java b/src/main/java/app/AppBuilder.java index e9eef5c81..3912e6fc0 100644 --- a/src/main/java/app/AppBuilder.java +++ b/src/main/java/app/AppBuilder.java @@ -1,42 +1,83 @@ package app; import java.awt.CardLayout; +import java.util.ArrayList; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.WindowConstants; -import data_access.InMemoryUserDataAccessObject; +import data_access.DBUserDataAccessObject; +import data_access.FileUserDataAccessObject; +import entity.CommonSimulatedHoldingFactory; +import entity.CommonStockFactory; import entity.CommonUserFactory; +import entity.SimulatedHoldingFactory; +import entity.StockFactory; import entity.UserFactory; import interface_adapter.ViewManagerModel; +import interface_adapter.buy_view.BuyController; +import interface_adapter.buy_view.BuyPresenter; +import interface_adapter.buy_view.BuyViewModel; import interface_adapter.change_password.ChangePasswordController; import interface_adapter.change_password.ChangePasswordPresenter; import interface_adapter.change_password.LoggedInViewModel; +import interface_adapter.home_view.HomeController; +import interface_adapter.home_view.HomePresenter; +import interface_adapter.home_view.HomeViewModel; +import interface_adapter.home_view.WatchlistController; +import interface_adapter.home_view.WatchlistPresenter; import interface_adapter.login.LoginController; import interface_adapter.login.LoginPresenter; import interface_adapter.login.LoginViewModel; import interface_adapter.logout.LogoutController; import interface_adapter.logout.LogoutPresenter; +import interface_adapter.portfolio.PortfolioController; +import interface_adapter.portfolio.PortfolioPresenter; +import interface_adapter.portfolio.PortfolioViewModel; import interface_adapter.signup.SignupController; import interface_adapter.signup.SignupPresenter; import interface_adapter.signup.SignupViewModel; +import interface_adapter.stock_view.StockController; +import interface_adapter.stock_view.StockPresenter; +import interface_adapter.stock_view.StockViewModel; +import interface_adapter.watchlist_view.WatchListViewModel; +import use_case.buy.BuyInputBoundary; +import use_case.buy.BuyInteractor; +import use_case.buy.BuyOutputBoundary; import use_case.change_password.ChangePasswordInputBoundary; import use_case.change_password.ChangePasswordInteractor; import use_case.change_password.ChangePasswordOutputBoundary; +import use_case.home_view.HomeInputBoundary; +import use_case.home_view.HomeInteractor; +import use_case.home_view.HomeOutputBoundary; +import use_case.home_view.WatchlistInputBoundary; +import use_case.home_view.WatchlistInteractor; +import use_case.home_view.WatchlistOutputBoundary; import use_case.login.LoginInputBoundary; import use_case.login.LoginInteractor; import use_case.login.LoginOutputBoundary; import use_case.logout.LogoutInputBoundary; import use_case.logout.LogoutInteractor; import use_case.logout.LogoutOutputBoundary; +import use_case.portfolio.PortfolioInputBoundary; +import use_case.portfolio.PortfolioInteractor; +import use_case.portfolio.PortfolioOutputBoundary; import use_case.signup.SignupInputBoundary; import use_case.signup.SignupInteractor; import use_case.signup.SignupOutputBoundary; +import use_case.stock.StockInputBoundary; +import use_case.stock.StockInteractor; +import use_case.stock.StockOutputBoundary; +import view.BuyView; +import view.HomeView; import view.LoggedInView; import view.LoginView; +import view.PortfolioView; import view.SignupView; +import view.StockView; import view.ViewManager; +import view.WatchListView; /** * The AppBuilder class is responsible for putting together the pieces of @@ -54,30 +95,57 @@ public class AppBuilder { private final CardLayout cardLayout = new CardLayout(); // thought question: is the hard dependency below a problem? private final UserFactory userFactory = new CommonUserFactory(); + private final StockFactory stockFactory = new CommonStockFactory(); + private final SimulatedHoldingFactory simulatedHoldingFactory = new CommonSimulatedHoldingFactory(); private final ViewManagerModel viewManagerModel = new ViewManagerModel(); private final ViewManager viewManager = new ViewManager(cardPanel, cardLayout, viewManagerModel); // thought question: is the hard dependency below a problem? - private final InMemoryUserDataAccessObject userDataAccessObject = new InMemoryUserDataAccessObject(); + private final DBUserDataAccessObject dbUserDataAccessObject = new DBUserDataAccessObject(userFactory, stockFactory); + private final FileUserDataAccessObject fileUserDataAccessObject = + new FileUserDataAccessObject(simulatedHoldingFactory); + private HomeView homeView; + private HomeViewModel homeViewModel; private SignupView signupView; private SignupViewModel signupViewModel; private LoginViewModel loginViewModel; private LoggedInViewModel loggedInViewModel; private LoggedInView loggedInView; private LoginView loginView; + private WatchListView watchListView; + private WatchListViewModel watchListViewModel; + private PortfolioView portfolioView; + private PortfolioViewModel portfolioViewModel; + private StockView stockView; + private StockViewModel stockViewModel; + private BuyView buyView; + private BuyViewModel buyViewModel; + private ArrayList watchList = fileUserDataAccessObject.getWatchList(); public AppBuilder() { cardPanel.setLayout(cardLayout); } + /** + * Adds the Home View to the application. + * @return this builder + */ + public AppBuilder addHomeView() { + homeViewModel = new HomeViewModel(); + homeView = new HomeView(homeViewModel); + cardPanel.add(homeView, homeView.getViewName()); + + return this; + } + /** * Adds the Signup View to the application. * @return this builder */ public AppBuilder addSignupView() { signupViewModel = new SignupViewModel(); - signupView = new SignupView(signupViewModel); + signupView = new SignupView(signupViewModel, viewManagerModel); cardPanel.add(signupView, signupView.getViewName()); return this; } @@ -88,7 +156,7 @@ public AppBuilder addSignupView() { */ public AppBuilder addLoginView() { loginViewModel = new LoginViewModel(); - loginView = new LoginView(loginViewModel); + loginView = new LoginView(loginViewModel, viewManagerModel); cardPanel.add(loginView, loginView.getViewName()); return this; } @@ -104,6 +172,88 @@ public AppBuilder addLoggedInView() { return this; } + /** + * Adds the WatchList View to the application. + * @return this builder + */ + public AppBuilder addWatchListView() { + watchListViewModel = new WatchListViewModel(); + watchListView = new WatchListView(watchListViewModel, viewManagerModel); + cardPanel.add(watchListView, watchListView.getViewName()); + return this; + } + + /** + * Adds the Portfolio View to the application. + * @return this builder + */ + public AppBuilder addPortfolioView() { + portfolioViewModel = new PortfolioViewModel(); + portfolioView = new PortfolioView(portfolioViewModel, viewManagerModel); + cardPanel.add(portfolioView, portfolioView.getViewName()); + return this; + } + + /** + * Adds the Stock View to the application. + * @return this builder + */ + public AppBuilder addStockView() { + stockViewModel = new StockViewModel(); + stockView = new StockView(stockViewModel, viewManagerModel); + cardPanel.add(stockView, stockView.getViewName()); + return this; + } + + /** + * Adds the Buy View to the application. + * @return this builder + */ + public AppBuilder addBuyView() { + buyViewModel = new BuyViewModel(); + buyView = new BuyView(buyViewModel, viewManagerModel); + cardPanel.add(buyView, buyView.getViewName()); + return this; + } + + /** + * Adds the Home Use Case to the application. + * @return this builder + */ + public AppBuilder addHomeUseCase() { + final HomeOutputBoundary homeOutputBoundary = new HomePresenter(homeViewModel, + loginViewModel, + signupViewModel, + viewManagerModel, + portfolioViewModel, + stockViewModel, + watchListViewModel); + final HomeInputBoundary homeInteractor = new HomeInteractor(dbUserDataAccessObject, + fileUserDataAccessObject, + fileUserDataAccessObject, + homeOutputBoundary); + + final HomeController controller = new HomeController(homeInteractor); + homeView.setHomeController(controller); + return this; + } + + /** + * Adds the Home Use Case to the application. + * @return this builder + */ + public AppBuilder addWatchlistUseCase() { + final WatchlistOutputBoundary watchlistOutputBoundary = new WatchlistPresenter( + viewManagerModel, + stockViewModel, + watchListViewModel); + final WatchlistInputBoundary watchlistInteractor = new WatchlistInteractor(dbUserDataAccessObject, watchlistOutputBoundary, dbUserDataAccessObject); + + final WatchlistController controller = new WatchlistController(watchlistInteractor); + watchListView.setwatchlistController(controller); + return this; + } + /** * Adds the Signup Use Case to the application. * @return this builder @@ -112,7 +262,7 @@ public AppBuilder addSignupUseCase() { final SignupOutputBoundary signupOutputBoundary = new SignupPresenter(viewManagerModel, signupViewModel, loginViewModel); final SignupInputBoundary userSignupInteractor = new SignupInteractor( - userDataAccessObject, signupOutputBoundary, userFactory); + dbUserDataAccessObject, signupOutputBoundary, userFactory); final SignupController controller = new SignupController(userSignupInteractor); signupView.setSignupController(controller); @@ -125,9 +275,9 @@ public AppBuilder addSignupUseCase() { */ public AppBuilder addLoginUseCase() { final LoginOutputBoundary loginOutputBoundary = new LoginPresenter(viewManagerModel, - loggedInViewModel, loginViewModel); + loggedInViewModel, loginViewModel, signupViewModel, homeViewModel); final LoginInputBoundary loginInteractor = new LoginInteractor( - userDataAccessObject, loginOutputBoundary); + dbUserDataAccessObject, loginOutputBoundary); final LoginController loginController = new LoginController(loginInteractor); loginView.setLoginController(loginController); @@ -143,7 +293,7 @@ public AppBuilder addChangePasswordUseCase() { new ChangePasswordPresenter(loggedInViewModel); final ChangePasswordInputBoundary changePasswordInteractor = - new ChangePasswordInteractor(userDataAccessObject, changePasswordOutputBoundary, userFactory); + new ChangePasswordInteractor(dbUserDataAccessObject, changePasswordOutputBoundary, userFactory); final ChangePasswordController changePasswordController = new ChangePasswordController(changePasswordInteractor); @@ -160,24 +310,71 @@ public AppBuilder addLogoutUseCase() { loggedInViewModel, loginViewModel); final LogoutInputBoundary logoutInteractor = - new LogoutInteractor(userDataAccessObject, logoutOutputBoundary); + new LogoutInteractor(dbUserDataAccessObject, logoutOutputBoundary); final LogoutController logoutController = new LogoutController(logoutInteractor); loggedInView.setLogoutController(logoutController); return this; } + /** + * Adds the Stock Use Case to the application. + * @return this builder + */ + public AppBuilder addStockUseCase() { + final StockOutputBoundary stockPresenter = new StockPresenter(stockViewModel, + buyViewModel, + homeViewModel, + watchListViewModel, + viewManagerModel); + final StockInputBoundary stockInteractor = + new StockInteractor(stockPresenter, fileUserDataAccessObject, fileUserDataAccessObject); + final StockController stockController = new StockController(stockInteractor); + stockView.setStockController(stockController); + + return this; + } + + /** + * Adds the Buy Use Case to the application. + * @return this builder + */ + public AppBuilder addBuyUseCase() { + final BuyOutputBoundary buyOutputBoundary = new BuyPresenter(buyViewModel, portfolioViewModel, + viewManagerModel, homeViewModel, stockViewModel); + final BuyInputBoundary buyInteractor = new BuyInteractor( + buyOutputBoundary, fileUserDataAccessObject); + + final BuyController buyController = new BuyController(buyInteractor); + buyView.setBuyController(buyController); + return this; + } + + /** + * Adds the Portfolio Use Case to the application. + * @return this builder + */ + public AppBuilder addPortfolioUseCase() { + final PortfolioOutputBoundary portfolioPresenter = new PortfolioPresenter(portfolioViewModel); + final PortfolioInputBoundary portfolioInteractor = + new PortfolioInteractor(fileUserDataAccessObject, dbUserDataAccessObject, portfolioPresenter); + final PortfolioController portfolioController = new PortfolioController(portfolioInteractor); + portfolioView.setController(portfolioController); + + return this; + } + /** * Creates the JFrame for the application and initially sets the SignupView to be displayed. * @return the application */ public JFrame build() { - final JFrame application = new JFrame("Login Example"); + final JFrame application = new JFrame("Stock MarketPlace"); application.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); application.add(cardPanel); - viewManagerModel.setState(signupView.getViewName()); + viewManagerModel.setState(homeView.getViewName()); viewManagerModel.firePropertyChanged(); return application; diff --git a/src/main/java/app/Main.java b/src/main/java/app/Main.java index bef63ad7a..ce4abb325 100644 --- a/src/main/java/app/Main.java +++ b/src/main/java/app/Main.java @@ -12,14 +12,24 @@ public class Main { */ public static void main(String[] args) { final AppBuilder appBuilder = new AppBuilder(); - // TODO: add the Logout Use Case to the app using the appBuilder final JFrame application = appBuilder + .addHomeView() + .addWatchListView() .addLoginView() .addSignupView() .addLoggedInView() + .addStockView() + .addPortfolioView() + .addBuyView() + .addHomeUseCase() + .addWatchlistUseCase() .addSignupUseCase() .addLoginUseCase() + .addLogoutUseCase() .addChangePasswordUseCase() + .addStockUseCase() + .addBuyUseCase() + .addPortfolioUseCase() .build(); application.pack(); diff --git a/src/main/java/data_access/DBUserDataAccessObject.java b/src/main/java/data_access/DBUserDataAccessObject.java index 377ee6e7e..9eef90dc9 100644 --- a/src/main/java/data_access/DBUserDataAccessObject.java +++ b/src/main/java/data_access/DBUserDataAccessObject.java @@ -1,21 +1,24 @@ package data_access; import java.io.IOException; +import java.util.ArrayList; +import entity.*; +import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; -import entity.User; -import entity.UserFactory; import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; import use_case.change_password.ChangePasswordUserDataAccessInterface; +import use_case.home_view.HomeDataAccessInterface; import use_case.login.LoginUserDataAccessInterface; import use_case.logout.LogoutUserDataAccessInterface; import use_case.signup.SignupUserDataAccessInterface; +import use_case.watchlist.WatchListDataAccessInterface; /** * The DAO for user data. @@ -23,8 +26,11 @@ public class DBUserDataAccessObject implements SignupUserDataAccessInterface, LoginUserDataAccessInterface, ChangePasswordUserDataAccessInterface, - LogoutUserDataAccessInterface { + LogoutUserDataAccessInterface, + HomeDataAccessInterface, + WatchListDataAccessInterface { private static final int SUCCESS_CODE = 200; + private static final String CONTENT_TYPE_LABEL = "Content-Type"; private static final String CONTENT_TYPE_JSON = "application/json"; private static final String STATUS_CODE_LABEL = "status_code"; @@ -32,10 +38,11 @@ public class DBUserDataAccessObject implements SignupUserDataAccessInterface, private static final String PASSWORD = "password"; private static final String MESSAGE = "message"; private final UserFactory userFactory; + private final StockFactory stockFactory; - public DBUserDataAccessObject(UserFactory userFactory) { + public DBUserDataAccessObject(UserFactory userFactory, StockFactory stockFactory) { this.userFactory = userFactory; - // No need to do anything to reinitialize a user list! The data is the cloud that may be miles away. + this.stockFactory = stockFactory; } @Override @@ -55,8 +62,11 @@ public User get(String username) { final JSONObject userJSONObject = responseBody.getJSONObject("user"); final String name = userJSONObject.getString(USERNAME); final String password = userJSONObject.getString(PASSWORD); + final ArrayList emptyWatchList = new ArrayList<>(); + final ArrayList emptyStockList = new ArrayList<>(); + final User user = userFactory.create(name, password, emptyWatchList, emptyStockList); - return userFactory.create(name, password); + return user; } else { throw new RuntimeException(responseBody.getString(MESSAGE)); @@ -158,8 +168,52 @@ public void changePassword(User user) { } } + // Stock related APIs + @Override + public Stock getStock(String symbol) { + final OkHttpClient client = new OkHttpClient().newBuilder().build(); + final Request request = new Request.Builder() + .url(String.format("https://api.marketstack.com/v1/eod?access_key=3847d86b56ca461a0da759024332c06a&symbols=%s", symbol)) + .get() + .build(); + try (Response response = client.newCall(request).execute()) { + if (!response.isSuccessful()) throw new RuntimeException("Request Failed: " + response); + + final String responseBody = response.body().string(); + final JSONObject json = new JSONObject(responseBody); + final JSONArray dataArray = json.getJSONArray("data"); + final JSONObject stockData = dataArray.getJSONObject(0); + + final double open = stockData.getDouble("open"); + final double high = stockData.getDouble("high"); + final double low = stockData.getDouble("low"); + final double close = stockData.getDouble("close"); + final double volume = stockData.getDouble("volume"); + + return stockFactory.create(symbol, open, close, volume, high, low); + } + catch (IOException | JSONException ex) { + throw new RuntimeException(ex); + } + } + @Override public String getCurrentUsername() { return null; } + + @Override + public ArrayList getWatchList() { + return null; + } + + @Override + public void saveWatchList() { + + } + + @Override + public void createWatchList() { + + } } diff --git a/src/main/java/data_access/FileUserDataAccessObject.java b/src/main/java/data_access/FileUserDataAccessObject.java index d301a3241..5417eb6bf 100644 --- a/src/main/java/data_access/FileUserDataAccessObject.java +++ b/src/main/java/data_access/FileUserDataAccessObject.java @@ -6,112 +6,272 @@ import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.Map; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.*; -import entity.User; -import entity.UserFactory; -import use_case.change_password.ChangePasswordUserDataAccessInterface; -import use_case.login.LoginUserDataAccessInterface; -import use_case.signup.SignupUserDataAccessInterface; +import entity.*; +import use_case.buy.BuyUserDataAccessInterface; +import use_case.portfolio.PortfolioDataAccessInterface; +import use_case.watchlist.WatchListDataAccessInterface; +import use_case.watchlist.WatchListModifyDataAccessInterface; /** * DAO for user data implemented using a File to persist the data. */ -public class FileUserDataAccessObject implements SignupUserDataAccessInterface, - LoginUserDataAccessInterface, - ChangePasswordUserDataAccessInterface { +public class FileUserDataAccessObject implements WatchListDataAccessInterface, + WatchListModifyDataAccessInterface, PortfolioDataAccessInterface, BuyUserDataAccessInterface { - private static final String HEADER = "username,password"; + private final ArrayList watchList = new ArrayList<>(); + private final ArrayList portfolioList = new ArrayList<>(); - private final File csvFile; - private final Map headers = new LinkedHashMap<>(); - private final Map accounts = new HashMap<>(); - private String currentUsername; + private final String mainFilePath = "src/main/java/data_access/"; + private final String watchListFilePath = "watchlist.txt"; + private final String portfolioFilePath = "portfolio.txt"; + private final String userIsLoggedInFilePath = "userIsLoggedIn.txt"; - public FileUserDataAccessObject(String csvPath, UserFactory userFactory) throws IOException { + private SimulatedHoldingFactory simulatedHoldingFactory; - csvFile = new File(csvPath); - headers.put("username", 0); - headers.put("password", 1); + private boolean userIsLoggedIn; - if (csvFile.length() == 0) { - save(); + // Constructor prepared for userIsLoggedIn data. + public FileUserDataAccessObject() { + } + + public FileUserDataAccessObject(SimulatedHoldingFactory simulatedHoldingFactory) { + this.simulatedHoldingFactory = simulatedHoldingFactory; + } + + // Watch list related APIs + @Override + public ArrayList getWatchList() { + // Check if watchlist.txt file exists + final Boolean isFileExisted = new File(mainFilePath + watchListFilePath).isFile(); + + if (isFileExisted) { + // Using FileReader and BufferedReader to read the file + try (BufferedReader reader = new BufferedReader(new FileReader(mainFilePath + watchListFilePath))) { + watchList.clear(); + final String line = reader.readLine(); + if (line != null) { + Collections.addAll(watchList, line.split(",")); + } + + return watchList; + } + catch (IOException ex) { + // Normally, this means the file doesn't exist + throw new RuntimeException(ex); + } } else { + // If the watchlist file doesn't exist, we create a new one + createWatchList(); - try (BufferedReader reader = new BufferedReader(new FileReader(csvFile))) { - final String header = reader.readLine(); + // Get watchlist data again + return this.getWatchList(); + } + } - if (!header.equals(HEADER)) { - throw new RuntimeException(String.format("header should be%n: %s%but was:%n%s", HEADER, header)); - } + @Override + public void saveWatchList() { + final BufferedWriter writer; + try { + // Override original file + writer = new BufferedWriter(new FileWriter(mainFilePath + watchListFilePath, false)); + writer.write(String.join(",", watchList)); + + writer.close(); + } + catch (IOException ex) { + throw new RuntimeException(ex); + } + } + + @Override + public void createWatchList() { + this.watchList.clear(); + this.watchList.add("AAPL"); + this.watchList.add("COST"); + this.watchList.add("NVDA"); + this.saveWatchList(); + } + + @Override + public void addToWatchList(String symbol) { + watchList.add(symbol); + watchList.sort(String::compareTo); + saveWatchList(); + } + + @Override + public void removeFromWatchList(String symbol) { + watchList.remove(symbol); + saveWatchList(); + } + + // Portfolio list related APIs + public ArrayList getPortfolioList() { + // Check if watchlist.txt file exists + final Boolean isFileExisted = new File(mainFilePath + portfolioFilePath).isFile(); - String row; - while ((row = reader.readLine()) != null) { - final String[] col = row.split(","); - final String username = String.valueOf(col[headers.get("username")]); - final String password = String.valueOf(col[headers.get("password")]); - final User user = userFactory.create(username, password); - accounts.put(username, user); + if (isFileExisted) { + // Using FileReader and BufferedReader to read the file + try (BufferedReader reader = new BufferedReader(new FileReader(mainFilePath + portfolioFilePath))) { + // Clear all data first + portfolioList.clear(); + + String line; + while ((line = reader.readLine()) != null) { + final String[] data = line.split(","); + final String symbol = data[0]; + final double price = Double.parseDouble(data[1]); + final double amount = Double.parseDouble(data[2]); + final SimulatedHolding simulatedHolding = simulatedHoldingFactory.create(symbol, price, amount); + + portfolioList.add(simulatedHolding); } + + return portfolioList; + } + catch (IOException ex) { + // Normally, this means the file doesn't exist + throw new RuntimeException(ex); } } + else { + // If file doesn't exist, we create one first + savePortfolioList(); + + return this.getPortfolioList(); + } } - private void save() { + public void savePortfolioList() { final BufferedWriter writer; try { - writer = new BufferedWriter(new FileWriter(csvFile)); - writer.write(String.join(",", headers.keySet())); - writer.newLine(); - - for (User user : accounts.values()) { - final String line = String.format("%s,%s", - user.getName(), user.getPassword()); - writer.write(line); + // Override original file + writer = new BufferedWriter(new FileWriter(mainFilePath + portfolioFilePath, false)); + for (SimulatedHolding simulatedHolding : portfolioList) { + writer.write(simulatedHolding.getSymbol() + ","); + writer.write(simulatedHolding.getPurchasePrice() + ","); + writer.write(simulatedHolding.getPurchaseAmount() + ","); writer.newLine(); } writer.close(); - } catch (IOException ex) { + // Normally, this means the file doesn't exist throw new RuntimeException(ex); } } @Override - public void save(User user) { - accounts.put(user.getName(), user); - this.save(); + public void createPortfolioList() { + // Create an empty portfolio list file + this.portfolioList.clear(); + savePortfolioList(); } - @Override - public User get(String username) { - return accounts.get(username); + public void addToPortfolioList(SimulatedHolding simulatedHolding) { + boolean duplicate = false; + SimulatedHolding updatedHolding = null; + + for (SimulatedHolding data : new ArrayList<>(portfolioList)) { + if (data.getSymbol().equals(simulatedHolding.getSymbol())) { + final double newAmount = simulatedHolding.getPurchaseAmount() + data.getPurchaseAmount(); + final double newPrice = roundToOneDecimalPlace( + (simulatedHolding.getPurchasePrice() * simulatedHolding.getPurchaseAmount() + + data.getPurchasePrice() * data.getPurchaseAmount()) / newAmount); + + updatedHolding = simulatedHoldingFactory.create(data.getSymbol(), newPrice, newAmount); + + portfolioList.remove(data); + duplicate = true; + break; + } + } + if (duplicate) { + portfolioList.add(updatedHolding); + } + else { + portfolioList.add(simulatedHolding); + } + savePortfolioList(); } - @Override - public void setCurrentUsername(String name) { - this.currentUsername = name; + public void removeFromPortfolioList(SimulatedHolding simulatedHolding) { + portfolioList.remove(simulatedHolding); + savePortfolioList(); } - @Override - public String getCurrentUsername() { - return this.currentUsername; + /** + * Determines if the user is currently logged in by checking the presence and content of a specific file. + * This method reads the login status from a file and returns true if the status indicates the user is logged in. + * If the file does not exist, it assumes the user is not logged in and saves this default status. + * + * @return true if the file exists and contains 'true', false otherwise. + * @throws RuntimeException if reading the file fails. + */ + public boolean isUserLoggedIn() { + // Check whether the file exists + final File file = new File(mainFilePath + userIsLoggedInFilePath); + if (file.isFile()) { + try (BufferedReader reader = new BufferedReader(new FileReader(file))) { + final String line = reader.readLine(); + if (line != null) { + // Converts the read string to a Boolean value + userIsLoggedIn = Boolean.parseBoolean(line); + } + } + catch (IOException ex) { + throw new RuntimeException(ex); + } + } + else { + // If the file does not exist, the default setting is not logged in + userIsLoggedIn = false; + saveUserLoginStatus(); + } + return userIsLoggedIn; } - @Override - public boolean existsByName(String identifier) { - return accounts.containsKey(identifier); + /** + * Saves the current login status of the user to a file. + * This method writes the user's login status as a string ('true' or 'false') to a designated file. + * + * @throws RuntimeException if writing to the file fails. + */ + public void saveUserLoginStatus() { + try (BufferedWriter writer = new BufferedWriter(new FileWriter(mainFilePath + userIsLoggedInFilePath, false))) { + // Converts a Boolean value to a string and writes it to a file + writer.write(Boolean.toString(userIsLoggedIn)); + } + catch (IOException ex) { + throw new RuntimeException(ex); + } } - @Override - public void changePassword(User user) { - // Replace the User object in the map - accounts.put(user.getName(), user); - save(); + /** + * Updates and persists the login status of the user. + * This method sets the user's login status to the specified value and saves it to a file. + * + * @param loggedIn The new login status to set. true if the user is logging in, false otherwise. + */ + public void setUserLoggedIn(boolean loggedIn) { + this.userIsLoggedIn = loggedIn; + saveUserLoginStatus(); + } + + /** + * Helper method to round a double to one decimal place. + * @param value The double value to be rounded. + * @return The rounded value. + */ + private double roundToOneDecimalPlace(double value) { + return BigDecimal.valueOf(value) + .setScale(1, RoundingMode.HALF_UP) + .doubleValue(); } } diff --git a/src/main/java/data_access/InMemoryUserDataAccessObject.java b/src/main/java/data_access/InMemoryUserDataAccessObject.java index 71f00862c..9a6566129 100644 --- a/src/main/java/data_access/InMemoryUserDataAccessObject.java +++ b/src/main/java/data_access/InMemoryUserDataAccessObject.java @@ -1,8 +1,15 @@ package data_access; +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; import java.util.HashMap; import java.util.Map; +import org.json.JSONObject; + +import entity.Stock; import entity.User; import use_case.change_password.ChangePasswordUserDataAccessInterface; import use_case.login.LoginUserDataAccessInterface; diff --git a/src/main/java/data_access/portfolio.txt b/src/main/java/data_access/portfolio.txt new file mode 100644 index 000000000..06c5eeab2 --- /dev/null +++ b/src/main/java/data_access/portfolio.txt @@ -0,0 +1,3 @@ +NVDA,138.3,104.0, +AAPL,322.1,123.0, +COST,322.1,123.0, diff --git a/src/main/java/data_access/userIsLoggedIn.txt b/src/main/java/data_access/userIsLoggedIn.txt new file mode 100644 index 000000000..f32a5804e --- /dev/null +++ b/src/main/java/data_access/userIsLoggedIn.txt @@ -0,0 +1 @@ +true \ No newline at end of file diff --git a/src/main/java/data_access/watchlist.txt b/src/main/java/data_access/watchlist.txt new file mode 100644 index 000000000..12395cbeb --- /dev/null +++ b/src/main/java/data_access/watchlist.txt @@ -0,0 +1 @@ +AAPL,COST,NVDA \ No newline at end of file diff --git a/src/main/java/entity/CommonSimulatedHolding.java b/src/main/java/entity/CommonSimulatedHolding.java new file mode 100644 index 000000000..5300b6e6c --- /dev/null +++ b/src/main/java/entity/CommonSimulatedHolding.java @@ -0,0 +1,39 @@ +package entity; + +public class CommonSimulatedHolding implements SimulatedHolding { + private String symbol; + private double purchasePrice; + private double purchaseAmount; + + public CommonSimulatedHolding(String symbol, double purchasePrice, double purchaseAmount) { + this.symbol = symbol; + this.purchasePrice = purchasePrice; + this.purchaseAmount = purchaseAmount; + } + + @Override + public String getSymbol() { + return symbol; + } + + @Override + public double getPurchasePrice() { + return purchasePrice; + } + + @Override + public double getPurchaseAmount() { + return purchaseAmount; + } + + @Override + public void setPurchasePrice(double purchasePrice) { + this.purchasePrice = purchasePrice; + } + + @Override + public void setPurchaseAmount(double purchaseAmount) { + this.purchaseAmount = purchaseAmount; + } +} + diff --git a/src/main/java/entity/CommonSimulatedHoldingFactory.java b/src/main/java/entity/CommonSimulatedHoldingFactory.java new file mode 100644 index 000000000..b760c933e --- /dev/null +++ b/src/main/java/entity/CommonSimulatedHoldingFactory.java @@ -0,0 +1,11 @@ +package entity; + +/** + * Factory for creating CommonUser objects. + */ +public class CommonSimulatedHoldingFactory implements SimulatedHoldingFactory { + @Override + public SimulatedHolding create(String symbol, double price, double amount) { + return new CommonSimulatedHolding(symbol, price, amount); + } +} diff --git a/src/main/java/entity/CommonStock.java b/src/main/java/entity/CommonStock.java new file mode 100644 index 000000000..c86d6a32c --- /dev/null +++ b/src/main/java/entity/CommonStock.java @@ -0,0 +1,84 @@ +package entity; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +/** + * A simple implementation of the Stock interface. + */ +public class CommonStock implements Stock { + + private final String symbol; + private final double openPrice; + private final double closePrice; + private final double volume; + private final double high; + private final double low; + + public CommonStock(String symbol, double openPrice, double closePrice, double volume, double high, double low) { + this.symbol = symbol; + this.openPrice = openPrice; + this.closePrice = closePrice; + this.volume = volume; + this.high = high; + this.low = low; + } + + @Override + public String getSymbol() { + return symbol; + } + + @Override + public double getOpenPrice() { + return openPrice; + } + + @Override + public double getClosePrice() { + return closePrice; + } + + @Override + public double getVolume() { + return volume; + } + + @Override + public double getHigh() { + return high; + } + + @Override + public double getLow() { + return low; + } + + @Override + public double getDailyChange() { + return roundToOneDecimalPlace(closePrice - openPrice); + } + + @Override + public double getDailyPercentage() { + double result; + if (openPrice == 0) { + result = 0; + } else { + final double percentage = (closePrice - openPrice) / openPrice * 100; + result = roundToOneDecimalPlace(percentage); + } + return result; + } + + /** + * Helper method to round a double to one decimal place. + * @param value The double value to be rounded. + * @return The rounded value. + */ + private double roundToOneDecimalPlace(double value) { + return BigDecimal.valueOf(value) + .setScale(1, RoundingMode.HALF_UP) + .doubleValue(); + } +} diff --git a/src/main/java/entity/CommonStockFactory.java b/src/main/java/entity/CommonStockFactory.java new file mode 100644 index 000000000..160fdd4e1 --- /dev/null +++ b/src/main/java/entity/CommonStockFactory.java @@ -0,0 +1,12 @@ +package entity; + +/** + * Factory for creating CommonUser objects. + */ +public class CommonStockFactory implements StockFactory { + + @Override + public Stock create(String symbol, double openPrice, double closePrice, double volume, double high, double low) { + return new CommonStock(symbol, openPrice, closePrice, volume, high, low); + } +} diff --git a/src/main/java/entity/CommonUser.java b/src/main/java/entity/CommonUser.java index ba25fd20a..5f54e22cb 100644 --- a/src/main/java/entity/CommonUser.java +++ b/src/main/java/entity/CommonUser.java @@ -1,5 +1,8 @@ package entity; +import java.lang.reflect.Array; +import java.util.ArrayList; + /** * A simple implementation of the User interface. */ @@ -7,10 +10,14 @@ public class CommonUser implements User { private final String name; private final String password; + private final ArrayList watchList; + private final ArrayList portfolioList; - public CommonUser(String name, String password) { + public CommonUser(String name, String password, ArrayList watchList, ArrayList portfolioList) { this.name = name; this.password = password; + this.watchList = watchList; + this.portfolioList = portfolioList; } @Override @@ -23,4 +30,13 @@ public String getPassword() { return password; } + @Override + public ArrayList getWatchList() { + return watchList; + } + + @Override + public ArrayList getPortfolioList() { + return portfolioList; + } } diff --git a/src/main/java/entity/CommonUserFactory.java b/src/main/java/entity/CommonUserFactory.java index ebede69e3..d9c528717 100644 --- a/src/main/java/entity/CommonUserFactory.java +++ b/src/main/java/entity/CommonUserFactory.java @@ -1,12 +1,17 @@ package entity; +import java.util.ArrayList; + /** * Factory for creating CommonUser objects. */ public class CommonUserFactory implements UserFactory { @Override - public User create(String name, String password) { - return new CommonUser(name, password); + public User create(String name, + String password, + ArrayList watchList, + ArrayList portfolioList) { + return new CommonUser(name, password, watchList, portfolioList); } } diff --git a/src/main/java/entity/DebugMode.java b/src/main/java/entity/DebugMode.java new file mode 100644 index 000000000..bd59ad5c4 --- /dev/null +++ b/src/main/java/entity/DebugMode.java @@ -0,0 +1,5 @@ +package entity; + +public class DebugMode { + public static boolean debugMode = true; +} diff --git a/src/main/java/entity/SimulatedHolding.java b/src/main/java/entity/SimulatedHolding.java new file mode 100644 index 000000000..0e8dfc786 --- /dev/null +++ b/src/main/java/entity/SimulatedHolding.java @@ -0,0 +1,41 @@ +package entity; + +public interface SimulatedHolding { + /** + * Returns the stock symbol/ticker of the stock. + * @return the stock symbol/ticker of the stock. + */ + String getSymbol(); + + /** + * Returns the purchase price of the stock. + * @return the purchase price of the stock. + */ + double getPurchasePrice(); + + /** + * Returns the amount the stock that user purchased. + * @return the amount the stock that user purchased. + */ + double getPurchaseAmount(); + + /** + * Sets the new purchase price of the stock. + * @param purchasePrice the new purchase price of the stock. + */ + void setPurchasePrice(double purchasePrice); + + /** + * Sets the new purchase amount of the stock. + * @param purchaseAmount the new purchase amount of the stock. + */ + void setPurchaseAmount(double purchaseAmount); + + /** + * Returns the amount of the stock that the user purchased. + * @return the amount of the stock that the user purchased. + */ + default double getAmount() { + return getPurchaseAmount(); + } +} diff --git a/src/main/java/entity/SimulatedHoldingFactory.java b/src/main/java/entity/SimulatedHoldingFactory.java new file mode 100644 index 000000000..62b66f538 --- /dev/null +++ b/src/main/java/entity/SimulatedHoldingFactory.java @@ -0,0 +1,14 @@ +package entity; + +import java.util.ArrayList; + +public interface SimulatedHoldingFactory { + /** + * Creates a new User. + * @param symbol the symbol of stock + * @param price the purchase price of stock + * @param amount the amount that user purchased + * @return the new simulated holding + */ + SimulatedHolding create(String symbol, double price, double amount); +} diff --git a/src/main/java/entity/Stock.java b/src/main/java/entity/Stock.java new file mode 100644 index 000000000..1a2a51da8 --- /dev/null +++ b/src/main/java/entity/Stock.java @@ -0,0 +1,55 @@ +package entity; + +/** + * The representation of a stock in our program. + */ +public interface Stock { + + /** + * Returns the stock symbol/ticker of the stock. + * @return the stock symbol/ticker of the stock. + */ + String getSymbol(); + + /** + * Returns the stock open price of the stock. + * @return the stock open price of the stock. + */ + double getOpenPrice(); + + /** + * Returns the stock close price of the stock. + * @return the stock close price of the stock. + */ + double getClosePrice(); + + /** + * Returns the stock volume of the stock. + * @return the stock volume of the stock. + */ + double getVolume(); + + /** + * Returns the stock highest price of the stock. + * @return the stock highest price of the stock. + */ + double getHigh(); + + /** + * Returns the stock lowest price of the stock. + * @return the stock lowest price of the stock. + */ + double getLow(); + + /** + * Returns the stock dailyChange price of the stock. + * @return the stock dailyChange price of the stock. + */ + double getDailyChange(); + + /** + * Returns the stock dailyPercentage of price of the stock. + * @return the stock dailyPercentage of price of the stock. + */ + double getDailyPercentage(); +} diff --git a/src/main/java/entity/StockFactory.java b/src/main/java/entity/StockFactory.java new file mode 100644 index 000000000..61b5f1dc3 --- /dev/null +++ b/src/main/java/entity/StockFactory.java @@ -0,0 +1,19 @@ +package entity; + +/** + * Factory for creating stocks. + */ +public interface StockFactory { + + /** + * Creates a new User. + * @param symbol the symbol/ticker of the stock + * @param openPrice the open price of the stock + * @param closePrice the close price of the stock + * @param volume the volume of the stock + * @param high the highest price of the stock + * @param low the lowest price of the stock + * @return the new user + */ + Stock create(String symbol, double openPrice, double closePrice, double volume, double high, double low); +} diff --git a/src/main/java/entity/User.java b/src/main/java/entity/User.java index 0ad073902..c504731e6 100644 --- a/src/main/java/entity/User.java +++ b/src/main/java/entity/User.java @@ -1,5 +1,7 @@ package entity; +import java.util.ArrayList; + /** * The representation of a user in our program. */ @@ -17,4 +19,16 @@ public interface User { */ String getPassword(); + /** + * Returns the watch list of the user. + * @return the watch list of the user. + */ + ArrayList getWatchList(); + + /** + * Returns the portfolio of the user. + * @return the portfolio of the user. + */ + ArrayList getPortfolioList(); + } diff --git a/src/main/java/entity/UserFactory.java b/src/main/java/entity/UserFactory.java index c7a508708..a853547b0 100644 --- a/src/main/java/entity/UserFactory.java +++ b/src/main/java/entity/UserFactory.java @@ -1,5 +1,7 @@ package entity; +import java.util.ArrayList; + /** * Factory for creating users. */ @@ -8,8 +10,11 @@ public interface UserFactory { * Creates a new User. * @param name the name of the new user * @param password the password of the new user + * @param watchList the watch list of the new user + * @param portfolioList the portfolio list of the new user * @return the new user */ - User create(String name, String password); + User create(String name, String password, + ArrayList watchList, ArrayList portfolioList); } diff --git a/src/main/java/interface_adapter/ViewManagerModel.java b/src/main/java/interface_adapter/ViewManagerModel.java index 99dc0ffe5..9c2ab0290 100644 --- a/src/main/java/interface_adapter/ViewManagerModel.java +++ b/src/main/java/interface_adapter/ViewManagerModel.java @@ -1,14 +1,50 @@ package interface_adapter; +import java.util.Stack; + /** * Model for the View Manager. Its state is the name of the View which * is currently active. An initial state of "" is used. */ public class ViewManagerModel extends ViewModel { + private final Stack viewStack = new Stack<>(); + /** + * Constructs a ViewManagerModel with the initial state set to "home view". + */ public ViewManagerModel() { super("view manager"); - this.setState(""); + this.setState("home view"); + } + + /** + * Pushes the current view to the stack and navigates to the specified view. + * + * @param viewName The name of the new view to navigate to. + */ + public void pushView(String viewName) { + + if (!this.getState().equals(viewName)) { + viewStack.push(this.getState()); + } + this.setState(viewName); + this.firePropertyChanged(); } + /** + * Pops the last view from the stack and navigates back to it. + * If the stack is empty, it stays on the current view. + */ + public void popView() { + + if (!viewStack.isEmpty()) { + final String previousView = viewStack.pop(); + this.setState(previousView); + this.firePropertyChanged(); + } + else { + this.setState("home view"); + this.firePropertyChanged(); + } + } } diff --git a/src/main/java/interface_adapter/buy_view/BuyController.java b/src/main/java/interface_adapter/buy_view/BuyController.java new file mode 100644 index 000000000..b3ff73b38 --- /dev/null +++ b/src/main/java/interface_adapter/buy_view/BuyController.java @@ -0,0 +1,44 @@ +package interface_adapter.buy_view; + +import use_case.buy.BuyInputBoundary; +import use_case.buy.BuyInputData; + +/** + * The controller for the Buy Use Case. + */ +public class BuyController { + + private final BuyInputBoundary buyUseCaseInteractor; + + public BuyController(BuyInputBoundary buyUseCaseInteractor) { + this.buyUseCaseInteractor = buyUseCaseInteractor; + } + + /** + * Executes the Buy Use Case. + * @param symbol the symbol of the stock + * @param price the price of the stock + * @param quantity the quantity of the stock + */ + + public void execute(String symbol, double price, double quantity) { + final BuyInputData buyInputData = new BuyInputData( + symbol, price, quantity); + + buyUseCaseInteractor.execute(buyInputData); + } + + /** + * Executes the "switch to HomeView" Use Case. + */ + public void switchToHomeView() { + buyUseCaseInteractor.switchToHomeView(); + } + + /** + * Executes the "switch to StockView" Use Case. + */ + public void switchToStockView() { + buyUseCaseInteractor.switchToStockView(); + } +} diff --git a/src/main/java/interface_adapter/buy_view/BuyPresenter.java b/src/main/java/interface_adapter/buy_view/BuyPresenter.java new file mode 100644 index 000000000..5545432d0 --- /dev/null +++ b/src/main/java/interface_adapter/buy_view/BuyPresenter.java @@ -0,0 +1,51 @@ +package interface_adapter.buy_view; + +import interface_adapter.ViewManagerModel; +import interface_adapter.home_view.HomeViewModel; +import interface_adapter.portfolio.PortfolioViewModel; +import interface_adapter.stock_view.StockViewModel; +import use_case.buy.BuyOutputBoundary; +import use_case.buy.BuyOutputData; + +/** + * The Presenter for the Buy Use Case. + */ +public class BuyPresenter implements BuyOutputBoundary { + + private final BuyViewModel buyViewModel; + private final PortfolioViewModel portfolioViewModel; + private final ViewManagerModel viewManagerModel; + private final HomeViewModel homeViewModel; + private final StockViewModel stockViewModel; + + public BuyPresenter(BuyViewModel buyViewModel, PortfolioViewModel portfolioViewModel, + ViewManagerModel viewManagerModel, HomeViewModel homeViewModel, StockViewModel stockViewModel) { + this.buyViewModel = buyViewModel; + this.portfolioViewModel = portfolioViewModel; + this.viewManagerModel = viewManagerModel; + this.homeViewModel = homeViewModel; + this.stockViewModel = stockViewModel; + } + + @Override + public void prepareSuccessView(BuyOutputData response) { + portfolioViewModel.firePropertyChanged("addNewStock"); + + switchToHomeView(); + } + + @Override + public void switchToHomeView() { + viewManagerModel.setState(homeViewModel.getViewName()); + viewManagerModel.firePropertyChanged(); + + } + + @Override + public void switchToStockView() { + viewManagerModel.setState(stockViewModel.getViewName()); + viewManagerModel.firePropertyChanged("switchToStockView"); + + viewManagerModel.pushView("StockView"); + } +} diff --git a/src/main/java/interface_adapter/buy_view/BuyState.java b/src/main/java/interface_adapter/buy_view/BuyState.java new file mode 100644 index 000000000..ea62e9642 --- /dev/null +++ b/src/main/java/interface_adapter/buy_view/BuyState.java @@ -0,0 +1,34 @@ +package interface_adapter.buy_view; + +/** + * The state for the Buy View Model. + */ +public class BuyState { + private String price = ""; + private String quantity = ""; + private String symbol = ""; + + public String getPrice() { + return price; + } + + public String getQuantity() { + return quantity; + } + + public String getSymbol() { + return symbol; + } + + public void setPrice(String price) { + this.price = price; + } + + public void setQuantity(String qunatity) { + this.quantity = qunatity; + } + + public void setSymbol(String symbol) { + this.symbol = symbol; + } +} diff --git a/src/main/java/interface_adapter/buy_view/BuyViewModel.java b/src/main/java/interface_adapter/buy_view/BuyViewModel.java new file mode 100644 index 000000000..f04f8e665 --- /dev/null +++ b/src/main/java/interface_adapter/buy_view/BuyViewModel.java @@ -0,0 +1,18 @@ +package interface_adapter.buy_view; + +import interface_adapter.ViewModel; + +/** + * The ViewModel for the Buy View. + */ + +public class BuyViewModel extends ViewModel { + + public static final String BUY_BUTTON_LABEL = "Sign up"; + public static final String CANCEL_BUTTON_LABEL = "Cancel"; + + public BuyViewModel() { + super("buy view"); + setState(new BuyState()); + } +} diff --git a/src/main/java/interface_adapter/change_password/IsLoggedIn.java b/src/main/java/interface_adapter/change_password/IsLoggedIn.java new file mode 100644 index 000000000..2f1ddeca1 --- /dev/null +++ b/src/main/java/interface_adapter/change_password/IsLoggedIn.java @@ -0,0 +1,19 @@ +package interface_adapter.change_password; + +/** + * Check whether the user is logged in. + */ +public class IsLoggedIn { + private static boolean isLoggedIn; + + public IsLoggedIn() { + } + + public static boolean isLoggedIn() { + return isLoggedIn; + } + + public static void setLoggedIn(boolean whetherLoggedIn) { + IsLoggedIn.isLoggedIn = whetherLoggedIn; + } +} diff --git a/src/main/java/interface_adapter/home_view/HomeController.java b/src/main/java/interface_adapter/home_view/HomeController.java new file mode 100644 index 000000000..599895a8f --- /dev/null +++ b/src/main/java/interface_adapter/home_view/HomeController.java @@ -0,0 +1,70 @@ +package interface_adapter.home_view; + +import entity.Stock; +import use_case.home_view.HomeInputBoundary; +import use_case.home_view.SearchInputData; + +import java.util.ArrayList; + +/** + * The controller for the Login Use Case. + */ +public class HomeController { + + private final HomeInputBoundary homeUseCaseInteractor; + + public HomeController(HomeInputBoundary homeUseCaseInteractor) { + this.homeUseCaseInteractor = homeUseCaseInteractor; + } + + /** + * Executes the Search Use Case. + * @param symbol the username of the user logging in + */ + public void search(String symbol) { + final SearchInputData searchInputData = new SearchInputData(symbol); + homeUseCaseInteractor.search(searchInputData); + } + + /** + * Executes the "switch to Portfolio" Use Case. + */ + public void switchToPortfolio() { + homeUseCaseInteractor.switchToPortfolio(); + } + + /** + * Executes the "switch to LoginView" Use Case. + */ + public void switchToWatchList() { + homeUseCaseInteractor.switchToWatchList(); + } + + /** + * Executes the "switch to LoginView" Use Case. + */ + public void switchToLoginView() { + homeUseCaseInteractor.switchToLoginView(); + } + + /** + * Executes the "switch to SignupView" Use Case. + */ + public void switchToSignupView() { + homeUseCaseInteractor.switchToSignupView(); + } + + /** + * Load watch list data. + */ + public void getWatchList() { + homeUseCaseInteractor.getWatchListData(); + } + + /** + * Delete watchlist and portfolio data, and updates relative views + */ + public void deleteLocalData() { + homeUseCaseInteractor.deleteLocalData(); + } +} diff --git a/src/main/java/interface_adapter/home_view/HomePresenter.java b/src/main/java/interface_adapter/home_view/HomePresenter.java new file mode 100644 index 000000000..cacf4affe --- /dev/null +++ b/src/main/java/interface_adapter/home_view/HomePresenter.java @@ -0,0 +1,126 @@ +package interface_adapter.home_view; + +import entity.Stock; +import interface_adapter.ViewManagerModel; +import interface_adapter.login.LoginViewModel; +import interface_adapter.portfolio.PortfolioViewModel; +import interface_adapter.signup.SignupViewModel; +import interface_adapter.stock_view.StockViewModel; +import interface_adapter.stock_view.StockViewState; +import interface_adapter.stock_view.WatchListViewState; +import interface_adapter.watchlist_view.WatchListViewModel; +import use_case.home_view.HomeOutputBoundary; +import use_case.home_view.SearchOutputData; +import view.WatchListView; + +import java.util.ArrayList; + +/** + * The Presenter for the Search Use Case. + */ +public class HomePresenter implements HomeOutputBoundary { + + private final HomeViewModel homeViewModel; + private final LoginViewModel loginViewModel; + private final SignupViewModel signupViewModel; + private final ViewManagerModel viewManagerModel; + private final PortfolioViewModel portfolioViewModel; + private final StockViewModel stockViewModel; + private final WatchListViewModel watchListViewModel; + + public HomePresenter(HomeViewModel homeViewModel, + LoginViewModel loginViewModel, + SignupViewModel signupViewModel, + ViewManagerModel viewManagerModel, + PortfolioViewModel portfolioViewModel, + StockViewModel stockViewModel, + WatchListViewModel watchListViewModel) { + + this.homeViewModel = homeViewModel; + this.loginViewModel = loginViewModel; + this.signupViewModel = signupViewModel; + this.viewManagerModel = viewManagerModel; + this.portfolioViewModel = portfolioViewModel; + this.stockViewModel = stockViewModel; + this.watchListViewModel = watchListViewModel; + } + + @Override + public void prepareSuccessView(SearchOutputData searchOutputData) { + // Show stock view + final StockViewState stockViewState = new StockViewState(); + stockViewState.setStock(searchOutputData.getStock()); + this.stockViewModel.setState(stockViewState); + this.stockViewModel.firePropertyChanged("switchToStockView"); + + // Clean home view error message + this.sendErrorMessage(""); + + viewManagerModel.pushView("StockView"); + } + + @Override + public void prepareFailView(String errorMessage) { + this.sendErrorMessage(errorMessage); + } + + @Override + public void switchToPortfolio() { + viewManagerModel.setState("PortfolioView"); + viewManagerModel.firePropertyChanged(); + + viewManagerModel.pushView("PortfolioView"); + } + + @Override + public void switchToWatchList(ArrayList watchListStocks) { + // Pass watchList symbols to watchList view + final WatchListViewState watchListViewState = new WatchListViewState(); + watchListViewState.setWatchlist(watchListStocks); + watchListViewModel.setState(watchListViewState); + watchListViewModel.firePropertyChanged("watchList"); + + viewManagerModel.pushView("WatchListView"); + } + + @Override + public void switchToLoginView() { + viewManagerModel.setState(loginViewModel.getViewName()); + viewManagerModel.firePropertyChanged(); + } + + @Override + public void switchToSignupView() { + viewManagerModel.setState(loginViewModel.getViewName()); + viewManagerModel.firePropertyChanged(); + } + + @Override + public void getWatchListData(ArrayList watchList) { + final HomeState homeState = this.homeViewModel.getState(); + homeState.setWatchList(watchList); + this.homeViewModel.setState(homeState); + this.homeViewModel.firePropertyChanged("getWatchList"); + } + + @Override + public void deleteLocalData() { + final HomeState homeState = this.homeViewModel.getState(); + homeState.resetWatchList(); + homeViewModel.setState(homeState); + homeViewModel.firePropertyChanged("getWatchList"); + + final WatchListViewState watchListViewState = this.watchListViewModel.getState(); + watchListViewState.resetWatchList(); + watchListViewModel.setState(watchListViewState); + watchListViewModel.firePropertyChanged("watchList"); + } + + // Helper function + private void sendErrorMessage(String errorMessage) { + final HomeState homeState = this.homeViewModel.getState(); + homeState.setErrorMessage(errorMessage); + this.homeViewModel.setState(homeState); + this.homeViewModel.firePropertyChanged("error"); + } +} diff --git a/src/main/java/interface_adapter/home_view/HomeState.java b/src/main/java/interface_adapter/home_view/HomeState.java new file mode 100644 index 000000000..8e4af3d02 --- /dev/null +++ b/src/main/java/interface_adapter/home_view/HomeState.java @@ -0,0 +1,73 @@ +package interface_adapter.home_view; + +import entity.Stock; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; + +/** + * The state for the Home View Model. + */ +public class HomeState { + private String symbol; + private ArrayList watchList; + private String errorMessage; + + public String getSymbol() { + return symbol; + } + + public ArrayList getWatchList() { + return watchList; + } + + public String getErrorMessage() { + return errorMessage; + } + + public void setSymbol(String symbol) { + this.symbol = symbol; + } + + public void setWatchList(ArrayList watchList) { + this.watchList = watchList; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + + public void add(Stock stock) { + this.watchList.add(stock); + Collections.sort(this.watchList, new Comparator() { + public int compare(Stock o1, Stock o2) { + return o1.getSymbol().compareTo(o2.getSymbol()); + } + }); + System.out.println(watchList.size()); + } + + public void remove(Stock stock) { + int i = 0; + for (Stock s : watchList) { + if (s.getSymbol().equals(stock.getSymbol())) { + break; + } + i++; + } + this.watchList.remove(i); + System.out.println(watchList.size()); + } + + public void resetWatchList() { + final ArrayList temp = new ArrayList<>(); + for (Stock s : watchList) { + if (s.getSymbol().equals("AAPL") || s.getSymbol().equals("COST") || s.getSymbol().equals("NVDA")) { + temp.add(s); + } + } + this.watchList.clear(); + this.watchList.addAll(temp); + } +} diff --git a/src/main/java/interface_adapter/home_view/HomeViewModel.java b/src/main/java/interface_adapter/home_view/HomeViewModel.java new file mode 100644 index 000000000..c72165ac3 --- /dev/null +++ b/src/main/java/interface_adapter/home_view/HomeViewModel.java @@ -0,0 +1,15 @@ +package interface_adapter.home_view; + +import interface_adapter.ViewModel; + +/** + * The View Model for the Home View. + */ +public class HomeViewModel extends ViewModel { + + public HomeViewModel() { + super("home view"); + setState(new HomeState()); + } + +} diff --git a/src/main/java/interface_adapter/login/LoginController.java b/src/main/java/interface_adapter/login/LoginController.java index 57e950666..5ec3363ee 100644 --- a/src/main/java/interface_adapter/login/LoginController.java +++ b/src/main/java/interface_adapter/login/LoginController.java @@ -25,4 +25,11 @@ public void execute(String username, String password) { loginUseCaseInteractor.execute(loginInputData); } + + /** + * Executes the "switch to SignUpView" Use Case. + */ + public void switchToSignUpView() { + loginUseCaseInteractor.switchToSignUpView(); + } } diff --git a/src/main/java/interface_adapter/login/LoginPresenter.java b/src/main/java/interface_adapter/login/LoginPresenter.java index 66560d51a..f373c6a98 100644 --- a/src/main/java/interface_adapter/login/LoginPresenter.java +++ b/src/main/java/interface_adapter/login/LoginPresenter.java @@ -1,8 +1,12 @@ package interface_adapter.login; +import data_access.FileUserDataAccessObject; import interface_adapter.ViewManagerModel; +import interface_adapter.change_password.IsLoggedIn; import interface_adapter.change_password.LoggedInState; import interface_adapter.change_password.LoggedInViewModel; +import interface_adapter.home_view.HomeViewModel; +import interface_adapter.signup.SignupViewModel; import use_case.login.LoginOutputBoundary; import use_case.login.LoginOutputData; @@ -14,26 +18,40 @@ public class LoginPresenter implements LoginOutputBoundary { private final LoginViewModel loginViewModel; private final LoggedInViewModel loggedInViewModel; private final ViewManagerModel viewManagerModel; + private final SignupViewModel signUpViewModel; + private final HomeViewModel homeViewModel; + private final FileUserDataAccessObject dataAccessObject = new FileUserDataAccessObject(); public LoginPresenter(ViewManagerModel viewManagerModel, LoggedInViewModel loggedInViewModel, - LoginViewModel loginViewModel) { + LoginViewModel loginViewModel, + SignupViewModel signUpViewModel, + HomeViewModel homeViewModel) { this.viewManagerModel = viewManagerModel; this.loggedInViewModel = loggedInViewModel; this.loginViewModel = loginViewModel; + this.signUpViewModel = signUpViewModel; + this.homeViewModel = homeViewModel; } @Override public void prepareSuccessView(LoginOutputData response) { // On success, switch to the logged in view. + dataAccessObject.setUserLoggedIn(true); + dataAccessObject.saveUserLoginStatus(); + + homeViewModel.firePropertyChanged(); + final LoggedInState loggedInState = loggedInViewModel.getState(); loggedInState.setUsername(response.getUsername()); + this.loggedInViewModel.setState(loggedInState); this.loggedInViewModel.firePropertyChanged(); - this.viewManagerModel.setState(loggedInViewModel.getViewName()); + this.viewManagerModel.setState(homeViewModel.getViewName()); this.viewManagerModel.firePropertyChanged(); + IsLoggedIn.setLoggedIn(true); } @Override @@ -42,4 +60,10 @@ public void prepareFailView(String error) { loginState.setLoginError(error); loginViewModel.firePropertyChanged(); } + + @Override + public void switchToSignUpView() { + viewManagerModel.setState(signUpViewModel.getViewName()); + viewManagerModel.firePropertyChanged(); + } } diff --git a/src/main/java/interface_adapter/logout/LogoutController.java b/src/main/java/interface_adapter/logout/LogoutController.java index e184a3bba..5e85a6fcc 100644 --- a/src/main/java/interface_adapter/logout/LogoutController.java +++ b/src/main/java/interface_adapter/logout/LogoutController.java @@ -1,6 +1,7 @@ package interface_adapter.logout; import use_case.logout.LogoutInputBoundary; +import use_case.logout.LogoutInputData; /** * The controller for the Logout Use Case. @@ -10,7 +11,7 @@ public class LogoutController { private LogoutInputBoundary logoutUseCaseInteractor; public LogoutController(LogoutInputBoundary logoutUseCaseInteractor) { - // TODO: Save the interactor in the instance variable. + this.logoutUseCaseInteractor = logoutUseCaseInteractor; } /** @@ -18,8 +19,7 @@ public LogoutController(LogoutInputBoundary logoutUseCaseInteractor) { * @param username the username of the user logging in */ public void execute(String username) { - // TODO: run the use case interactor for the logout use case - // 1. instantiate the `LogoutInputData`, which should contain the username. - // 2. tell the Interactor to execute. + final LogoutInputData loginInputData = new LogoutInputData(username); + logoutUseCaseInteractor.execute(loginInputData); } } diff --git a/src/main/java/interface_adapter/logout/LogoutPresenter.java b/src/main/java/interface_adapter/logout/LogoutPresenter.java index 78ef306a1..58fb98e47 100644 --- a/src/main/java/interface_adapter/logout/LogoutPresenter.java +++ b/src/main/java/interface_adapter/logout/LogoutPresenter.java @@ -1,7 +1,9 @@ package interface_adapter.logout; import interface_adapter.ViewManagerModel; +import interface_adapter.change_password.LoggedInState; import interface_adapter.change_password.LoggedInViewModel; +import interface_adapter.login.LoginState; import interface_adapter.login.LoginViewModel; import use_case.logout.LogoutOutputBoundary; import use_case.logout.LogoutOutputData; @@ -16,9 +18,11 @@ public class LogoutPresenter implements LogoutOutputBoundary { private LoginViewModel loginViewModel; public LogoutPresenter(ViewManagerModel viewManagerModel, - LoggedInViewModel loggedInViewModel, + LoggedInViewModel loggedInViewModel, LoginViewModel loginViewModel) { - // TODO: assign to the three instance variables. + this.loggedInViewModel = loggedInViewModel; + this.viewManagerModel = viewManagerModel; + this.loginViewModel = loginViewModel; } @Override @@ -28,18 +32,16 @@ public void prepareSuccessView(LogoutOutputData response) { // We also need to set the username in the LoggedInState to // the empty string. - - // TODO: have prepareSuccessView update the LoggedInState - // 1. get the LoggedInState out of the appropriate View Model, - // 2. set the username in the state to the empty string - // 3. set the state in the LoggedInViewModel to the updated state - // 4. firePropertyChanged so that the View that is listening is updated. - - // TODO: have prepareSuccessView update the LoginState - // 5. get the LoginState out of the appropriate View Model, - // 6. set the username and password in the state to the empty string - // 7. set the state in the LoginViewModel to the updated state - // 8. firePropertyChanged so that the View that is listening is updated. + final LoggedInState loggedInState = loggedInViewModel.getState(); + loggedInState.setUsername(""); + this.loggedInViewModel.setState(loggedInState); + this.loggedInViewModel.firePropertyChanged(); + + final LoginState loginState = loginViewModel.getState(); + loginState.setUsername(""); + loginState.setPassword(""); + this.loginViewModel.setState(loginState); + this.loginViewModel.firePropertyChanged(); // This code tells the View Manager to switch to the LoginView. this.viewManagerModel.setState(loginViewModel.getViewName()); diff --git a/src/main/java/interface_adapter/portfolio/PortfolioController.java b/src/main/java/interface_adapter/portfolio/PortfolioController.java new file mode 100644 index 000000000..07028e2b4 --- /dev/null +++ b/src/main/java/interface_adapter/portfolio/PortfolioController.java @@ -0,0 +1,16 @@ +package interface_adapter.portfolio; + +import use_case.portfolio.PortfolioInputBoundary; + +public class PortfolioController { + + private final PortfolioInputBoundary portfolioInteractor; + + public PortfolioController(PortfolioInputBoundary portfolioInteractor) { + this.portfolioInteractor = portfolioInteractor; + } + + public void getPortfolioList() { + portfolioInteractor.getPortfolioListData(); + } +} diff --git a/src/main/java/interface_adapter/portfolio/PortfolioPresenter.java b/src/main/java/interface_adapter/portfolio/PortfolioPresenter.java new file mode 100644 index 000000000..4c2267aad --- /dev/null +++ b/src/main/java/interface_adapter/portfolio/PortfolioPresenter.java @@ -0,0 +1,25 @@ +package interface_adapter.portfolio; + +import entity.SimulatedHolding; +import entity.Stock; +import use_case.portfolio.PortfolioOutputBoundary; + +import java.util.ArrayList; + +public class PortfolioPresenter implements PortfolioOutputBoundary { + + private final PortfolioViewModel portfolioViewModel; + + public PortfolioPresenter(final PortfolioViewModel portfolioViewModel) { + this.portfolioViewModel = portfolioViewModel; + } + + @Override + public void presentPortfolioListData(ArrayList portfolioList, ArrayList stockList) { + final PortfolioState portfolioState = portfolioViewModel.getState(); + portfolioState.setSimulatedHoldings(portfolioList); + portfolioState.setStocks(stockList); + portfolioViewModel.setState(portfolioState); + portfolioViewModel.firePropertyChanged("getPortfolioList"); + } +} diff --git a/src/main/java/interface_adapter/portfolio/PortfolioState.java b/src/main/java/interface_adapter/portfolio/PortfolioState.java new file mode 100644 index 000000000..1d764522f --- /dev/null +++ b/src/main/java/interface_adapter/portfolio/PortfolioState.java @@ -0,0 +1,34 @@ +package interface_adapter.portfolio; + +import entity.SimulatedHolding; +import entity.Stock; + +import java.util.ArrayList; + +/** + * The state for the Signup View Model. + */ +public class PortfolioState { + private ArrayList stocks; + private ArrayList simulatedHoldings; + + public ArrayList getStocks() { + return stocks; + } + + public ArrayList getSimulatedHoldings() { + return simulatedHoldings; + } + + public void setStocks(ArrayList stocks) { + this.stocks = stocks; + } + + public void setSimulatedHoldings(ArrayList simulatedHoldings) { + this.simulatedHoldings = simulatedHoldings; + } + + public void addToPortfolioList(SimulatedHolding simulatedHolding) { + simulatedHoldings.add(simulatedHolding); + } +} diff --git a/src/main/java/interface_adapter/portfolio/PortfolioViewModel.java b/src/main/java/interface_adapter/portfolio/PortfolioViewModel.java new file mode 100644 index 000000000..ac781e2b2 --- /dev/null +++ b/src/main/java/interface_adapter/portfolio/PortfolioViewModel.java @@ -0,0 +1,18 @@ +package interface_adapter.portfolio; + +import interface_adapter.ViewModel; +import interface_adapter.signup.SignupState; + +import java.util.ArrayList; +import java.util.List; + +/** + * The View Model for the Portfolio View. + */ + +public class PortfolioViewModel extends ViewModel { + public PortfolioViewModel() { + super("porfolio view"); + setState(new PortfolioState()); + } +} \ No newline at end of file diff --git a/src/main/java/interface_adapter/stock_view/StockController.java b/src/main/java/interface_adapter/stock_view/StockController.java new file mode 100644 index 000000000..4f01ab4a2 --- /dev/null +++ b/src/main/java/interface_adapter/stock_view/StockController.java @@ -0,0 +1,33 @@ +package interface_adapter.stock_view; + +import entity.Stock; +import use_case.stock.StockInputBoundary; + +/** + * Controller for the Stock Use Case. + */ +public class StockController { + + private final StockInputBoundary stockInteractor; + + public StockController(StockInputBoundary stockInteractor) { + this.stockInteractor = stockInteractor; + } + + /** + * Executes the buy Stock. + * @param stock the stock to buy + */ + public void buyStock(Stock stock) { + stockInteractor.buyStock(stock); + } + + /** + * Verify watchlist. + * @param stock the username to sign up + * @param shouldModifyData the username to sign up + */ + public void toggleWatchlist(Stock stock, Boolean shouldModifyData) { + stockInteractor.toggleWatchlist(stock, shouldModifyData); + } +} diff --git a/src/main/java/interface_adapter/stock_view/StockPresenter.java b/src/main/java/interface_adapter/stock_view/StockPresenter.java new file mode 100644 index 000000000..73288f7b0 --- /dev/null +++ b/src/main/java/interface_adapter/stock_view/StockPresenter.java @@ -0,0 +1,85 @@ +package interface_adapter.stock_view; + +import entity.Stock; +import interface_adapter.ViewManagerModel; +import interface_adapter.buy_view.BuyState; +import interface_adapter.buy_view.BuyViewModel; +import interface_adapter.home_view.HomeState; +import interface_adapter.home_view.HomeViewModel; +import interface_adapter.stock_view.WatchListViewState; +import interface_adapter.watchlist_view.WatchListViewModel; +import use_case.stock.StockOutputBoundary; +import view.StockView; + +/** + * The Presenter for the stockview Use Case. + */ +public class StockPresenter implements StockOutputBoundary { + + private final StockViewModel stockViewModel; + private final BuyViewModel buyViewModel; + private final HomeViewModel homeViewModel; + private final WatchListViewModel watchListViewModel; + private final ViewManagerModel viewManagerModel; + + public StockPresenter(StockViewModel stockViewModel, + BuyViewModel buyViewModel, + HomeViewModel homeViewModel, + WatchListViewModel watchListViewModel, + ViewManagerModel viewManagerModel) { + this.stockViewModel = stockViewModel; + this.buyViewModel = buyViewModel; + this.homeViewModel = homeViewModel; + this.watchListViewModel = watchListViewModel; + this.viewManagerModel = viewManagerModel; + } + + @Override + public void presentBuyView(Stock stock) { + // Pass stock data to buy view + final BuyState buyState = new BuyState(); + buyState.setSymbol(stock.getSymbol()); + buyState.setPrice(Double.toString(stock.getClosePrice())); + buyViewModel.setState(buyState); + buyViewModel.firePropertyChanged(); + + // Switch to buy view + viewManagerModel.setState("buy view"); + viewManagerModel.firePropertyChanged(); + } + + @Override + public void presentAddToWatchlist(Stock stock) { + final HomeState homeState = homeViewModel.getState(); + final WatchListViewState watchListViewState = watchListViewModel.getState(); + + // Pass new watchlist data to homeView + homeState.add(stock); + homeViewModel.firePropertyChanged("getWatchList"); + + // Pass new watchlist data to watchListView + watchListViewState.add(stock); + watchListViewModel.firePropertyChanged("getWatchList"); + } + + @Override + public void presentRemoveFromWatchlist(Stock stock) { + final HomeState homeState = homeViewModel.getState(); + final WatchListViewState watchListViewState = watchListViewModel.getState(); + + // Pass new watchlist data to homeView + homeState.remove(stock); + homeViewModel.firePropertyChanged("getWatchList"); + + // Pass new watchlist data to watchListView + watchListViewState.remove(stock); + watchListViewModel.firePropertyChanged("watchList"); + } + + @Override + public void updateFavouriteButton(Boolean isFavourite) { + final StockViewState stockViewState = stockViewModel.getState(); + stockViewState.setIsFavorite(isFavourite); + stockViewModel.firePropertyChanged("updateFavouriteButton"); + } +} diff --git a/src/main/java/interface_adapter/stock_view/StockViewModel.java b/src/main/java/interface_adapter/stock_view/StockViewModel.java new file mode 100644 index 000000000..5336688e5 --- /dev/null +++ b/src/main/java/interface_adapter/stock_view/StockViewModel.java @@ -0,0 +1,14 @@ +package interface_adapter.stock_view; + +import interface_adapter.ViewModel; + +/** + * The ViewModel for the Stock View. + */ +public class StockViewModel extends ViewModel { + + public StockViewModel() { + super("stock view"); + setState(new StockViewState()); + } +} diff --git a/src/main/java/interface_adapter/stock_view/StockViewState.java b/src/main/java/interface_adapter/stock_view/StockViewState.java new file mode 100644 index 000000000..ce1798c6b --- /dev/null +++ b/src/main/java/interface_adapter/stock_view/StockViewState.java @@ -0,0 +1,27 @@ +package interface_adapter.stock_view; + +import entity.Stock; + +/** + * The state for the Stock View Model. + */ +public class StockViewState { + private Stock stock; + private Boolean isFavorite; + + public Stock getStock() { + return stock; + } + + public Boolean getIsFavorite() { + return isFavorite; + } + + public void setStock(Stock newStock) { + this.stock = newStock; + } + + public void setIsFavorite(Boolean isFavorite) { + this.isFavorite = isFavorite; + } +} diff --git a/src/main/java/interface_adapter/watchlist_view/WatchListViewModel.java b/src/main/java/interface_adapter/watchlist_view/WatchListViewModel.java new file mode 100644 index 000000000..354655b37 --- /dev/null +++ b/src/main/java/interface_adapter/watchlist_view/WatchListViewModel.java @@ -0,0 +1,27 @@ +package interface_adapter.watchlist_view; + +import entity.Stock; +import interface_adapter.ViewModel; +import interface_adapter.stock_view.StockViewState; +import interface_adapter.stock_view.WatchListViewState; + +import java.beans.PropertyChangeSupport; +import java.util.ArrayList; + +/** + * The View Model for the WatchList View. + */ +public class WatchListViewModel extends ViewModel { + + public WatchListViewModel() { + super("watch list view"); + setState(new WatchListViewState()); + } + + public void updateWatchlist(ArrayList stocks) { + WatchListViewState newState = getState(); + newState.setWatchlist(stocks); + setState(newState); + } + +} diff --git a/src/main/java/interface_adapter/watchlist_view/WatchListViewState.java b/src/main/java/interface_adapter/watchlist_view/WatchListViewState.java new file mode 100644 index 000000000..8631cf117 --- /dev/null +++ b/src/main/java/interface_adapter/watchlist_view/WatchListViewState.java @@ -0,0 +1,84 @@ +package interface_adapter.stock_view; + +import entity.Stock; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; + +/** + * The state for the Stock View Model. + */ +public class WatchListViewState { + private String symbol; + private Stock stock; + private ArrayList watchlist; + + public String getSymbol() { + return symbol; + } + + public Stock getStock() { + return stock; + } + + public ArrayList getWatchlist() { + return watchlist; + } + + public void setStock(Stock newStock) { + this.stock = newStock; + } + + public void setWatchlist(ArrayList newWatchlist) { + this.watchlist = newWatchlist; + } + + public void setSymbol(String symbol) { + this.symbol = symbol; + } + + public void add(Stock stock) { + if (this.watchlist == null) { + this.watchlist = new ArrayList<>(); + } + this.watchlist.add(stock); + Collections.sort(this.watchlist, new Comparator() { + public int compare(Stock o1, Stock o2) { + return o1.getSymbol().compareTo(o2.getSymbol()); + } + }); + System.out.println(watchlist.size()); + } + + public void remove(Stock stock) { + if (this.watchlist == null) { + return; + } + int i = 0; + for (Stock s : watchlist) { + if (s.getSymbol().equals(stock.getSymbol())) { + break; + } + i++; + } + if (i < watchlist.size()) { + this.watchlist.remove(i); + System.out.println(watchlist.size()); + } + } + + public void resetWatchList() { + if (this.watchlist == null) { + return; + } + final ArrayList temp = new ArrayList<>(); + for (Stock s : watchlist) { + if (s.getSymbol().equals("AAPL") || s.getSymbol().equals("COST") || s.getSymbol().equals("NVDA")) { + temp.add(s); + } + } + this.watchlist.clear(); + this.watchlist.addAll(temp); + } +} diff --git a/src/main/java/interface_adapter/watchlist_view/WatchlistController.java b/src/main/java/interface_adapter/watchlist_view/WatchlistController.java new file mode 100644 index 000000000..80df2a952 --- /dev/null +++ b/src/main/java/interface_adapter/watchlist_view/WatchlistController.java @@ -0,0 +1,44 @@ +package interface_adapter.home_view; + +import entity.Stock; +import use_case.home_view.HomeInputBoundary; +import use_case.home_view.SearchInputData; +import use_case.home_view.WatchlistInputBoundary; + +import java.util.ArrayList; + +/** + * The controller for the Login Use Case. + */ +public class WatchlistController { + + private final WatchlistInputBoundary watchlistInteractor; + + public WatchlistController(WatchlistInputBoundary watchlistInteractor) { + this.watchlistInteractor = watchlistInteractor; + } + + /** + * Executes the Search Use Case. + * @param symbol the username of the user logging in + */ + public void search(String symbol) { + final SearchInputData searchInputData = new SearchInputData(symbol); + watchlistInteractor.search(searchInputData); + } + + + /** + * Try to get WatchlistData as Stock information. + * @param stockCodes the code of the stock. + */ + public void loadWatchlistData(ArrayList stockCodes) { + ArrayList stocks = new ArrayList<>(); + for (String stockCode : stockCodes) { + final Stock stock = watchlistInteractor.getStockData(stockCode); + stocks.add(stock); + } + watchlistInteractor.updateWatchlist(stocks); + } + +} \ No newline at end of file diff --git a/src/main/java/interface_adapter/watchlist_view/WatchlistPresenter.java b/src/main/java/interface_adapter/watchlist_view/WatchlistPresenter.java new file mode 100644 index 000000000..0fc0b867c --- /dev/null +++ b/src/main/java/interface_adapter/watchlist_view/WatchlistPresenter.java @@ -0,0 +1,47 @@ +package interface_adapter.home_view; + +import entity.Stock; +import interface_adapter.ViewManagerModel; +import interface_adapter.login.LoginViewModel; +import interface_adapter.portfolio.PortfolioViewModel; +import interface_adapter.signup.SignupViewModel; +import interface_adapter.stock_view.StockViewModel; +import interface_adapter.stock_view.StockViewState; +import interface_adapter.stock_view.WatchListViewState; +import interface_adapter.watchlist_view.WatchListViewModel; +import use_case.home_view.HomeOutputBoundary; +import use_case.home_view.SearchOutputData; +import use_case.home_view.WatchlistOutputBoundary; +import view.WatchListView; + +import java.util.ArrayList; + +/** + * The Presenter for the Search Use Case. + */ +public class WatchlistPresenter implements WatchlistOutputBoundary { + + private final ViewManagerModel viewManagerModel; + private final StockViewModel stockViewModel; + private final WatchListViewModel watchListViewModel; + + public WatchlistPresenter( + ViewManagerModel viewManagerModel, + StockViewModel stockViewModel, + WatchListViewModel watchListViewModel) { + + this.viewManagerModel = viewManagerModel; + this.stockViewModel = stockViewModel; + this.watchListViewModel = watchListViewModel; + } + + public void prepareSuccessView(SearchOutputData searchOutputData) { + // Show stock view + final StockViewState stockViewState = new StockViewState(); + stockViewState.setStock(searchOutputData.getStock()); + this.stockViewModel.setState(stockViewState); + this.stockViewModel.firePropertyChanged("switchToStockView"); + + viewManagerModel.pushView("StockView"); + } +} \ No newline at end of file diff --git a/src/main/java/use_case/buy/BuyInputBoundary.java b/src/main/java/use_case/buy/BuyInputBoundary.java new file mode 100644 index 000000000..fbcdf9695 --- /dev/null +++ b/src/main/java/use_case/buy/BuyInputBoundary.java @@ -0,0 +1,23 @@ +package use_case.buy; + +/** + * Input Boundary for actions which are related to buying stock. + */ +public interface BuyInputBoundary { + + /** + * Executes the buy use case. + * @param buyInputData the input data + */ + void execute(BuyInputData buyInputData); + + /** + * Executes the switch to home view use case. + */ + void switchToHomeView(); + + /** + * Executes the switch to stock view use case. + */ + void switchToStockView(); +} diff --git a/src/main/java/use_case/buy/BuyInputData.java b/src/main/java/use_case/buy/BuyInputData.java new file mode 100644 index 000000000..45569812e --- /dev/null +++ b/src/main/java/use_case/buy/BuyInputData.java @@ -0,0 +1,30 @@ +package use_case.buy; + +/** + * The Input Data for the Buy Use Case. + */ +public class BuyInputData { + + private final String symbol; + private final double price; + private final double quantity; + + public BuyInputData(String symbol, double price, double quantity) { + this.symbol = symbol; + this.price = price; + this.quantity = quantity; + } + + public String getSymbol() { + return symbol; + } + + public double getPrice() { + return price; + } + + public double getQuantity() { + return quantity; + } + +} diff --git a/src/main/java/use_case/buy/BuyInteractor.java b/src/main/java/use_case/buy/BuyInteractor.java new file mode 100644 index 000000000..6a7ddffb7 --- /dev/null +++ b/src/main/java/use_case/buy/BuyInteractor.java @@ -0,0 +1,51 @@ +package use_case.buy; + +import entity.CommonSimulatedHoldingFactory; +import entity.SimulatedHolding; +import use_case.portfolio.PortfolioDataAccessInterface; + +/** + * The Login Interactor. + */ +public class BuyInteractor implements BuyInputBoundary { + + private final BuyOutputBoundary buyPresenter; + private final PortfolioDataAccessInterface portfolioDataAccessObject; + + public BuyInteractor(BuyOutputBoundary buyOutputBoundary, PortfolioDataAccessInterface portfolioDataAccessObject) { + this.buyPresenter = buyOutputBoundary; + this.portfolioDataAccessObject = portfolioDataAccessObject; + } + + @Override + public void execute(BuyInputData buyInputData) { + final String symbol = buyInputData.getSymbol(); + final double price = buyInputData.getPrice(); + final double quantity = buyInputData.getQuantity(); + + final CommonSimulatedHoldingFactory commonSimulatedHoldingFactory = new CommonSimulatedHoldingFactory(); + final SimulatedHolding simulatedHolding = commonSimulatedHoldingFactory.create(symbol, price, quantity); + + // Add simulated holding into database + portfolioDataAccessObject.addToPortfolioList(simulatedHolding); + + // Pass data to Portfolio View + final BuyOutputData buyOutputData = new BuyOutputData(simulatedHolding); + buyPresenter.prepareSuccessView(buyOutputData); + + // Tempory output data + System.out.println("User purchases: " + quantity + " " + symbol + " stock(s) at $" + price); + } + + @Override + public void switchToHomeView() { + + buyPresenter.switchToHomeView(); + } + + @Override + public void switchToStockView() { + + buyPresenter.switchToStockView(); + } +} diff --git a/src/main/java/use_case/buy/BuyOutputBoundary.java b/src/main/java/use_case/buy/BuyOutputBoundary.java new file mode 100644 index 000000000..0d7a670b0 --- /dev/null +++ b/src/main/java/use_case/buy/BuyOutputBoundary.java @@ -0,0 +1,23 @@ +package use_case.buy; + +/** + * The output boundary for the Buy Use Case. + */ +public interface BuyOutputBoundary { + + /** + * Prepares the success view for the Buy Use Case. + * @param outputData the output data + */ + void prepareSuccessView(BuyOutputData outputData); + + /** + * Switches to the Home View. + */ + void switchToHomeView(); + + /** + * Switches to the Stock View. + */ + void switchToStockView(); +} diff --git a/src/main/java/use_case/buy/BuyOutputData.java b/src/main/java/use_case/buy/BuyOutputData.java new file mode 100644 index 000000000..8369962ce --- /dev/null +++ b/src/main/java/use_case/buy/BuyOutputData.java @@ -0,0 +1,18 @@ +package use_case.buy; + +import entity.SimulatedHolding; + +/** + * Output Data for the Buy Use Case. + */ +public class BuyOutputData { + private final SimulatedHolding simulatedHolding; + + public BuyOutputData(SimulatedHolding simulatedHolding) { + this.simulatedHolding = simulatedHolding; + } + + public SimulatedHolding getSimulatedHolding() { + return simulatedHolding; + } +} diff --git a/src/main/java/use_case/buy/BuyUserDataAccessInterface.java b/src/main/java/use_case/buy/BuyUserDataAccessInterface.java new file mode 100644 index 000000000..f9d0d403b --- /dev/null +++ b/src/main/java/use_case/buy/BuyUserDataAccessInterface.java @@ -0,0 +1,7 @@ +package use_case.buy; + +/** + * DAO for the Buy Use Case. + */ +public interface BuyUserDataAccessInterface { +} diff --git a/src/main/java/use_case/change_password/ChangePasswordInteractor.java b/src/main/java/use_case/change_password/ChangePasswordInteractor.java index df91196c1..de048ee39 100644 --- a/src/main/java/use_case/change_password/ChangePasswordInteractor.java +++ b/src/main/java/use_case/change_password/ChangePasswordInteractor.java @@ -1,5 +1,7 @@ package use_case.change_password; +import java.util.ArrayList; + import entity.User; import entity.UserFactory; @@ -22,7 +24,9 @@ public ChangePasswordInteractor(ChangePasswordUserDataAccessInterface changePass @Override public void execute(ChangePasswordInputData changePasswordInputData) { final User user = userFactory.create(changePasswordInputData.getUsername(), - changePasswordInputData.getPassword()); + changePasswordInputData.getPassword(), + new ArrayList<>(), + new ArrayList<>()); userDataAccessObject.changePassword(user); final ChangePasswordOutputData changePasswordOutputData = new ChangePasswordOutputData(user.getName(), diff --git a/src/main/java/use_case/home_view/HomeDataAccessInterface.java b/src/main/java/use_case/home_view/HomeDataAccessInterface.java new file mode 100644 index 000000000..c2b6a568f --- /dev/null +++ b/src/main/java/use_case/home_view/HomeDataAccessInterface.java @@ -0,0 +1,16 @@ +package use_case.home_view; + +import entity.Stock; + +/** + * DAO for the Login Use Case. + */ +public interface HomeDataAccessInterface { + + /** + * Returns the stock with the given symbol. + * @param symbol the stock symbol to look up + * @return the stock with the given symbol + */ + Stock getStock(String symbol); +} diff --git a/src/main/java/use_case/home_view/HomeInputBoundary.java b/src/main/java/use_case/home_view/HomeInputBoundary.java new file mode 100644 index 000000000..b59da3ef1 --- /dev/null +++ b/src/main/java/use_case/home_view/HomeInputBoundary.java @@ -0,0 +1,44 @@ +package use_case.home_view; + +import entity.Stock; + +import java.util.ArrayList; + +public interface HomeInputBoundary { + + /** + * Executes the search use case. + * @param searchInputData the input data + */ + void search(SearchInputData searchInputData); + + /** + * Executes the switch to portfolio view use case. + */ + void switchToPortfolio(); + + /** + * Executes the switch to watch list view use case. + */ + void switchToWatchList(); + + /** + * Executes the switch to login view use case. + */ + void switchToLoginView(); + + /** + * Executes the switch to Signup view use case. + */ + void switchToSignupView(); + + /** + * Executes the switch to Signup view use case. + */ + void getWatchListData(); + + /** + * Executes the switch to Signup view use case. + */ + void deleteLocalData(); +} diff --git a/src/main/java/use_case/home_view/HomeInteractor.java b/src/main/java/use_case/home_view/HomeInteractor.java new file mode 100644 index 000000000..7b27eee7a --- /dev/null +++ b/src/main/java/use_case/home_view/HomeInteractor.java @@ -0,0 +1,136 @@ +package use_case.home_view; + +import entity.CommonStockFactory; +import entity.DebugMode; +import entity.Stock; +import entity.StockFactory; +import use_case.portfolio.PortfolioDataAccessInterface; +import use_case.watchlist.WatchListDataAccessInterface; + +import java.util.ArrayList; +import java.util.Random; + +/** + * The Home View Interactor. + */ +public class HomeInteractor implements HomeInputBoundary { + + private final HomeDataAccessInterface homeDataAccessInterface; + private final WatchListDataAccessInterface watchListDataAccessInterface; + private final PortfolioDataAccessInterface portfolioDataAccessInterface; + private final HomeOutputBoundary homePresenter; + + public HomeInteractor(HomeDataAccessInterface homeDataAccessInterface, + WatchListDataAccessInterface watchListDataAccessInterface, + PortfolioDataAccessInterface portfolioDataAccessInterface, + HomeOutputBoundary homeInteractor) { + this.homeDataAccessInterface = homeDataAccessInterface; + this.watchListDataAccessInterface = watchListDataAccessInterface; + this.portfolioDataAccessInterface = portfolioDataAccessInterface; + this.homePresenter = homeInteractor; + } + + @Override + public void search(SearchInputData searchInputData) { + // Default search symbol is NVDA + String stockSymbol = searchInputData.getStockSymbol(); + if (stockSymbol == null || stockSymbol.isEmpty()) { + stockSymbol = "NVDA"; + } + else { + stockSymbol = stockSymbol.toUpperCase(); + } + + if (DebugMode.debugMode) { + // Using fake data to search stock data based on symbol + final StockFactory stockFactory = new CommonStockFactory(); + final Stock stock = stockFactory.create(stockSymbol, 128.2, 322.1, 100002322, 500.1, 100.23); + + final SearchOutputData searchOutputData = new SearchOutputData(stock, false); + homePresenter.prepareSuccessView(searchOutputData); + } + else { + // Using real data to search stock data based on symbol + try { + final Stock stock = homeDataAccessInterface.getStock(stockSymbol); + final SearchOutputData searchOutputData = new SearchOutputData(stock, false); + homePresenter.prepareSuccessView(searchOutputData); + } + catch (Exception err) { + err.printStackTrace(); + homePresenter.prepareFailView("Failed to fetch stock data"); + } + } + } + + @Override + public void switchToPortfolio() { + homePresenter.switchToPortfolio(); + } + + @Override + public void switchToWatchList() { + final ArrayList watchListData = watchListDataAccessInterface.getWatchList(); + final ArrayList watchList = new ArrayList<>(); + + final StockFactory stockFactory = new CommonStockFactory(); + + for (String symbol : watchListData) { + if (DebugMode.debugMode) { + // Using fake data + final Stock stock = stockFactory.create(symbol, 0.0, 0.0, 0, 0.0, 0.0); + watchList.add(stock); + } + else { + // Using real data + final Stock stock = homeDataAccessInterface.getStock(symbol); + watchList.add(stock); + } + + } + + homePresenter.switchToWatchList(watchList); + } + + @Override + public void switchToLoginView() { + homePresenter.switchToLoginView(); + } + + @Override + public void switchToSignupView() { + homePresenter.switchToSignupView(); + } + + @Override + public void getWatchListData() { + final ArrayList watchListData = watchListDataAccessInterface.getWatchList(); + final ArrayList watchList = new ArrayList<>(); + + if (DebugMode.debugMode) { + // Using fake data + final StockFactory stockFactory = new CommonStockFactory(); + for (String symbol : watchListData) { + final Stock stock = stockFactory.create(symbol, 0, 0, 100002322, 500.1, 100.23); + watchList.add(stock); + } + } + else { + // Using real data + for (String symbol : watchListData) { + final Stock stockData = homeDataAccessInterface.getStock(symbol); + watchList.add(stockData); + } + } + + homePresenter.getWatchListData(watchList); + } + + @Override + public void deleteLocalData() { + watchListDataAccessInterface.createWatchList(); + portfolioDataAccessInterface.createPortfolioList(); + + homePresenter.deleteLocalData(); + } +} diff --git a/src/main/java/use_case/home_view/HomeOutputBoundary.java b/src/main/java/use_case/home_view/HomeOutputBoundary.java new file mode 100644 index 000000000..5c0ecb5c2 --- /dev/null +++ b/src/main/java/use_case/home_view/HomeOutputBoundary.java @@ -0,0 +1,55 @@ +package use_case.home_view; + +import entity.Stock; + +import java.util.ArrayList; + +/** + * The output boundary for the Home Use Case. + */ +public interface HomeOutputBoundary { + + /** + * Prepares the success view for the Search Use Case. + * @param searchOutputData the output data + */ + void prepareSuccessView(SearchOutputData searchOutputData); + + /** + * Prepares the failure view for the Search Use Case. + * @param errorMessage the explanation of the failure + */ + void prepareFailView(String errorMessage); + + /** + * Switches to the watch list View. + */ + void switchToPortfolio(); + + /** + * Switches to the watch list View. + * @param watchListSymbols the symbols of whole watch list + */ + void switchToWatchList(ArrayList watchListSymbols); + + /** + * Switches to the Login View. + */ + void switchToLoginView(); + + /** + * Switches to the Signup View. + */ + void switchToSignupView(); + + /** + * Preload watchlist data for Home View. + * @param watchList watchList data + */ + void getWatchListData(ArrayList watchList); + + /** + * Delete watchlist and portfolio data, and updates relative views + */ + void deleteLocalData(); +} diff --git a/src/main/java/use_case/home_view/SearchInputData.java b/src/main/java/use_case/home_view/SearchInputData.java new file mode 100644 index 000000000..072feb612 --- /dev/null +++ b/src/main/java/use_case/home_view/SearchInputData.java @@ -0,0 +1,17 @@ +package use_case.home_view; + +/** + * The Input Data for the Search Use Case. + */ +public class SearchInputData { + + private final String stockSymbol; + + public SearchInputData(String stockSymbol) { + this.stockSymbol = stockSymbol; + } + + public String getStockSymbol() { + return stockSymbol; + } +} diff --git a/src/main/java/use_case/home_view/SearchOutputData.java b/src/main/java/use_case/home_view/SearchOutputData.java new file mode 100644 index 000000000..366448a11 --- /dev/null +++ b/src/main/java/use_case/home_view/SearchOutputData.java @@ -0,0 +1,21 @@ +package use_case.home_view; + +import entity.Stock; + +/** + * Output Data for the Search Use Case. + */ +public class SearchOutputData { + + private final Stock stock; + private final boolean useCaseFailed; + + public SearchOutputData(Stock stock, boolean useCaseFailed) { + this.stock = stock; + this.useCaseFailed = useCaseFailed; + } + + public Stock getStock() { + return stock; + } +} diff --git a/src/main/java/use_case/login/LoginInputBoundary.java b/src/main/java/use_case/login/LoginInputBoundary.java index faf72dc96..12819e5d7 100644 --- a/src/main/java/use_case/login/LoginInputBoundary.java +++ b/src/main/java/use_case/login/LoginInputBoundary.java @@ -10,4 +10,9 @@ public interface LoginInputBoundary { * @param loginInputData the input data */ void execute(LoginInputData loginInputData); + + /** + * Executes the switch to SignUp view use case. + */ + void switchToSignUpView(); } diff --git a/src/main/java/use_case/login/LoginInteractor.java b/src/main/java/use_case/login/LoginInteractor.java index 5b36ddcd8..27fa4bc92 100644 --- a/src/main/java/use_case/login/LoginInteractor.java +++ b/src/main/java/use_case/login/LoginInteractor.java @@ -1,11 +1,15 @@ package use_case.login; +import data_access.FileUserDataAccessObject; import entity.User; /** * The Login Interactor. */ public class LoginInteractor implements LoginInputBoundary { + // Add DAO + private final FileUserDataAccessObject dataAccessObject; + private final LoginUserDataAccessInterface userDataAccessObject; private final LoginOutputBoundary loginPresenter; @@ -13,6 +17,7 @@ public LoginInteractor(LoginUserDataAccessInterface userDataAccessInterface, LoginOutputBoundary loginOutputBoundary) { this.userDataAccessObject = userDataAccessInterface; this.loginPresenter = loginOutputBoundary; + this.dataAccessObject = new FileUserDataAccessObject(); } @Override @@ -28,6 +33,8 @@ public void execute(LoginInputData loginInputData) { loginPresenter.prepareFailView("Incorrect password for \"" + username + "\"."); } else { + dataAccessObject.setUserLoggedIn(true); + dataAccessObject.saveUserLoginStatus(); final User user = userDataAccessObject.get(loginInputData.getUsername()); @@ -37,4 +44,9 @@ public void execute(LoginInputData loginInputData) { } } } + + @Override + public void switchToSignUpView() { + loginPresenter.switchToSignUpView(); + } } diff --git a/src/main/java/use_case/login/LoginOutputBoundary.java b/src/main/java/use_case/login/LoginOutputBoundary.java index 08bc4731f..81c6934f3 100644 --- a/src/main/java/use_case/login/LoginOutputBoundary.java +++ b/src/main/java/use_case/login/LoginOutputBoundary.java @@ -15,4 +15,9 @@ public interface LoginOutputBoundary { * @param errorMessage the explanation of the failure */ void prepareFailView(String errorMessage); + + /** + * Switches to the SignUp View. + */ + void switchToSignUpView(); } diff --git a/src/main/java/use_case/logout/LogoutInputData.java b/src/main/java/use_case/logout/LogoutInputData.java index 56a33b375..bd972173d 100644 --- a/src/main/java/use_case/logout/LogoutInputData.java +++ b/src/main/java/use_case/logout/LogoutInputData.java @@ -4,9 +4,13 @@ * The Input Data for the Logout Use Case. */ public class LogoutInputData { + private final String username; public LogoutInputData(String username) { - // TODO: save the current username in an instance variable and add a getter. + this.username = username; } + String getUsername() { + return username; + } } diff --git a/src/main/java/use_case/logout/LogoutInteractor.java b/src/main/java/use_case/logout/LogoutInteractor.java index 1ca93b44e..90ca7f559 100644 --- a/src/main/java/use_case/logout/LogoutInteractor.java +++ b/src/main/java/use_case/logout/LogoutInteractor.java @@ -9,17 +9,16 @@ public class LogoutInteractor implements LogoutInputBoundary { public LogoutInteractor(LogoutUserDataAccessInterface userDataAccessInterface, LogoutOutputBoundary logoutOutputBoundary) { - // TODO: save the DAO and Presenter in the instance variables. - // Which parameter is the DAO and which is the presenter? + this.userDataAccessObject = userDataAccessInterface; + this.logoutPresenter = logoutOutputBoundary; } @Override public void execute(LogoutInputData logoutInputData) { - // TODO: implement the logic of the Logout Use Case (depends on the LogoutInputData.java TODO) - // * get the username out of the input data, - // * set the username to null in the DAO - // * instantiate the `LogoutOutputData`, which needs to contain the username. - // * tell the presenter to prepare a success view. + final String username = logoutInputData.getUsername(); + userDataAccessObject.setCurrentUsername(null); + final LogoutOutputData logoutOutputData = new LogoutOutputData(username, false); + logoutPresenter.prepareSuccessView(logoutOutputData); } } diff --git a/src/main/java/use_case/logout/LogoutOutputData.java b/src/main/java/use_case/logout/LogoutOutputData.java index 974279155..ad26e464f 100644 --- a/src/main/java/use_case/logout/LogoutOutputData.java +++ b/src/main/java/use_case/logout/LogoutOutputData.java @@ -9,7 +9,8 @@ public class LogoutOutputData { private boolean useCaseFailed; public LogoutOutputData(String username, boolean useCaseFailed) { - // TODO: save the parameters in the instance variables. + this.username = username; + this.useCaseFailed = useCaseFailed; } public String getUsername() { @@ -20,3 +21,4 @@ public boolean isUseCaseFailed() { return useCaseFailed; } } + diff --git a/src/main/java/use_case/portfolio/PortfolioDataAccessInterface.java b/src/main/java/use_case/portfolio/PortfolioDataAccessInterface.java new file mode 100644 index 000000000..7067a148a --- /dev/null +++ b/src/main/java/use_case/portfolio/PortfolioDataAccessInterface.java @@ -0,0 +1,42 @@ +package use_case.portfolio; + +import java.util.ArrayList; + +import entity.SimulatedHolding; + +/** + * Interface for managing portfolio data. + */ +public interface PortfolioDataAccessInterface { + + /** + * Get the portfolio list. + * + * @return the portfolio list + */ + ArrayList getPortfolioList(); + + /** + * Save the portfolio list. + */ + void savePortfolioList(); + + /** + * Add a holding to the portfolio list. + * + * @param simulatedHolding the holding to add + */ + void addToPortfolioList(SimulatedHolding simulatedHolding); + + /** + * Remove a holding from the portfolio list. + * + * @param simulatedHolding the holding to remove + */ + void removeFromPortfolioList(SimulatedHolding simulatedHolding); + + /** + * Create a new portfolio list. + */ + void createPortfolioList(); +} diff --git a/src/main/java/use_case/portfolio/PortfolioInputBoundary.java b/src/main/java/use_case/portfolio/PortfolioInputBoundary.java new file mode 100644 index 000000000..9c68778c9 --- /dev/null +++ b/src/main/java/use_case/portfolio/PortfolioInputBoundary.java @@ -0,0 +1,11 @@ +package use_case.portfolio; + +/** + * Input Boundary for actions which are related to portfolio. + */ +public interface PortfolioInputBoundary { + /** + * Get portfolio list data. + */ + void getPortfolioListData(); +} diff --git a/src/main/java/use_case/portfolio/PortfolioInteractor.java b/src/main/java/use_case/portfolio/PortfolioInteractor.java new file mode 100644 index 000000000..fe0b49af9 --- /dev/null +++ b/src/main/java/use_case/portfolio/PortfolioInteractor.java @@ -0,0 +1,63 @@ +package use_case.portfolio; + +import java.util.ArrayList; + +import entity.CommonStockFactory; +import entity.DebugMode; +import entity.SimulatedHolding; +import entity.Stock; +import use_case.home_view.HomeDataAccessInterface; + +/** + * The Portfolio Interactor. + */ +public class PortfolioInteractor implements PortfolioInputBoundary { + private static final int INITIAL_STOCK_PRICE = 100; + private static final int STOCK_PRICE_INCREMENT = 100; + private static final int ADDITIONAL_PRICE = 50; + private static final double FAKE_VOLUME = 10000000.2; + + private final PortfolioDataAccessInterface portfolioDataAccessObject; + private final HomeDataAccessInterface homeDataAccessObject; + private final PortfolioOutputBoundary portfolioPresenter; + + public PortfolioInteractor(PortfolioDataAccessInterface portfolioDataAccessObject, + HomeDataAccessInterface homeDataAccessObject, + PortfolioOutputBoundary portfolioPresenter) { + this.portfolioDataAccessObject = portfolioDataAccessObject; + this.homeDataAccessObject = homeDataAccessObject; + this.portfolioPresenter = portfolioPresenter; + } + + @Override + public void getPortfolioListData() { + final ArrayList simulatedHoldings = portfolioDataAccessObject.getPortfolioList(); + final ArrayList stockList = new ArrayList<>(); + + if (DebugMode.debugMode) { + // Using fake data + int stockPrice = INITIAL_STOCK_PRICE; + final CommonStockFactory stockFactory = new CommonStockFactory(); + for (SimulatedHolding simulatedHolding : simulatedHoldings) { + final Stock stock = stockFactory.create( + simulatedHolding.getSymbol(), + 0, + stockPrice + ADDITIONAL_PRICE, + FAKE_VOLUME, + 0, + 0 + ); + stockList.add(stock); + + stockPrice += STOCK_PRICE_INCREMENT; + } + } + else { + // Using real data + for (SimulatedHolding simulatedHolding : simulatedHoldings) { + final Stock stock = homeDataAccessObject.getStock(simulatedHolding.getSymbol()); + stockList.add(stock); + } + } + } +} diff --git a/src/main/java/use_case/portfolio/PortfolioOutputBoundary.java b/src/main/java/use_case/portfolio/PortfolioOutputBoundary.java new file mode 100644 index 000000000..b791bea1a --- /dev/null +++ b/src/main/java/use_case/portfolio/PortfolioOutputBoundary.java @@ -0,0 +1,18 @@ +package use_case.portfolio; + +import java.util.ArrayList; + +import entity.SimulatedHolding; +import entity.Stock; + +/** + * Output Data for the portfolio Use Case. + */ +public interface PortfolioOutputBoundary { + /** + * Present portfolio list data for Portfolio View. + * @param portfolioList portfolio list data + * @param stockList portfolio list data + */ + void presentPortfolioListData(ArrayList portfolioList, ArrayList stockList); +} diff --git a/src/main/java/use_case/signup/SignupInteractor.java b/src/main/java/use_case/signup/SignupInteractor.java index 3fd6560c7..5f95f8ec7 100644 --- a/src/main/java/use_case/signup/SignupInteractor.java +++ b/src/main/java/use_case/signup/SignupInteractor.java @@ -1,5 +1,7 @@ package use_case.signup; +import java.util.ArrayList; + import entity.User; import entity.UserFactory; @@ -28,7 +30,10 @@ else if (!signupInputData.getPassword().equals(signupInputData.getRepeatPassword userPresenter.prepareFailView("Passwords don't match."); } else { - final User user = userFactory.create(signupInputData.getUsername(), signupInputData.getPassword()); + final User user = userFactory.create(signupInputData.getUsername(), + signupInputData.getPassword(), + new ArrayList<>(), + new ArrayList<>()); userDataAccessObject.save(user); final SignupOutputData signupOutputData = new SignupOutputData(user.getName(), false); diff --git a/src/main/java/use_case/stock/StockInputBoundary.java b/src/main/java/use_case/stock/StockInputBoundary.java new file mode 100644 index 000000000..0330cac3d --- /dev/null +++ b/src/main/java/use_case/stock/StockInputBoundary.java @@ -0,0 +1,21 @@ +package use_case.stock; + +import entity.Stock; + +/** + * Input Boundary for actions which are related to Stock. + */ +public interface StockInputBoundary { + /** + * Handles the logic when a user wants to buy a stock. + * @param stock The stock to buy. + */ + void buyStock(Stock stock); + + /** + * Handles adding or removing a stock from the watchlist. + * @param stock The stock to add/remove from the watchlist. + * @param shouldModifyData To decide if we should modify data or not. + */ + void toggleWatchlist(Stock stock, Boolean shouldModifyData); +} diff --git a/src/main/java/use_case/stock/StockInteractor.java b/src/main/java/use_case/stock/StockInteractor.java new file mode 100644 index 000000000..ee160c8a0 --- /dev/null +++ b/src/main/java/use_case/stock/StockInteractor.java @@ -0,0 +1,55 @@ +package use_case.stock; + +import java.util.ArrayList; + +import entity.Stock; +import use_case.watchlist.WatchListDataAccessInterface; +import use_case.watchlist.WatchListModifyDataAccessInterface; + +/** + * The Stock Interactor. + */ +public class StockInteractor implements StockInputBoundary { + + private final StockOutputBoundary stockPresenter; + private final WatchListDataAccessInterface watchListDataAccessInterface; + private final WatchListModifyDataAccessInterface watchListModifyDataAccessInterface; + + public StockInteractor(StockOutputBoundary stockPresenter, + WatchListDataAccessInterface watchListDataAccessInterface, + WatchListModifyDataAccessInterface watchListModifyDataAccessInterface) { + this.stockPresenter = stockPresenter; + this.watchListDataAccessInterface = watchListDataAccessInterface; + this.watchListModifyDataAccessInterface = watchListModifyDataAccessInterface; + } + + @Override + public void buyStock(Stock stock) { + stockPresenter.presentBuyView(stock); + } + + @Override + public void toggleWatchlist(Stock stock, Boolean shouldModifyData) { + final ArrayList watchList = watchListDataAccessInterface.getWatchList(); + if (watchList.contains(stock.getSymbol())) { + if (shouldModifyData) { + watchListModifyDataAccessInterface.removeFromWatchList(stock.getSymbol()); + stockPresenter.presentRemoveFromWatchlist(stock); + stockPresenter.updateFavouriteButton(false); + } + else { + stockPresenter.updateFavouriteButton(true); + } + } + else { + if (shouldModifyData) { + watchListModifyDataAccessInterface.addToWatchList(stock.getSymbol()); + stockPresenter.presentAddToWatchlist(stock); + stockPresenter.updateFavouriteButton(true); + } + else { + stockPresenter.updateFavouriteButton(false); + } + } + } +} diff --git a/src/main/java/use_case/stock/StockOutputBoundary.java b/src/main/java/use_case/stock/StockOutputBoundary.java new file mode 100644 index 000000000..a946a3716 --- /dev/null +++ b/src/main/java/use_case/stock/StockOutputBoundary.java @@ -0,0 +1,32 @@ +package use_case.stock; + +import entity.Stock; + +/** + * Output Boundary for actions which are related to stock. + */ +public interface StockOutputBoundary { + /** + * Presents the Buy View when a user wants to buy a stock. + * @param stock The stock to buy. + */ + void presentBuyView(Stock stock); + + /** + * Presents the updated watchlist after adding a stock. + * @param stock The stock that was added. + */ + void presentAddToWatchlist(Stock stock); + + /** + * Presents the updated watchlist after removing a stock. + * @param stock The stock that was removed. + */ + void presentRemoveFromWatchlist(Stock stock); + + /** + * Updates favourite button UI. + * @param isFavourite is stock in watchlist. + */ + void updateFavouriteButton(Boolean isFavourite); +} diff --git a/src/main/java/use_case/watchlist/WatchListDataAccessInterface.java b/src/main/java/use_case/watchlist/WatchListDataAccessInterface.java new file mode 100644 index 000000000..262dff90b --- /dev/null +++ b/src/main/java/use_case/watchlist/WatchListDataAccessInterface.java @@ -0,0 +1,22 @@ +package use_case.watchlist; + +import java.util.ArrayList; + +public interface WatchListDataAccessInterface { + + /** + * Returns the user's watchlist. + * @return the user's watchlist + */ + ArrayList getWatchList(); + + /** + * Saves the watchlist. + */ + void saveWatchList(); + + /** + * Creates the watchlist. + */ + void createWatchList(); +} diff --git a/src/main/java/use_case/watchlist/WatchListInputData.java b/src/main/java/use_case/watchlist/WatchListInputData.java new file mode 100644 index 000000000..4e621f1dc --- /dev/null +++ b/src/main/java/use_case/watchlist/WatchListInputData.java @@ -0,0 +1,16 @@ +package use_case.watchlist; + +import java.util.ArrayList; + +/** + * The List of Stock code in watchlist. + */ + +public class WatchListInputData { + private ArrayList watchlist; + + public WatchListInputData() { + this.watchlist = new ArrayList(); + } + +} diff --git a/src/main/java/use_case/watchlist/WatchListModifyDataAccessInterface.java b/src/main/java/use_case/watchlist/WatchListModifyDataAccessInterface.java new file mode 100644 index 000000000..2baec22d7 --- /dev/null +++ b/src/main/java/use_case/watchlist/WatchListModifyDataAccessInterface.java @@ -0,0 +1,15 @@ +package use_case.watchlist; + +public interface WatchListModifyDataAccessInterface { + /** + * Add stock to watchlist. + * @param symbol the stock's stock to add + */ + void addToWatchList(String symbol); + + /** + * Remove stock from watchlist. + * @param symbol the stock's stock to add + */ + void removeFromWatchList(String symbol); +} diff --git a/src/main/java/use_case/watchlist/WatchlistInputBoundary.java b/src/main/java/use_case/watchlist/WatchlistInputBoundary.java new file mode 100644 index 000000000..a53600bb8 --- /dev/null +++ b/src/main/java/use_case/watchlist/WatchlistInputBoundary.java @@ -0,0 +1,30 @@ +package use_case.home_view; + +import entity.Stock; + +import java.util.ArrayList; + +public interface WatchlistInputBoundary { + + /** + * Executes the search use case. + * @param searchInputData the input data + */ + void search(SearchInputData searchInputData); + + + /** + * Executes the search use case. + * @param stocks the input data + */ + + void updateWatchlist(ArrayList stocks); + + + /** + * Executes the search use case. + * @param stockCode the input data + */ + + Stock getStockData(String stockCode); +} diff --git a/src/main/java/use_case/watchlist/WatchlistInteractor.java b/src/main/java/use_case/watchlist/WatchlistInteractor.java new file mode 100644 index 000000000..0647a9473 --- /dev/null +++ b/src/main/java/use_case/watchlist/WatchlistInteractor.java @@ -0,0 +1,57 @@ +package use_case.home_view; + +import data_access.DBUserDataAccessObject; +import entity.CommonStockFactory; +import entity.Stock; +import entity.StockFactory; +import interface_adapter.home_view.WatchlistPresenter; +import use_case.home_view.WatchlistOutputBoundary; +import use_case.home_view.WatchlistInputBoundary; +import use_case.watchlist.WatchListDataAccessInterface; + +import java.util.ArrayList; + +/** + * The Home View Interactor. + */ +public class WatchlistInteractor implements WatchlistInputBoundary { + + private final WatchListDataAccessInterface watchlistDataAccessInterface; + private final WatchlistOutputBoundary watchlistPresenter; + private final DBUserDataAccessObject dbUserDataAccessObject; + + public WatchlistInteractor(WatchListDataAccessInterface watchlistDataAccessInterface, + WatchlistOutputBoundary watchlistPresenter, DBUserDataAccessObject dbUserDataAccessObject) { + this.watchlistDataAccessInterface = watchlistDataAccessInterface; + this.watchlistPresenter = watchlistPresenter; + this.dbUserDataAccessObject = dbUserDataAccessObject; + } + + public void search(SearchInputData searchInputData) { +// final String stockSymbol = searchInputData.getStockSymbol(); +// try { +// final Stock stock = homeDataAccessInterface.getStock(stockSymbol); +// final SearchOutputData searchOutputData = new SearchOutputData(stock, false); +// homePresenter.prepareSuccessView(searchOutputData); +// } +// catch (Exception err) { +// err.printStackTrace(); +// homePresenter.prepareFailView("Failed to fetch stock data"); +// } + final StockFactory stockFactory = new CommonStockFactory(); + final Stock stock = stockFactory.create(searchInputData.getStockSymbol(), 128.2, 322.1, 100002322, 500.1, 100.23); + + final SearchOutputData searchOutputData = new SearchOutputData(stock, false); + watchlistPresenter.prepareSuccessView(searchOutputData); + } + + @Override + public Stock getStockData(String stockCode) { + return dbUserDataAccessObject.getStock(stockCode); + } + + @Override + public void updateWatchlist(ArrayList stocks) { + System.out.println("Watchlist updated with " + stocks.size() + " stocks."); + } +} \ No newline at end of file diff --git a/src/main/java/use_case/watchlist/WatchlistOutputBoundary.java b/src/main/java/use_case/watchlist/WatchlistOutputBoundary.java new file mode 100644 index 000000000..059d9f515 --- /dev/null +++ b/src/main/java/use_case/watchlist/WatchlistOutputBoundary.java @@ -0,0 +1,19 @@ +package use_case.home_view; + +import entity.Stock; + +import java.util.ArrayList; + +/** + * The output boundary for the Home Use Case. + */ +public interface WatchlistOutputBoundary { + + /** + * Prepares the success view for the Search Use Case. + * + * @param searchOutputData the output data + */ + void prepareSuccessView(SearchOutputData searchOutputData); + +} \ No newline at end of file diff --git a/src/main/java/use_case/watchlist/WatchlistOutputData.java b/src/main/java/use_case/watchlist/WatchlistOutputData.java new file mode 100644 index 000000000..efd1cfe50 --- /dev/null +++ b/src/main/java/use_case/watchlist/WatchlistOutputData.java @@ -0,0 +1,25 @@ +package use_case.home_view; + +import entity.Stock; + +import java.util.ArrayList; + +/** + * The output boundary for the Home Use Case. + */ +public interface WatchlistOutputData { + + /** + * Prepares the success view for the Search Use Case. + * + * @param searchOutputData the output data + */ + void prepareSuccessView(SearchOutputData searchOutputData); + + /** + * Prepares the failure view for the Search Use Case. + * + * @param errorMessage the explanation of the failure + */ + void prepareFailView(String errorMessage); +} \ No newline at end of file diff --git a/src/main/java/view/AbstractViewWithBackButton.java b/src/main/java/view/AbstractViewWithBackButton.java new file mode 100644 index 000000000..bb353d0db --- /dev/null +++ b/src/main/java/view/AbstractViewWithBackButton.java @@ -0,0 +1,37 @@ +package view; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +/** + * The Parent View for the all views that need back button. + */ + +abstract class AbstractViewWithBackButton extends JPanel { + private final JButton backButton = new JButton("←"); + + AbstractViewWithBackButton() { + // Configure back button + final JPanel topPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0)); + backButton.setFont(new Font("SansSerif", Font.PLAIN, 20)); + backButton.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); + backButton.setFocusPainted(false); + backButton.setContentAreaFilled(false); + topPanel.add(backButton, BorderLayout.WEST);; + + backButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + // Call the abstract method to be implemented by subclasses + backButtonAction(); + } + }); + + // Add panel(s) + this.add(topPanel); + } + + abstract void backButtonAction(); +} diff --git a/src/main/java/view/BuyView.java b/src/main/java/view/BuyView.java new file mode 100644 index 000000000..78237d5e7 --- /dev/null +++ b/src/main/java/view/BuyView.java @@ -0,0 +1,173 @@ +package view; + +import java.awt.BorderLayout; +import java.awt.Color; +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.JTextField; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; + +import interface_adapter.ViewManagerModel; +import interface_adapter.buy_view.BuyController; +import interface_adapter.buy_view.BuyState; +import interface_adapter.buy_view.BuyViewModel; + +/** + * The View for the user buy a stock. + */ +public class BuyView extends JPanel implements ActionListener, PropertyChangeListener { + + private final String viewName = "buy view"; + private final ViewManagerModel viewManagerModel; + private final BuyViewModel buyViewModel; + private final JTextField quantityInputField = new JTextField(15); + private final JTextField priceInputField = new JTextField(15); + private final JButton buy; + private final JButton cancel; + private BuyController buyController; + + public BuyView(final BuyViewModel viewModel, final ViewManagerModel viewManagerModel) { + this.viewManagerModel = viewManagerModel; + this.buyViewModel = viewModel; + this.buyViewModel.addPropertyChangeListener(this); + + setLayout(new BorderLayout()); + setBackground(Color.WHITE); + final JLabel title = new JLabel("Buy Screen"); + title.setAlignmentX(Component.CENTER_ALIGNMENT); + + final LabelTextPanel priceInfo = new LabelTextPanel(new JLabel("Price"), priceInputField); + final LabelTextPanel quantityInfo = new LabelTextPanel(new JLabel("Quantity"), quantityInputField); + + priceInputField.setEditable(true); + priceInputField.setText(buyViewModel.getState().getPrice()); + final JPanel buttons = new JPanel(); + buy = new JButton("Buy"); + buttons.add(buy); + cancel = new JButton("Cancel"); + buttons.add(cancel); + + setupBuyButton(); + setupCancelButton(); + setupQuantityInputFieldListener(); + setupPriceInputFieldListener(); + + this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); + this.add(title); + this.add(priceInfo); + this.add(quantityInfo); + this.add(buttons); + } + + private void setupBuyButton() { + buy.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { + try { + final double quantity = Double.parseDouble(quantityInputField.getText()); + if (quantity > 0) { + final BuyState buyState = buyViewModel.getState(); + buyController.execute( + buyState.getSymbol(), + Double.parseDouble(buyState.getPrice()), + quantity); + } + else { + JOptionPane.showMessageDialog(null, "Please enter a positive quantity."); + } + } + catch (final NumberFormatException event) { + JOptionPane.showMessageDialog(null, "Please enter a valid quantity."); + } + } + }); + } + + private void setupCancelButton() { + cancel.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent event) { + buyController.switchToStockView(); + } + }); + } + + private void setupQuantityInputFieldListener() { + quantityInputField.getDocument().addDocumentListener(new DocumentListener() { + private void documentListenerHelper() { + final BuyState currentState = buyViewModel.getState(); + currentState.setQuantity(quantityInputField.getText()); + buyViewModel.setState(currentState); + } + + @Override public void insertUpdate(DocumentEvent e) { + documentListenerHelper(); + } + + @Override public void removeUpdate(DocumentEvent e) { + documentListenerHelper(); + } + + @Override public void changedUpdate(DocumentEvent e) { + documentListenerHelper(); + } + }); + } + + private void setupPriceInputFieldListener() { + priceInputField.getDocument().addDocumentListener(new DocumentListener() { + private void documentListenerHelper() { + final BuyState currentState = buyViewModel.getState(); + currentState.setPrice(priceInputField.getText()); + buyViewModel.setState(currentState); + } + + @Override public void insertUpdate(DocumentEvent e) { + documentListenerHelper(); + } + + @Override public void removeUpdate(DocumentEvent e) { + documentListenerHelper(); + } + + @Override public void changedUpdate(DocumentEvent e) { + documentListenerHelper(); + } + }); + } + + /** + * React to a button click that results in evt. + * @param evt the ActionEvent to react to + */ + public void actionPerformed(final ActionEvent evt) { + System.out.println("Click " + evt.getActionCommand()); + } + + @Override + public void propertyChange(final PropertyChangeEvent evt) { + final BuyState state = (BuyState) evt.getNewValue(); + setFields(state); + } + + private void setFields(final BuyState state) { + priceInputField.setText(state.getPrice()); + quantityInputField.setText(state.getQuantity()); + } + + public String getViewName() { + return viewName; + } + + public void setBuyController(final BuyController buyController) { + this.buyController = buyController; + } +} diff --git a/src/main/java/view/HomeView.java b/src/main/java/view/HomeView.java new file mode 100644 index 000000000..a6b622367 --- /dev/null +++ b/src/main/java/view/HomeView.java @@ -0,0 +1,373 @@ +package view; + +import data_access.FileUserDataAccessObject; +import entity.SimulatedHoldingFactory; +import entity.Stock; +import interface_adapter.change_password.IsLoggedIn; +import interface_adapter.home_view.HomeController; +import interface_adapter.home_view.HomeState; +import interface_adapter.home_view.HomeViewModel; +import interface_adapter.login.LoginState; + +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.ArrayList; + +import javax.swing.*; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; + +/** + * The View for the Home Page. + */ +public class HomeView extends JPanel implements PropertyChangeListener { + + private static final String RIGHTARROW = "->"; + private static final String FONTFAMILY = "SansSerif"; + + private final String viewName = "home view"; + private final HomeViewModel homeViewModel; + private HomeController homeController; + + // Welcome component + private final JLabel welcomeLabel; + + // Search components + private final JTextField searchTextField = new JTextField(20); + private final JLabel searchErrorMessageLabel = new JLabel(); + + // Stock view components + private final JButton searchButton = new JButton(RIGHTARROW); + + // Portfolio components + private final JLabel portfolioLabel = new JLabel("Portfolio"); + private final JButton portfolioButton = new JButton(RIGHTARROW); + + // Watch list components + private final JLabel watchListLabel = new JLabel("Watchlist"); + private final JButton watchListButton = new JButton(RIGHTARROW); + private final JPanel watchListContentPanel = new JPanel(); + + // Login/Logout component + private JButton loginButton = new JButton(); + + // Add DAO + private final FileUserDataAccessObject dataAccessObject; + + public HomeView(HomeViewModel homeViewModel) { + + this.dataAccessObject = new FileUserDataAccessObject(); + + // Set up homeViewModel and listen any upcoming events + this.homeViewModel = homeViewModel; + this.homeViewModel.addPropertyChangeListener(this); + + // Config welcome label style + final JPanel welcomePanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + welcomePanel.setBackground(Color.WHITE); + welcomeLabel = new JLabel("Welcome"); + updateLabelStyle(welcomeLabel, 24); + welcomePanel.add(welcomeLabel); + + // Config search components style + final JPanel searchPanel = new JPanel(); + final JPanel searchTextFieldPanel = new JPanel(); + final JPanel searchErrorMessagePanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + searchTextField.setText("NVDA"); + searchErrorMessageLabel.setForeground(Color.RED); + searchErrorMessageLabel.setAlignmentX(Component.LEFT_ALIGNMENT); + searchTextFieldPanel.setBackground(Color.WHITE); + searchTextFieldPanel.add(searchTextField); + searchTextFieldPanel.add(searchButton); + searchErrorMessagePanel.setBackground(Color.WHITE); + searchErrorMessagePanel.add(searchErrorMessageLabel); + searchPanel.setLayout(new BoxLayout(searchPanel, BoxLayout.Y_AXIS)); + searchPanel.setBackground(Color.WHITE); + searchPanel.add(searchTextFieldPanel); + searchPanel.add(searchErrorMessagePanel); + // Add search stock button listener + searchButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { + if (evt.getSource().equals(searchButton)) { + final HomeState currentState = homeViewModel.getState(); + homeController.search(currentState.getSymbol()); + } + } + }); + // Add textField listener + searchTextField.getDocument().addDocumentListener(new DocumentListener() { + + private void documentListenerHelper() { + final HomeState currentState = homeViewModel.getState(); + currentState.setSymbol(new String(searchTextField.getText())); + homeViewModel.setState(currentState); + } + + @Override + public void insertUpdate(DocumentEvent e) { + documentListenerHelper(); + } + + @Override + public void removeUpdate(DocumentEvent e) { + documentListenerHelper(); + } + + @Override + public void changedUpdate(DocumentEvent e) { + documentListenerHelper(); + } + }); + + // Config portfolio components style + final JPanel portfolioPanel = new JPanel(); + final JPanel portfolioLeftPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + final JPanel portfolioRightPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); + updateLabelStyle(portfolioLabel, 20); + portfolioLeftPanel.setBackground(Color.WHITE); + portfolioLeftPanel.add(portfolioLabel); + portfolioRightPanel.setBackground(Color.WHITE); + portfolioRightPanel.add(portfolioButton); + portfolioPanel.setBackground(Color.WHITE); + portfolioPanel.setLayout(new BoxLayout(portfolioPanel, BoxLayout.X_AXIS)); + portfolioPanel.add(portfolioLeftPanel); + portfolioPanel.add(portfolioRightPanel); + portfolioButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { + if (dataAccessObject.isUserLoggedIn()) { + homeController.switchToPortfolio(); + } + else { + showLoginRequiredDialog(); + } + } + }); + + // Config watch list components style + final JPanel watchListPanel = new JPanel(); + final JPanel watchListLeftPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); + final JPanel watchListRightPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); + updateLabelStyle(watchListLabel, 20); + watchListPanel.setBackground(Color.WHITE); + watchListLeftPanel.setBackground(Color.WHITE); + watchListRightPanel.setBackground(Color.WHITE); + watchListPanel.setLayout(new BoxLayout(watchListPanel, BoxLayout.LINE_AXIS)); + watchListLeftPanel.add(watchListLabel); + watchListRightPanel.add(watchListButton); + watchListPanel.add(watchListLeftPanel); + watchListPanel.add(watchListRightPanel); + watchListPanel.setBorder(BorderFactory.createEmptyBorder(16, 0, 0, 0)); + watchListButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { + if (dataAccessObject.isUserLoggedIn()) { + homeController.switchToWatchList(); + } + else { + showLoginRequiredDialog(); + } + } + }); + // Watch list stock information + watchListContentPanel.setLayout(new BoxLayout(watchListContentPanel, BoxLayout.Y_AXIS)); + watchListContentPanel.setBackground(Color.WHITE); + + // Config login components style + loginButton.setAlignmentX(Component.CENTER_ALIGNMENT); + final JPanel loginPanel = new JPanel(); + loginButton.setPreferredSize(new Dimension(150, 32)); + loginPanel.add(loginButton); + changeLoginButtonText(); + loginButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { + if (dataAccessObject.isUserLoggedIn()) { + // If you are logged in, the logout dialog box is displayed + showLogoutDialog(); + } + else { + // If you are not logged in, switch to the login view + homeController.switchToLoginView(); + dataAccessObject.setUserLoggedIn(true); + changeLoginButtonText(); + } + } + }); + + // Config frame style + this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); + + // Add all components + this.add(welcomePanel); + this.add(searchPanel); + this.add(portfolioPanel); + this.add(watchListPanel); + this.add(watchListContentPanel); + this.add(loginPanel); + } + + @Override + public void propertyChange(PropertyChangeEvent evt) { + final HomeState state = (HomeState) evt.getNewValue(); + if (evt.getPropertyName().equals("getWatchList")) { + // Display watchlist data + final ArrayList watchList = state.getWatchList(); + + watchListContentPanel.removeAll(); + updateWatchListComponents(watchList); + watchListContentPanel.revalidate(); + watchListContentPanel.repaint(); + } + else if (evt.getPropertyName().equals("error")) { + searchErrorMessageLabel.setText(state.getErrorMessage()); + } + else if (evt.getPropertyName().equals("error")) { + changeLoginButtonText(); + } + } + + /** + * Updates the style of a JLabel with the specified font size. + * + * @param label The JLabel to update. + * @param fontSize The desired font size. + */ + public void updateLabelStyle(JLabel label, int fontSize) { + label.setFont(new Font(FONTFAMILY, Font.BOLD, fontSize)); + } + + /** + * Updates the watch list components with the given list of stocks. + * + * @param watchList The list of stocks to display. + */ + public void updateWatchListComponents(ArrayList watchList) { + for (Stock stock : watchList) { + final String symbol = stock.getSymbol(); + final String price = String.valueOf(stock.getClosePrice()); + final String dailyChange = String.valueOf(stock.getDailyChange()); + final String dailyPercentage = stock.getDailyPercentage() + "%"; + this.addWatchListItem(watchListContentPanel, symbol, price, dailyChange, dailyPercentage); + } + } + + private void addWatchListItem(JPanel contentPanel, String code, + String price, String dailyChange, String dailyPercentage) { + final JPanel stockPanel = new JPanel(new BorderLayout()); + stockPanel.setBackground(Color.WHITE); + + // Left part: Stock code and price + final JPanel leftPanel = new JPanel(); + leftPanel.setBorder(BorderFactory.createEmptyBorder(16, 64, 0, 0)); + leftPanel.setLayout(new BoxLayout(leftPanel, BoxLayout.Y_AXIS)); + leftPanel.setBackground(Color.WHITE); + + // Right part: up and down information + final JPanel rightPanel = new JPanel(); + rightPanel.setLayout(new BoxLayout(rightPanel, BoxLayout.Y_AXIS)); + rightPanel.setBackground(Color.WHITE); + rightPanel.setBorder(BorderFactory.createEmptyBorder(24, 0, 0, 5)); + + final JLabel stockCodeLabel = new JLabel("- " + code); + stockCodeLabel.setFont(new Font(FONTFAMILY, Font.BOLD, 16)); + final JLabel stockPriceLabel = new JLabel(price); + stockPriceLabel.setFont(new Font(FONTFAMILY, Font.PLAIN, 14)); + + final JLabel dailyChangeLabel = new JLabel(dailyChange + " (" + dailyPercentage + ")"); + dailyChangeLabel.setFont(new Font("SansSerif", Font.PLAIN, 16)); + + leftPanel.add(stockCodeLabel); + leftPanel.add(stockPriceLabel); + rightPanel.add(dailyChangeLabel); + + final JButton viewStockButton = new JButton(RIGHTARROW); + viewStockButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent evt) { + if (evt.getSource().equals(viewStockButton)) { + final HomeState currentState = homeViewModel.getState(); + currentState.setSymbol(code); + + homeController.search(currentState.getSymbol()); + } + } + }); + + // rightPanel.add(viewStockButton); + + // Add all to stockPanel + stockPanel.add(leftPanel, BorderLayout.WEST); + stockPanel.add(rightPanel, BorderLayout.EAST); + + // Add all information + contentPanel.add(stockPanel); + } + + public String getViewName() { + return viewName; + } + + /** + * Sets the HomeController for this view. + * + * @param homeController The HomeController to set. + */ + public void setHomeController(HomeController homeController) { + this.homeController = homeController; + + // Preload watchList data + homeController.getWatchList(); + } + + /** + * Displays a logout confirmation dialog and logs out if confirmed. + */ + public void showLogoutDialog() { + final int response = JOptionPane.showConfirmDialog(this, + "Do you want to logout?", "Confirm", JOptionPane.YES_NO_OPTION); + + if (response == JOptionPane.YES_OPTION) { + dataAccessObject.setUserLoggedIn(false); + dataAccessObject.saveUserLoginStatus(); + changeLoginButtonText(); + homeController.deleteLocalData(); + } + } + + /** + * Updates the text of the login button based on the login status. + */ + public void changeLoginButtonText() { + if (dataAccessObject.isUserLoggedIn()) { + loginButton.setText("Log Out"); + } + else { + loginButton.setText("Log In"); + } + homeViewModel.firePropertyChanged(); + } + + /** + * Displays a dialog indicating that login is required and switches to the login view. + */ + public void showLoginRequiredDialog() { + final Object[] options = {"Go to Login"}; + final int response = JOptionPane.showOptionDialog( + this, + "You need to login first", + "Login Required", + JOptionPane.YES_NO_OPTION, + JOptionPane.WARNING_MESSAGE, + null, + options, + options[0] + ); + + if (response == JOptionPane.YES_OPTION) { + dataAccessObject.setUserLoggedIn(true); + dataAccessObject.saveUserLoginStatus(); + changeLoginButtonText(); + homeController.switchToLoginView(); + } + } +} diff --git a/src/main/java/view/HomeViewComponents/ScrollablePanel.java b/src/main/java/view/HomeViewComponents/ScrollablePanel.java new file mode 100644 index 000000000..fab093e6e --- /dev/null +++ b/src/main/java/view/HomeViewComponents/ScrollablePanel.java @@ -0,0 +1,20 @@ +package view.HomeViewComponents; + +import javax.swing.*; +import java.awt.*; + +public class ScrollablePanel extends JPanel { + public ScrollablePanel() { + super(); + setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); + setBackground(Color.WHITE); + } + + public JScrollPane wrapInScrollPane() { + JScrollPane scrollPane = new JScrollPane(this); + scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED); + scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); + scrollPane.getVerticalScrollBar().setUnitIncrement(16); + return scrollPane; + } +} diff --git a/src/main/java/view/HomeViewComponents/WatchListLabelButtonPanel.java b/src/main/java/view/HomeViewComponents/WatchListLabelButtonPanel.java new file mode 100644 index 000000000..d0cd3835c --- /dev/null +++ b/src/main/java/view/HomeViewComponents/WatchListLabelButtonPanel.java @@ -0,0 +1,15 @@ +package view.HomeViewComponents; + +import javax.swing.*; + +/** + * A panel containing a label and a button. + * - AAPL -> + */ +public class WatchListLabelButtonPanel extends JPanel { + WatchListLabelButtonPanel(JLabel label, JButton button) { + this.add(label); + this.add(button); + this.setLayout(new BoxLayout(this, BoxLayout.X_AXIS)); + } +} diff --git a/src/main/java/view/LoggedInView.java b/src/main/java/view/LoggedInView.java index 76d40c0f1..f9662ea41 100644 --- a/src/main/java/view/LoggedInView.java +++ b/src/main/java/view/LoggedInView.java @@ -100,9 +100,8 @@ public void changedUpdate(DocumentEvent e) { // This creates an anonymous subclass of ActionListener and instantiates it. evt -> { if (evt.getSource().equals(logOut)) { - // TODO: execute the logout use case through the Controller - // 1. get the state out of the loggedInViewModel. It contains the username. - // 2. Execute the logout Controller. + final LoggedInState currentState = loggedInViewModel.getState(); + logoutController.execute(currentState.getUsername()); } } ); @@ -138,6 +137,6 @@ public void setChangePasswordController(ChangePasswordController changePasswordC } public void setLogoutController(LogoutController logoutController) { - // TODO: save the logout controller in the instance variable. + this.logoutController = logoutController; } } diff --git a/src/main/java/view/LoginView.java b/src/main/java/view/LoginView.java index 96d4f3845..89a502a54 100644 --- a/src/main/java/view/LoginView.java +++ b/src/main/java/view/LoginView.java @@ -15,6 +15,7 @@ import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; +import interface_adapter.ViewManagerModel; import interface_adapter.login.LoginController; import interface_adapter.login.LoginState; import interface_adapter.login.LoginViewModel; @@ -33,12 +34,13 @@ public class LoginView extends JPanel implements ActionListener, PropertyChangeL private final JPasswordField passwordInputField = new JPasswordField(15); private final JLabel passwordErrorField = new JLabel(); + private final JButton signUp; private final JButton logIn; - private final JButton cancel; + private final ViewManagerModel viewManagerModel; private LoginController loginController; - public LoginView(LoginViewModel loginViewModel) { - + public LoginView(LoginViewModel loginViewModel, ViewManagerModel viewManagerModel) { + this.viewManagerModel = viewManagerModel; this.loginViewModel = loginViewModel; this.loginViewModel.addPropertyChangeListener(this); @@ -51,10 +53,19 @@ public LoginView(LoginViewModel loginViewModel) { new JLabel("Password"), passwordInputField); final JPanel buttons = new JPanel(); - logIn = new JButton("log in"); + signUp = new JButton("Sign Up"); + buttons.add(signUp); + + logIn = new JButton("Log In"); buttons.add(logIn); - cancel = new JButton("cancel"); - buttons.add(cancel); + + signUp.addActionListener( + new ActionListener() { + public void actionPerformed(ActionEvent evt) { + loginController.switchToSignUpView(); + } + } + ); logIn.addActionListener( new ActionListener() { @@ -71,8 +82,6 @@ public void actionPerformed(ActionEvent evt) { } ); - cancel.addActionListener(this); - usernameInputField.getDocument().addDocumentListener(new DocumentListener() { private void documentListenerHelper() { diff --git a/src/main/java/view/PortfolioView.java b/src/main/java/view/PortfolioView.java new file mode 100644 index 000000000..8db0b6544 --- /dev/null +++ b/src/main/java/view/PortfolioView.java @@ -0,0 +1,233 @@ +package view; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Font; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.ArrayList; + +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.border.Border; + +import entity.SimulatedHolding; +import entity.Stock; +import interface_adapter.ViewManagerModel; +import interface_adapter.portfolio.PortfolioController; +import interface_adapter.portfolio.PortfolioState; +import interface_adapter.portfolio.PortfolioViewModel; + +/** + * The view for displaying portfolio details. + */ +public class PortfolioView extends JPanel implements PropertyChangeListener { + + private static final int PADDING_SMALL = 10; + private static final int PADDING_LARGE = 20; + private static final int FONT_SIZE_TITLE = 24; + private static final int FONT_SIZE_SUBTITLE = 18; + private static final int FONT_SIZE_LABEL = 16; + private static final int SCROLL_WIDTH = 400; + private static final int SCROLL_HEIGHT = 300; + + private final PortfolioViewModel portfolioViewModel; + private final ViewManagerModel viewManagerModel; + private PortfolioController portfolioController; + private JPanel contentPanel; + private JLabel currentAmount; + private JLabel todayChangeLabel; + private JLabel allTimeChangeLabel; + + public PortfolioView(PortfolioViewModel portfolioViewModel, ViewManagerModel viewManagerModel) { + this.portfolioViewModel = portfolioViewModel; + this.portfolioViewModel.addPropertyChangeListener(this); + this.viewManagerModel = viewManagerModel; + setLayout(new BorderLayout()); + setBackground(Color.WHITE); + + final JPanel statisticsPanel = new JPanel(); + statisticsPanel.setLayout(new BoxLayout(statisticsPanel, BoxLayout.Y_AXIS)); + statisticsPanel.setBorder(BorderFactory.createEmptyBorder(PADDING_SMALL, PADDING_SMALL, PADDING_LARGE, PADDING_SMALL)); + statisticsPanel.setBackground(Color.WHITE); + + final JPanel topRowPanel = new JPanel(); + topRowPanel.setLayout(new BoxLayout(topRowPanel, BoxLayout.Y_AXIS)); + topRowPanel.setBackground(Color.WHITE); + + final JButton backButton = new JButton("\u2190"); + backButton.setFont(new Font("SansSerif", Font.PLAIN, 20)); + backButton.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); + backButton.setFocusPainted(false); + backButton.setContentAreaFilled(false); + + backButton.addActionListener(e -> { + viewManagerModel.setState("home view"); + viewManagerModel.firePropertyChanged(); + }); + + // Current amount + currentAmount = new JLabel("$0"); + currentAmount.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0)); + currentAmount.setFont(new Font("SansSerif", Font.PLAIN, 20)); + + // Add return button and current amount to the top row + topRowPanel.add(backButton); + topRowPanel.add(Box.createRigidArea(new Dimension(0, 2))); + topRowPanel.add(currentAmount); + + // Create a sub-panel with BorderLayout + final JPanel mainPanel = new JPanel(new BorderLayout()); + mainPanel.setBackground(Color.WHITE); + + // Add top row panel to the left side (WEST) + mainPanel.add(topRowPanel, BorderLayout.WEST); + + // Dummy value on the right side (EAST) + final JLabel dummyValue = new JLabel(""); + dummyValue.setFont(new Font("SansSerif", Font.PLAIN, 16)); + mainPanel.add(dummyValue, BorderLayout.EAST); + + // Add topRowPanel to statisticsPanel + statisticsPanel.add(mainPanel); + + // Portfolio amount changes + todayChangeLabel = new JLabel("Today: $0 (0%)"); + todayChangeLabel.setFont(new Font("SansSerif", Font.PLAIN, 16)); + todayChangeLabel.setBorder(BorderFactory.createEmptyBorder(10, 0, 10, 0)); + statisticsPanel.add(todayChangeLabel); + + allTimeChangeLabel = new JLabel("All Time: $0 (0%)"); + allTimeChangeLabel.setFont(new Font("SansSerif", Font.PLAIN, 16)); + allTimeChangeLabel.setBorder(BorderFactory.createEmptyBorder(10, 0, 10, 0)); + statisticsPanel.add(allTimeChangeLabel); + + add(statisticsPanel, BorderLayout.NORTH); + + // Create the content panel + contentPanel = new JPanel(); + contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.Y_AXIS)); + contentPanel.setBackground(Color.WHITE); + + // Wrap the content panel in a JScrollPane + final JScrollPane scrollPane = new JScrollPane(contentPanel); + scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED); + scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); + + // Set the preferred size of the scroll pane + scrollPane.setPreferredSize(new Dimension(400, 300)); + add(scrollPane, BorderLayout.CENTER); + } + + private void addStockItem(String code, double closePrice, double purchasePrice, double amount, double openPrice) { + final JPanel stockPanel = new JPanel(new BorderLayout()); + stockPanel.setBackground(Color.WHITE); + // Create a matte border and an empty border + final Border matteBorder = BorderFactory.createMatteBorder(0, 0, 1, 0, Color.LIGHT_GRAY); + final Border emptyBorder = BorderFactory.createEmptyBorder(10, 10, 10, 10); + + // Combine them using CompoundBorder + final Border combinedBorder = BorderFactory.createCompoundBorder(emptyBorder, matteBorder); + stockPanel.setBorder(combinedBorder); + + // Calculate values + double currentValue = closePrice * amount; + double dailyChange = (closePrice - openPrice) * amount; + double allTimeChange = (closePrice - purchasePrice) * amount; + double dailyPercentage = openPrice != 0 ? ((closePrice - openPrice) / openPrice) * 100 : 100; + double allTimePercentage = purchasePrice != 0 ? ((closePrice - purchasePrice) / purchasePrice) * 100 : 100; + + // Left part: Stock code and price + final JPanel leftPanel = new JPanel(); + leftPanel.setLayout(new BoxLayout(leftPanel, BoxLayout.Y_AXIS)); + leftPanel.setBackground(Color.WHITE); + + final JLabel stockCodeLabel = new JLabel(code); + stockCodeLabel.setFont(new Font("SansSerif", Font.BOLD, 24)); + stockCodeLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0)); + + final JLabel stockPriceLabel = new JLabel("$" + String.format("%.2f", currentValue)); + stockPriceLabel.setFont(new Font("SansSerif", Font.PLAIN, 18)); + stockPriceLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0)); + + final JLabel stockAmountLabel = new JLabel("Amount: " + amount); + stockAmountLabel.setFont(new Font("SansSerif", Font.PLAIN, 16)); + + leftPanel.add(stockCodeLabel); + leftPanel.add(stockPriceLabel); + leftPanel.add(stockAmountLabel); + + // Right part: daily change and total change + final JPanel rightPanel = new JPanel(); + rightPanel.setLayout(new BoxLayout(rightPanel, BoxLayout.Y_AXIS)); + rightPanel.setBackground(Color.WHITE); + rightPanel.setBorder(BorderFactory.createEmptyBorder(10, 0, 0, 10)); + + final JLabel dailyChangeLabel = new JLabel(String.format("%+.2f (%.2f%%)", dailyChange, dailyPercentage)); + dailyChangeLabel.setFont(new Font("SansSerif", Font.PLAIN, 16)); + dailyChangeLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0)); + + final JLabel totalChangeLabel = new JLabel(String.format("%+.2f (%.2f%%)", allTimeChange, allTimePercentage)); + totalChangeLabel.setFont(new Font("SansSerif", Font.PLAIN, 16)); + totalChangeLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0)); + + rightPanel.add(dailyChangeLabel); + rightPanel.add(totalChangeLabel); + + // Add all to stockPanel + stockPanel.add(leftPanel, BorderLayout.WEST); + stockPanel.add(rightPanel, BorderLayout.EAST); + + // Add all information to the content panel + contentPanel.add(stockPanel); + } + + public String getViewName() { + return "PortfolioView"; + } + + public void setController(PortfolioController controller) { + this.portfolioController = controller; + controller.getPortfolioList(); + } + + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (evt.getPropertyName().equals("getPortfolioList")) { + final PortfolioState portfolioState = portfolioViewModel.getState(); + final ArrayList portfolioList = portfolioState.getSimulatedHoldings(); + final ArrayList stockList = portfolioState.getStocks(); + contentPanel.removeAll(); + + double totalValue = 0; + double totalDailyChange = 0; + double totalAllTimeChange = 0; + + int minSize = Math.min(portfolioList.size(), stockList.size()); + for (int i = 0; i < minSize; i++) { + SimulatedHolding holding = portfolioList.get(i); + Stock stock = stockList.get(i); + totalValue += stock.getClosePrice() * holding.getAmount(); + totalDailyChange += (stock.getClosePrice() - stock.getOpenPrice()) * holding.getAmount(); + totalAllTimeChange += (stock.getClosePrice() - holding.getPurchasePrice()) * holding.getAmount(); + addStockItem(holding.getSymbol(), stock.getClosePrice(), holding.getPurchasePrice(), holding.getAmount(), stock.getOpenPrice()); + } + + currentAmount.setText("$" + String.format("%.2f", totalValue)); + todayChangeLabel.setText(String.format("Today: %+.2f (%.2f%%)", totalDailyChange, totalValue != 0 ? (totalDailyChange / totalValue) * 100 : 0)); + allTimeChangeLabel.setText(String.format("All Time: %+.2f (%.2f%%)", totalAllTimeChange, totalValue != 0 ? (totalAllTimeChange / totalValue) * 100 : 0)); + + contentPanel.revalidate(); + contentPanel.repaint(); + } + else if (evt.getPropertyName().equals("addNewStock")) { + portfolioController.getPortfolioList(); + } + } +} diff --git a/src/main/java/view/SignupView.java b/src/main/java/view/SignupView.java index f98570d62..9641a3c08 100644 --- a/src/main/java/view/SignupView.java +++ b/src/main/java/view/SignupView.java @@ -16,6 +16,7 @@ import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; +import interface_adapter.ViewManagerModel; import interface_adapter.signup.SignupController; import interface_adapter.signup.SignupState; import interface_adapter.signup.SignupViewModel; @@ -30,13 +31,15 @@ public class SignupView extends JPanel implements ActionListener, PropertyChange private final JTextField usernameInputField = new JTextField(15); private final JPasswordField passwordInputField = new JPasswordField(15); private final JPasswordField repeatPasswordInputField = new JPasswordField(15); + private final ViewManagerModel viewManagerModel; private SignupController signupController; private final JButton signUp; private final JButton cancel; private final JButton toLogin; - public SignupView(SignupViewModel signupViewModel) { + public SignupView(SignupViewModel signupViewModel, ViewManagerModel viewManagerModel) { + this.viewManagerModel = viewManagerModel; this.signupViewModel = signupViewModel; signupViewModel.addPropertyChangeListener(this); @@ -83,7 +86,13 @@ public void actionPerformed(ActionEvent evt) { } ); - cancel.addActionListener(this); + cancel.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + viewManagerModel.setState("home view"); + viewManagerModel.firePropertyChanged(); + } + }); addUsernameListener(); addPasswordListener(); diff --git a/src/main/java/view/StockView.java b/src/main/java/view/StockView.java new file mode 100644 index 000000000..0d3015961 --- /dev/null +++ b/src/main/java/view/StockView.java @@ -0,0 +1,233 @@ +package view; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Font; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JPanel; + +import data_access.FileUserDataAccessObject; +import entity.Stock; +import interface_adapter.ViewManagerModel; +import interface_adapter.stock_view.StockController; +import interface_adapter.stock_view.StockViewModel; +import interface_adapter.stock_view.StockViewState; + +/** + * The View for displaying detailed information about a single stock. + */ +public class StockView extends AbstractViewWithBackButton implements PropertyChangeListener { + private static final String VIEW_NAME = "StockView"; + private static final String FONT_FAMILY = "SansSerif"; + private static final int FONT_SIZE_TITLE = 24; + private static final int FONT_SIZE_SUBTITLE = 18; + private static final int FONT_SIZE_LABEL = 16; + private static final int PADDING_LARGE = 20; + private static final int PADDING_SMALL = 10; + private static final int MILLION = 1_000_000; + private static final int THOUSAND = 1_000; + + private final ViewManagerModel viewManagerModel; + private final StockViewModel stockViewModel; + private StockController stockController; + + private JLabel stockSymbolLabel; + private JLabel stockPriceLabel; + private JLabel openPriceLabel; + private JLabel closePriceLabel; + private JLabel lowPriceLabel; + private JLabel highPriceLabel; + private JLabel volumeLabel; + private JButton favoriteButton; + private JButton buyButton; + private String previousView; + + private final FileUserDataAccessObject fileUserDataAccessObject = new FileUserDataAccessObject(); + + public StockView(StockViewModel stockViewModel, ViewManagerModel viewManagerModel) { + fileUserDataAccessObject.isUserLoggedIn(); + fileUserDataAccessObject.saveUserLoginStatus(); + + this.viewManagerModel = viewManagerModel; + this.stockViewModel = stockViewModel; + + this.stockViewModel.addPropertyChangeListener(this); + + setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); + setBackground(Color.WHITE); + + final JPanel contentPanel = new JPanel(); + contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.Y_AXIS)); + contentPanel.setBackground(Color.WHITE); + + initializeComponents(contentPanel); + + add(Box.createVerticalGlue()); + add(contentPanel); + add(Box.createRigidArea(new Dimension(0, PADDING_LARGE))); + add(createActionButtonsPanel()); + add(Box.createVerticalGlue()); + } + + public void setStockController(StockController stockController) { + this.stockController = stockController; + } + + private void initializeComponents(JPanel contentPanel) { + stockSymbolLabel = createLabel(FONT_SIZE_TITLE, Font.BOLD); + stockPriceLabel = createLabel(FONT_SIZE_SUBTITLE, Font.PLAIN); + + final JPanel stockInfoPanel = new JPanel(); + stockInfoPanel.setLayout(new BoxLayout(stockInfoPanel, BoxLayout.Y_AXIS)); + stockInfoPanel.setBackground(Color.WHITE); + stockInfoPanel.add(stockSymbolLabel); + stockInfoPanel.add(stockPriceLabel); + + openPriceLabel = createLabel(FONT_SIZE_LABEL, Font.PLAIN); + closePriceLabel = createLabel(FONT_SIZE_LABEL, Font.PLAIN); + lowPriceLabel = createLabel(FONT_SIZE_LABEL, Font.PLAIN); + highPriceLabel = createLabel(FONT_SIZE_LABEL, Font.PLAIN); + volumeLabel = createLabel(FONT_SIZE_LABEL, Font.PLAIN); + + contentPanel.add(stockInfoPanel); + contentPanel.add(Box.createRigidArea(new Dimension(0, PADDING_SMALL))); + contentPanel.add(openPriceLabel); + contentPanel.add(closePriceLabel); + contentPanel.add(lowPriceLabel); + contentPanel.add(highPriceLabel); + contentPanel.add(volumeLabel); + + contentPanel.setAlignmentX(Component.LEFT_ALIGNMENT); + stockInfoPanel.setAlignmentX(Component.LEFT_ALIGNMENT); + } + + private JPanel createActionButtonsPanel() { + final JPanel actionPanel = new JPanel(); + actionPanel.setLayout(new BoxLayout(actionPanel, BoxLayout.X_AXIS)); + actionPanel.setBackground(Color.WHITE); + actionPanel.setAlignmentX(Component.CENTER_ALIGNMENT); + + buyButton = new JButton("BUY"); + buyButton.setFont(new Font(FONT_FAMILY, Font.BOLD, FONT_SIZE_SUBTITLE)); + buyButton.setFocusPainted(false); + buyButton.addActionListener(event -> { + final Stock currentStock = stockViewModel.getState().getStock(); + if (currentStock != null && stockController != null) { + stockController.buyStock(currentStock); + } + }); + + // Star button for favorite + favoriteButton = new JButton("\u2606"); + favoriteButton.setFont(new Font(FONT_FAMILY, Font.BOLD, FONT_SIZE_SUBTITLE)); + favoriteButton.setFocusPainted(false); + favoriteButton.addActionListener(event -> { + final Stock currentStock = stockViewModel.getState().getStock(); + if (currentStock != null && stockController != null) { + final boolean isFavorite = "\u2605".equals(favoriteButton.getText()); + stockController.toggleWatchlist(currentStock, true); + } + }); + + actionPanel.add(Box.createHorizontalGlue()); + actionPanel.add(buyButton); + actionPanel.add(Box.createRigidArea(new Dimension(PADDING_LARGE, 0))); + actionPanel.add(favoriteButton); + actionPanel.add(Box.createHorizontalGlue()); + + return actionPanel; + } + + /** + * The update data part . + * @param stock as the stock that should be shown + */ + public void updateStockData(Stock stock) { + stockSymbolLabel.setText(stock.getSymbol()); + stockPriceLabel.setText("Current Price: $" + stock.getClosePrice()); + openPriceLabel.setText("Open Price: $" + stock.getOpenPrice()); + closePriceLabel.setText("Close Price: $" + stock.getClosePrice()); + lowPriceLabel.setText("Low: $" + stock.getLow()); + highPriceLabel.setText("High: $" + stock.getHigh()); + + final double volume = stock.getVolume(); + final String volumeText; + + if (volume >= MILLION) { + volumeText = (volume / MILLION) + " M"; + } + else if (volume >= THOUSAND) { + volumeText = (volume / THOUSAND) + " K"; + } + else { + volumeText = String.valueOf(volume); + } + + volumeLabel.setText("Volume: " + volumeText); + } + + private void updateFavoriteButtonVisibility(String stockSymbol) { + favoriteButton.setVisible(!"AAPL".equals(stockSymbol) && !"COST".equals(stockSymbol) + && !"NVDA".equals(stockSymbol)); + } + + private void setButtonVisible(boolean visible) { + favoriteButton.setVisible(visible); + buyButton.setVisible(visible); + } + + private JLabel createLabel(int fontSize, int fontStyle) { + final JLabel label = new JLabel(""); + label.setFont(new Font(FONT_FAMILY, fontStyle, fontSize)); + label.setAlignmentX(Component.LEFT_ALIGNMENT); + return label; + } + + public String getViewName() { + return VIEW_NAME; + } + + @Override + void backButtonAction() { + viewManagerModel.popView(); + viewManagerModel.firePropertyChanged(); + } + + @Override + public void propertyChange(PropertyChangeEvent evt) { + final StockViewState stockViewState = stockViewModel.getState(); + if ("switchToStockView".equals(evt.getPropertyName())) { + final Stock updatedStock = stockViewState.getStock(); + if (updatedStock != null) { + updateStockData(updatedStock); + } + + // Update favourite button UI only + stockController.toggleWatchlist(updatedStock, false); + } + else if ("updateFavouriteButton".equals(evt.getPropertyName())) { + // Toggle button text between filled and empty star + if (stockViewState.getIsFavorite()) { + favoriteButton.setText("\u2605"); + } + else { + favoriteButton.setText("\u2606"); + } + } + + // Update login state + setButtonVisible(fileUserDataAccessObject.isUserLoggedIn()); + } + + public void setPreviousView(String viewName) { + this.previousView = viewName; + } +} + diff --git a/src/main/java/view/WatchListView.java b/src/main/java/view/WatchListView.java new file mode 100644 index 000000000..8a1d7ad79 --- /dev/null +++ b/src/main/java/view/WatchListView.java @@ -0,0 +1,192 @@ +package view; + +import entity.Stock; +import view.HomeViewComponents.ScrollablePanel; + +import data_access.DBUserDataAccessObject; +import interface_adapter.ViewManagerModel; +import interface_adapter.home_view.WatchlistController; +import interface_adapter.stock_view.WatchListViewState; +import interface_adapter.watchlist_view.WatchListViewModel; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionListener; +import java.awt.event.ActionEvent; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.ArrayList; + + +public class WatchListView extends JPanel implements PropertyChangeListener { + + private final ViewManagerModel viewManagerModel; + private final WatchListViewModel watchListViewModel; + private final ScrollablePanel contentPanel = new ScrollablePanel(); + private WatchlistController watchlistController; + + public WatchListView(WatchListViewModel viewModel, ViewManagerModel viewManagerModel) { + this.viewManagerModel = viewManagerModel; + setLayout(new BorderLayout()); + setBackground(Color.WHITE); + this.watchListViewModel = viewModel; + this.watchListViewModel.addPropertyChangeListener(this); + + // return button + final JPanel topPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0)); + topPanel.setBackground(Color.WHITE); + + // create return button + final JButton backButton = new JButton("←"); + backButton.setFont(new Font("SansSerif", Font.PLAIN, 20)); + backButton.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); + backButton.setFocusPainted(false); + backButton.setContentAreaFilled(false); + topPanel.add(backButton, BorderLayout.WEST); + + // Listener for return button to home page + backButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + viewManagerModel.popView(); + } + }); + + add(topPanel, BorderLayout.NORTH); + + // stock information + contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.Y_AXIS)); + contentPanel.setBackground(Color.WHITE); + + JScrollPane scrollPane = contentPanel.wrapInScrollPane(); + scrollPane.setBorder(BorderFactory.createEmptyBorder()); + add(scrollPane, BorderLayout.CENTER); + + } + + private void addStockItem(JPanel contentPanel, String code, String price, String dailyChange, String dailyPercentage, String volume) { + final JPanel stockPanel = new JPanel(new BorderLayout()); + stockPanel.setBackground(Color.WHITE); + + final boolean isFirstStock = contentPanel.getComponentCount() == 0; + if (isFirstStock) { + stockPanel.setBorder(BorderFactory.createMatteBorder(1, 0, 1, 0, Color.LIGHT_GRAY)); + } + else { + stockPanel.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, Color.LIGHT_GRAY)); + } + + stockPanel.setMaximumSize(new Dimension(Integer.MAX_VALUE, 80)); + stockPanel.setMinimumSize(new Dimension(0, 80)); + + stockPanel.addMouseListener(new java.awt.event.MouseAdapter() { + @Override + public void mouseClicked(java.awt.event.MouseEvent evt) { + goToStockInformation(code); + } + + @Override + public void mouseEntered(java.awt.event.MouseEvent evt) { + stockPanel.setBackground(new Color(240, 240, 240)); + } + + @Override + public void mouseExited(java.awt.event.MouseEvent evt) { + stockPanel.setBackground(Color.WHITE); + } + }); + + // Left part: Stock code and price + final JPanel leftPanel = new JPanel(); + leftPanel.setOpaque(false); + leftPanel.setLayout(new BoxLayout(leftPanel, BoxLayout.Y_AXIS)); + leftPanel.setBackground(Color.WHITE); + + final JLabel stockCodeLabel = new JLabel(code); + stockCodeLabel.setFont(new Font("SansSerif", Font.BOLD, 24)); + + final JLabel stockPriceLabel = new JLabel(price); + stockPriceLabel.setFont(new Font("SansSerif", Font.PLAIN, 18)); + + leftPanel.add(Box.createVerticalGlue()); + leftPanel.add(stockCodeLabel); + leftPanel.add(stockPriceLabel); + leftPanel.add(Box.createVerticalGlue()); + + // Right part: up and down information + final JPanel rightPanel = new JPanel(); + rightPanel.setOpaque(false); + rightPanel.setLayout(new BoxLayout(rightPanel, BoxLayout.Y_AXIS)); + rightPanel.setBackground(Color.WHITE); + + rightPanel.setAlignmentX(Component.RIGHT_ALIGNMENT); + + final JLabel dailyChangeLabel = new JLabel(dailyChange + " (" + dailyPercentage + ")"); + dailyChangeLabel.setFont(new Font("SansSerif", Font.PLAIN, 16)); + dailyChangeLabel.setAlignmentX(Component.RIGHT_ALIGNMENT); + final JLabel volumeLabel = new JLabel(volume); + volumeLabel.setFont(new Font("SansSerif", Font.PLAIN, 16)); + volumeLabel.setAlignmentX(Component.RIGHT_ALIGNMENT); + + rightPanel.add(Box.createVerticalGlue()); + rightPanel.add(dailyChangeLabel); + rightPanel.add(volumeLabel); + rightPanel.add(Box.createVerticalGlue()); + + // Add all to stockPanel + stockPanel.add(leftPanel, BorderLayout.WEST); + stockPanel.add(rightPanel, BorderLayout.EAST); + + // Add all information + contentPanel.add(stockPanel); + } + + @Override + public void propertyChange(PropertyChangeEvent evt) { + if ("watchList".equals(evt.getPropertyName())) { + final WatchListViewState watchListViewState = (WatchListViewState) evt.getNewValue(); + if (watchListViewState.getWatchlist() != null) { + updateWatchList(watchListViewState.getWatchlist()); + } + } + } + + private void updateWatchList(ArrayList updatedWatchList) { + contentPanel.removeAll(); + createWatchListview(updatedWatchList); + contentPanel.revalidate(); + contentPanel.repaint(); + } + + private void goToStockInformation(String code) { + if (watchlistController == null) { + throw new IllegalStateException("WatchlistController is not initialized."); + } + final WatchListViewState currentState = watchListViewModel.getState(); + currentState.setSymbol(code); + watchlistController.search(currentState.getSymbol()); + + viewManagerModel.pushView("StockView"); + } + + private void createWatchListview(ArrayList WatchList) { + for (Stock stock : WatchList) { + final String code = stock.getSymbol(); + final String price = "$" + stock.getClosePrice(); + final String dailyChange = String.valueOf(stock.getDailyChange()); + final String dailyPercentage = stock.getDailyPercentage() + "%"; + final String volume = String.valueOf(stock.getVolume()); + + addStockItem(contentPanel, code, price, dailyChange, dailyPercentage, volume); + } + } + + public String getViewName() { + return "WatchListView"; + } + + public void setwatchlistController(WatchlistController watchlistController) { + this.watchlistController = watchlistController; + } +} + diff --git a/src/test/java/use_case/buy/BuyInteractorTest.java b/src/test/java/use_case/buy/BuyInteractorTest.java new file mode 100644 index 000000000..b45be735a --- /dev/null +++ b/src/test/java/use_case/buy/BuyInteractorTest.java @@ -0,0 +1,151 @@ +package use_case.buy; + +import entity.CommonSimulatedHoldingFactory; +import entity.SimulatedHolding; +import entity.SimulatedHoldingFactory; +import org.junit.jupiter.api.Test; +import use_case.portfolio.PortfolioDataAccessInterface; + +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.*; + +class BuyInteractorTest { + + @Test + void testBuyInputData() { + // Test the BuyInputData class + BuyInputData inputData = new BuyInputData("AAPL", 150.0, 10.0); + assertEquals("AAPL", inputData.getSymbol()); + assertEquals(150.0, inputData.getPrice()); + assertEquals(10.0, inputData.getQuantity()); + } + + @Test + void testBuyOutputData() { + // Test the BuyOutputData class + SimulatedHoldingFactory holdingFactory = new CommonSimulatedHoldingFactory(); + SimulatedHolding simulatedHolding = holdingFactory.create("AAPL", 150.0, 10.0); + BuyOutputData outputData = new BuyOutputData(simulatedHolding); + assertEquals(simulatedHolding, outputData.getSimulatedHolding()); + } + + @Test + void testExecute() { + // Prepare input data + BuyInputData inputData = new BuyInputData("AAPL", 150.0, 10.0); + + // Use in-memory portfolio data access object + InMemoryPortfolioDataAccess portfolioDataAccessObject = new InMemoryPortfolioDataAccess(); + + // Create a test presenter + TestBuyOutputBoundary buyPresenter = new TestBuyOutputBoundary(); + + // Create Interactor instance + BuyInteractor buyInteractor = new BuyInteractor(buyPresenter, portfolioDataAccessObject); + + // Execute test + buyInteractor.execute(inputData); + + // Verify that the SimulatedHolding was added to the portfolio + assertEquals(1, portfolioDataAccessObject.getPortfolioList().size()); + SimulatedHolding holding = portfolioDataAccessObject.getPortfolioList().get(0); + assertEquals("AAPL", holding.getSymbol()); + assertEquals(150.0, holding.getPurchasePrice()); + assertEquals(10.0, holding.getPurchaseAmount()); + + // Verify that the presenter was called correctly + assertTrue(buyPresenter.successViewCalled); + assertEquals(holding, buyPresenter.outputData.getSimulatedHolding()); + } + + @Test + void testSwitchToHomeView() { + // Use in-memory portfolio data access object + InMemoryPortfolioDataAccess portfolioDataAccessObject = new InMemoryPortfolioDataAccess(); + + // Create a test presenter + TestBuyOutputBoundary buyPresenter = new TestBuyOutputBoundary(); + + // Create Interactor instance + BuyInteractor buyInteractor = new BuyInteractor(buyPresenter, portfolioDataAccessObject); + + // Execute test + buyInteractor.switchToHomeView(); + + // Verify that switchToHomeView was called + assertTrue(buyPresenter.switchToHomeViewCalled); + } + + @Test + void testSwitchToStockView() { + // Use in-memory portfolio data access object + InMemoryPortfolioDataAccess portfolioDataAccessObject = new InMemoryPortfolioDataAccess(); + + // Create a test presenter + TestBuyOutputBoundary buyPresenter = new TestBuyOutputBoundary(); + + // Create Interactor instance + BuyInteractor buyInteractor = new BuyInteractor(buyPresenter, portfolioDataAccessObject); + + // Execute test + buyInteractor.switchToStockView(); + + // Verify that switchToStockView was called + assertTrue(buyPresenter.switchToStockViewCalled); + } + + // In-memory portfolio data access object implementation + static class InMemoryPortfolioDataAccess implements PortfolioDataAccessInterface { + private final ArrayList portfolioList = new ArrayList<>(); + + @Override + public ArrayList getPortfolioList() { + return portfolioList; + } + + @Override + public void savePortfolioList() { + // Not needed for the test + } + + @Override + public void addToPortfolioList(SimulatedHolding simulatedHolding) { + portfolioList.add(simulatedHolding); + } + + @Override + public void removeFromPortfolioList(SimulatedHolding simulatedHolding) { + portfolioList.remove(simulatedHolding); + } + + @Override + public void createPortfolioList() { + // Not needed for the test + } + } + + // Test presenter class + static class TestBuyOutputBoundary implements BuyOutputBoundary { + boolean successViewCalled = false; + boolean switchToHomeViewCalled = false; + boolean switchToStockViewCalled = false; + BuyOutputData outputData; + + @Override + public void prepareSuccessView(BuyOutputData outputData) { + this.successViewCalled = true; + this.outputData = outputData; + } + + @Override + public void switchToHomeView() { + this.switchToHomeViewCalled = true; + } + + @Override + public void switchToStockView() { + this.switchToStockViewCalled = true; + } + } +} diff --git a/src/test/java/use_case/home_view/HomeViewInteratorTest.java b/src/test/java/use_case/home_view/HomeViewInteratorTest.java new file mode 100644 index 000000000..09fb330e4 --- /dev/null +++ b/src/test/java/use_case/home_view/HomeViewInteratorTest.java @@ -0,0 +1,584 @@ +package use_case.home_view; + +import data_access.DBUserDataAccessObject; +import data_access.FileUserDataAccessObject; +import entity.*; +import org.junit.Test; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +public class HomeViewInteratorTest { + + @Test + public void successSearchWithGivenTextTest() { + final SearchInputData inputData = new SearchInputData("COST"); + final UserFactory userFactory = new CommonUserFactory(); + final StockFactory stockFactory = new CommonStockFactory(); + final SimulatedHoldingFactory simulatedHoldingFactory = new CommonSimulatedHoldingFactory(); + final FileUserDataAccessObject watchListData = new FileUserDataAccessObject(simulatedHoldingFactory); + final DBUserDataAccessObject stockData = new DBUserDataAccessObject(userFactory, stockFactory); + + // This creates a successPresenter that tests whether the test case is as we expect. + HomeOutputBoundary successPresenter = new HomeOutputBoundary() { + @Override + public void prepareSuccessView(SearchOutputData searchOutputData) { + assertEquals("COST", searchOutputData.getStock().getSymbol()); + } + + @Override + public void prepareFailView(String errorMessage) { + // Do nothing right now + } + + @Override + public void switchToPortfolio() { + // Do nothing right now + } + + @Override + public void switchToWatchList(ArrayList watchListSymbols) { + // Do nothing right now + } + + @Override + public void switchToLoginView() { + // Do nothing right now + } + + @Override + public void switchToSignupView() { + // Do nothing right now + } + + @Override + public void getWatchListData(ArrayList watchList) { + // Do nothing + } + + @Override + public void deleteLocalData() { + // Do nothing + } + }; + + final HomeInputBoundary interactor = new HomeInteractor(stockData, watchListData, watchListData, successPresenter); + interactor.search(inputData); + } + + @Test + public void successSearchWithDefaultTextTest() { + final SearchInputData inputData = new SearchInputData(""); + final UserFactory userFactory = new CommonUserFactory(); + final StockFactory stockFactory = new CommonStockFactory(); + final SimulatedHoldingFactory simulatedHoldingFactory = new CommonSimulatedHoldingFactory(); + final FileUserDataAccessObject watchListData = new FileUserDataAccessObject(simulatedHoldingFactory); + final DBUserDataAccessObject stockData = new DBUserDataAccessObject(userFactory, stockFactory); + + // This creates a successPresenter that tests whether the test case is as we expect. + HomeOutputBoundary successPresenter = new HomeOutputBoundary() { + @Override + public void prepareSuccessView(SearchOutputData searchOutputData) { + assertEquals("NVDA", searchOutputData.getStock().getSymbol()); + } + + @Override + public void prepareFailView(String errorMessage) { + // Do nothing right now + } + + @Override + public void switchToPortfolio() { + // Do nothing right now + } + + @Override + public void switchToWatchList(ArrayList watchListSymbols) { + // Do nothing right now + } + + @Override + public void switchToLoginView() { + // Do nothing right now + } + + @Override + public void switchToSignupView() { + // Do nothing right now + } + + @Override + public void getWatchListData(ArrayList watchList) { + // Do nothing + } + + @Override + public void deleteLocalData() { + // Do nothing + } + }; + + final HomeInputBoundary interactor = new HomeInteractor(stockData, watchListData, watchListData, successPresenter); + interactor.search(inputData); + } + + @Test + public void failSearchWithWrongTextTest() { + final SearchInputData inputData = new SearchInputData("ABC"); + final UserFactory userFactory = new CommonUserFactory(); + final StockFactory stockFactory = new CommonStockFactory(); + final SimulatedHoldingFactory simulatedHoldingFactory = new CommonSimulatedHoldingFactory(); + final FileUserDataAccessObject watchListData = new FileUserDataAccessObject(simulatedHoldingFactory); + final DBUserDataAccessObject stockData = new DBUserDataAccessObject(userFactory, stockFactory); + + // This creates a successPresenter that tests whether the test case is as we expect. + HomeOutputBoundary successPresenter = new HomeOutputBoundary() { + @Override + public void prepareSuccessView(SearchOutputData searchOutputData) { + // this should never be reached since the test case should fail + fail("Use case success is unexpected."); + } + + @Override + public void prepareFailView(String errorMessage) { + assertEquals("Failed to fetch stock data", errorMessage); + } + + @Override + public void switchToPortfolio() { + // Do nothing right now + } + + @Override + public void switchToWatchList(ArrayList watchListSymbols) { + // Do nothing right now + } + + @Override + public void switchToLoginView() { + // Do nothing right now + } + + @Override + public void switchToSignupView() { + // Do nothing right now + } + + @Override + public void getWatchListData(ArrayList watchList) { + // Do nothing + } + + @Override + public void deleteLocalData() { + // Do nothing + } + }; + + final HomeInputBoundary interactor = new HomeInteractor(stockData, watchListData, watchListData, successPresenter); + interactor.search(inputData); + } + + @Test + public void switchToPortfolioListViewTest() { + final UserFactory userFactory = new CommonUserFactory(); + final StockFactory stockFactory = new CommonStockFactory(); + final SimulatedHoldingFactory simulatedHoldingFactory = new CommonSimulatedHoldingFactory(); + final FileUserDataAccessObject watchListData = new FileUserDataAccessObject(simulatedHoldingFactory); + final DBUserDataAccessObject stockData = new DBUserDataAccessObject(userFactory, stockFactory); + + // This creates a successPresenter that tests whether the test case is as we expect. + HomeOutputBoundary successPresenter = new HomeOutputBoundary() { + @Override + public void prepareSuccessView(SearchOutputData searchOutputData) { + // this should never be reached since the test case should fail + fail("Use case success is unexpected."); + } + + @Override + public void prepareFailView(String errorMessage) { + // this should never be reached since the test case should fail + fail("Use case success is unexpected."); + } + + @Override + public void switchToPortfolio() { + assert true; + } + + @Override + public void switchToWatchList(ArrayList watchListSymbols) { + // Do nothing right now + } + + @Override + public void switchToLoginView() { + // Do nothing right now + } + + @Override + public void switchToSignupView() { + // Do nothing right now + } + + @Override + public void getWatchListData(ArrayList watchList) { + // Do nothing + } + + @Override + public void deleteLocalData() { + // Do nothing + } + }; + + final HomeInputBoundary interactor = new HomeInteractor(stockData, watchListData, watchListData, successPresenter); + interactor.switchToPortfolio(); + } + + @Test + public void switchToWatchListViewTest() { + final UserFactory userFactory = new CommonUserFactory(); + final StockFactory stockFactory = new CommonStockFactory(); + final SimulatedHoldingFactory simulatedHoldingFactory = new CommonSimulatedHoldingFactory(); + final FileUserDataAccessObject watchListData = new FileUserDataAccessObject(simulatedHoldingFactory); + final DBUserDataAccessObject stockData = new DBUserDataAccessObject(userFactory, stockFactory); + + // This creates a successPresenter that tests whether the test case is as we expect. + HomeOutputBoundary successPresenter = new HomeOutputBoundary() { + @Override + public void prepareSuccessView(SearchOutputData searchOutputData) { + // this should never be reached since the test case should fail + fail("Use case success is unexpected."); + } + + @Override + public void prepareFailView(String errorMessage) { + // this should never be reached since the test case should fail + fail("Use case success is unexpected."); + } + + @Override + public void switchToPortfolio() { + // this should never be reached since the test case should fail + fail("Use case success is unexpected."); + } + + @Override + public void switchToWatchList(ArrayList watchListSymbols) { + // Do nothing right now + assert true; + } + + @Override + public void switchToLoginView() { + // Do nothing right now + } + + @Override + public void switchToSignupView() { + // Do nothing right now + } + + @Override + public void getWatchListData(ArrayList watchList) { + // Do nothing + } + + @Override + public void deleteLocalData() { + // Do nothing + } + }; + + final HomeInputBoundary interactor = new HomeInteractor(stockData, watchListData, watchListData, successPresenter); + interactor.switchToWatchList(); + } + + @Test + public void switchToLoginViewTest() { + final UserFactory userFactory = new CommonUserFactory(); + final StockFactory stockFactory = new CommonStockFactory(); + final SimulatedHoldingFactory simulatedHoldingFactory = new CommonSimulatedHoldingFactory(); + final FileUserDataAccessObject watchListData = new FileUserDataAccessObject(simulatedHoldingFactory); + final DBUserDataAccessObject stockData = new DBUserDataAccessObject(userFactory, stockFactory); + + // This creates a successPresenter that tests whether the test case is as we expect. + HomeOutputBoundary successPresenter = new HomeOutputBoundary() { + @Override + public void prepareSuccessView(SearchOutputData searchOutputData) { + // this should never be reached since the test case should fail + fail("Use case success is unexpected."); + } + + @Override + public void prepareFailView(String errorMessage) { + // this should never be reached since the test case should fail + fail("Use case success is unexpected."); + } + + @Override + public void switchToPortfolio() { + // this should never be reached since the test case should fail + fail("Use case success is unexpected."); + } + + @Override + public void switchToWatchList(ArrayList watchListSymbols) { + // this should never be reached since the test case should fail + fail("Use case success is unexpected."); + } + + @Override + public void switchToLoginView() { + // Do nothing right now + assert true; + } + + @Override + public void switchToSignupView() { + // Do nothing right now + } + + @Override + public void getWatchListData(ArrayList watchList) { + // Do nothing + } + + @Override + public void deleteLocalData() { + // Do nothing + } + }; + + final HomeInputBoundary interactor = new HomeInteractor(stockData, watchListData, watchListData, successPresenter); + interactor.switchToLoginView(); + } + + @Test + public void switchToSignupViewTest() { + final UserFactory userFactory = new CommonUserFactory(); + final StockFactory stockFactory = new CommonStockFactory(); + final SimulatedHoldingFactory simulatedHoldingFactory = new CommonSimulatedHoldingFactory(); + final FileUserDataAccessObject watchListData = new FileUserDataAccessObject(simulatedHoldingFactory); + final DBUserDataAccessObject stockData = new DBUserDataAccessObject(userFactory, stockFactory); + + // This creates a successPresenter that tests whether the test case is as we expect. + HomeOutputBoundary successPresenter = new HomeOutputBoundary() { + @Override + public void prepareSuccessView(SearchOutputData searchOutputData) { + // this should never be reached since the test case should fail + fail("Use case success is unexpected."); + } + + @Override + public void prepareFailView(String errorMessage) { + // this should never be reached since the test case should fail + fail("Use case success is unexpected."); + } + + @Override + public void switchToPortfolio() { + // this should never be reached since the test case should fail + fail("Use case success is unexpected."); + } + + @Override + public void switchToWatchList(ArrayList watchListSymbols) { + // this should never be reached since the test case should fail + fail("Use case success is unexpected."); + } + + @Override + public void switchToLoginView() { + // this should never be reached since the test case should fail + fail("Use case success is unexpected."); + } + + @Override + public void switchToSignupView() { + // Do nothing right now + assert true; + } + + @Override + public void getWatchListData(ArrayList watchList) { + // Do nothing + } + + @Override + public void deleteLocalData() { + // Do nothing + } + }; + + final HomeInputBoundary interactor = new HomeInteractor(stockData, watchListData, watchListData, successPresenter); + interactor.switchToSignupView(); + } + + @Test + public void getWatchlistDataTest() { + final UserFactory userFactory = new CommonUserFactory(); + final StockFactory stockFactory = new CommonStockFactory(); + final SimulatedHoldingFactory simulatedHoldingFactory = new CommonSimulatedHoldingFactory(); + final FileUserDataAccessObject watchListData = new FileUserDataAccessObject(simulatedHoldingFactory); + final DBUserDataAccessObject stockData = new DBUserDataAccessObject(userFactory, stockFactory); + + // This creates a successPresenter that tests whether the test case is as we expect. + HomeOutputBoundary successPresenter = new HomeOutputBoundary() { + @Override + public void prepareSuccessView(SearchOutputData searchOutputData) { + // this should never be reached since the test case should fail + fail("Use case success is unexpected."); + } + + @Override + public void prepareFailView(String errorMessage) { + // this should never be reached since the test case should fail + fail("Use case success is unexpected."); + } + + @Override + public void switchToPortfolio() { + // this should never be reached since the test case should fail + fail("Use case success is unexpected."); + } + + @Override + public void switchToWatchList(ArrayList watchListSymbols) { + // this should never be reached since the test case should fail + fail("Use case success is unexpected."); + } + + @Override + public void switchToLoginView() { + // this should never be reached since the test case should fail + fail("Use case success is unexpected."); + } + + @Override + public void switchToSignupView() { + // Do nothing right now + assert true; + } + + @Override + public void getWatchListData(ArrayList watchList) { + int expected = 3; + int actual = watchList.size(); + assertEquals(expected, actual); + } + + @Override + public void deleteLocalData() { + // Do nothing + } + }; + + final HomeInputBoundary interactor = new HomeInteractor(stockData, watchListData, watchListData, successPresenter); + interactor.getWatchListData(); + } + + @Test + public void deleteLocalDataTest() { + final UserFactory userFactory = new CommonUserFactory(); + final StockFactory stockFactory = new CommonStockFactory(); + final SimulatedHoldingFactory simulatedHoldingFactory = new CommonSimulatedHoldingFactory(); + final FileUserDataAccessObject watchListData = new FileUserDataAccessObject(simulatedHoldingFactory); + final DBUserDataAccessObject stockData = new DBUserDataAccessObject(userFactory, stockFactory); + + // This creates a successPresenter that tests whether the test case is as we expect. + HomeOutputBoundary successPresenter = new HomeOutputBoundary() { + @Override + public void prepareSuccessView(SearchOutputData searchOutputData) { + // this should never be reached since the test case should fail + fail("Use case success is unexpected."); + } + + @Override + public void prepareFailView(String errorMessage) { + // this should never be reached since the test case should fail + fail("Use case success is unexpected."); + } + + @Override + public void switchToPortfolio() { + // this should never be reached since the test case should fail + fail("Use case success is unexpected."); + } + + @Override + public void switchToWatchList(ArrayList watchListSymbols) { + // this should never be reached since the test case should fail + fail("Use case success is unexpected."); + } + + @Override + public void switchToLoginView() { + // this should never be reached since the test case should fail + fail("Use case success is unexpected."); + } + + @Override + public void switchToSignupView() { + // this should never be reached since the test case should fail + fail("Use case success is unexpected."); + } + + @Override + public void getWatchListData(ArrayList watchList) { + // this should never be reached since the test case should fail + fail("Use case success is unexpected."); + } + + @Override + public void deleteLocalData() { + int watchListSizeExpected = 3; + int watchListSizeActual = 0; + int portfolioListSizeExpected = 0; + int portfolioListSizeActual = 0; + + final String mainFilePath = "src/main/java/data_access/"; + final String watchListFilePath = "watchlist.txt"; + final String portfolioFilePath = "portfolio.txt"; + + // Using FileReader and BufferedReader to read the file + try (BufferedReader reader = new BufferedReader(new FileReader(mainFilePath + portfolioFilePath))) { + + String line; + while ((line = reader.readLine()) != null) { + portfolioListSizeActual++; + } + } + catch (IOException ex) { + // Normally, this means the file doesn't exist + assert false; + } + + // Using FileReader and BufferedReader to read the file + try (BufferedReader reader = new BufferedReader(new FileReader(mainFilePath + watchListFilePath))) { + final String line = reader.readLine(); + if (line != null) { + watchListSizeActual = line.split(",").length; + } + } + catch (IOException ex) { + // Normally, this means the file doesn't exist + assert false; + } + + assertEquals(watchListSizeExpected, watchListSizeActual); + assertEquals(portfolioListSizeExpected, portfolioListSizeActual); + } + }; + + final HomeInputBoundary interactor = new HomeInteractor(stockData, watchListData, watchListData, successPresenter); + interactor.deleteLocalData(); + } +} diff --git a/src/test/java/use_case/login/LoginInteractorTest.java b/src/test/java/use_case/login/LoginInteractorTest.java index 9fc1bfc89..7580145da 100644 --- a/src/test/java/use_case/login/LoginInteractorTest.java +++ b/src/test/java/use_case/login/LoginInteractorTest.java @@ -1,124 +1,256 @@ package use_case.login; -import data_access.InMemoryUserDataAccessObject; -import entity.CommonUserFactory; +import data_access.FileUserDataAccessObject; import entity.User; import entity.UserFactory; +import entity.CommonUserFactory; import org.junit.jupiter.api.Test; -import java.time.LocalDateTime; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; import static org.junit.jupiter.api.Assertions.*; class LoginInteractorTest { @Test - void successTest() { - LoginInputData inputData = new LoginInputData("Paul", "password"); - LoginUserDataAccessInterface userRepository = new InMemoryUserDataAccessObject(); + void testLoginInputData() { + // Test the LoginInputData class + LoginInputData inputData = new LoginInputData("testUser", "testPassword"); + assertEquals("testUser", inputData.getUsername()); + assertEquals("testPassword", inputData.getPassword()); + } - // For the success test, we need to add Paul to the data access repository before we log in. - UserFactory factory = new CommonUserFactory(); - User user = factory.create("Paul", "password"); - userRepository.save(user); + @Test + void testLoginOutputData() { + // Test the LoginOutputData class + LoginOutputData outputData = new LoginOutputData("testUser", false); + assertEquals("testUser", outputData.getUsername()); + } + + @Test + void testUserDoesNotExist() { + // Prepare input data + LoginInputData inputData = new LoginInputData("nonexistentUser", "password123"); + + // Use in-memory user data access object with a UserFactory + UserFactory userFactory = new CommonUserFactory(); + InMemoryLoginUserDataAccess userDataAccessObject = new InMemoryLoginUserDataAccess(userFactory); + + // Create a test presenter + LoginOutputBoundary loginPresenter = new LoginOutputBoundary() { + @Override + public void prepareSuccessView(LoginOutputData outputData) { + fail("Expected login failure, but login succeeded."); + } - // This creates a successPresenter that tests whether the test case is as we expect. - LoginOutputBoundary successPresenter = new LoginOutputBoundary() { @Override - public void prepareSuccessView(LoginOutputData user) { - assertEquals("Paul", user.getUsername()); + public void prepareFailView(String errorMessage) { + assertEquals("nonexistentUser: Account does not exist.", errorMessage); } @Override - public void prepareFailView(String error) { - fail("Use case failure is unexpected."); + public void switchToSignUpView() { + fail("switchToSignUpView should not be called in this test."); } }; - LoginInputBoundary interactor = new LoginInteractor(userRepository, successPresenter); - interactor.execute(inputData); + // Create Interactor instance + LoginInteractor loginInteractor = new LoginInteractor(userDataAccessObject, loginPresenter); + + // Execute test + loginInteractor.execute(inputData); } @Test - void successUserLoggedInTest() { - LoginInputData inputData = new LoginInputData("Paul", "password"); - LoginUserDataAccessInterface userRepository = new InMemoryUserDataAccessObject(); + void testIncorrectPassword() { + // Prepare input data + LoginInputData inputData = new LoginInputData("existingUser", "wrongPassword"); - // For the success test, we need to add Paul to the data access repository before we log in. - UserFactory factory = new CommonUserFactory(); - User user = factory.create("Paul", "password"); - userRepository.save(user); + // Use in-memory user data access object and add a user using UserFactory + UserFactory userFactory = new CommonUserFactory(); + InMemoryLoginUserDataAccess userDataAccessObject = new InMemoryLoginUserDataAccess(userFactory); + User user = userFactory.create("existingUser", "correctPassword", new ArrayList<>(), new ArrayList<>()); + userDataAccessObject.save(user); - // This creates a successPresenter that tests whether the test case is as we expect. - LoginOutputBoundary successPresenter = new LoginOutputBoundary() { + // Create a test presenter + LoginOutputBoundary loginPresenter = new LoginOutputBoundary() { @Override - public void prepareSuccessView(LoginOutputData user) { - assertEquals("Paul", userRepository.getCurrentUsername()); + public void prepareSuccessView(LoginOutputData outputData) { + fail("Expected login failure, but login succeeded."); } @Override - public void prepareFailView(String error) { - fail("Use case failure is unexpected."); + public void prepareFailView(String errorMessage) { + assertEquals("Incorrect password for \"existingUser\".", errorMessage); + } + + @Override + public void switchToSignUpView() { + fail("switchToSignUpView should not be called in this test."); } }; - LoginInputBoundary interactor = new LoginInteractor(userRepository, successPresenter); - assertEquals(null, userRepository.getCurrentUsername()); + // Create Interactor instance + LoginInteractor loginInteractor = new LoginInteractor(userDataAccessObject, loginPresenter); - interactor.execute(inputData); + // Execute test + loginInteractor.execute(inputData); } @Test - void failurePasswordMismatchTest() { - LoginInputData inputData = new LoginInputData("Paul", "wrong"); - LoginUserDataAccessInterface userRepository = new InMemoryUserDataAccessObject(); - - // For this failure test, we need to add Paul to the data access repository before we log in, and - // the passwords should not match. - UserFactory factory = new CommonUserFactory(); - User user = factory.create("Paul", "password"); - userRepository.save(user); - - // This creates a presenter that tests whether the test case is as we expect. - LoginOutputBoundary failurePresenter = new LoginOutputBoundary() { + void testSuccessfulLogin() { + // Prepare input data + LoginInputData inputData = new LoginInputData("existingUser", "correctPassword"); + + // Use in-memory user data access object and add a user using UserFactory + UserFactory userFactory = new CommonUserFactory(); + InMemoryLoginUserDataAccess userDataAccessObject = new InMemoryLoginUserDataAccess(userFactory); + User user = userFactory.create("existingUser", "correctPassword", new ArrayList<>(), new ArrayList<>()); + userDataAccessObject.save(user); + + // Create a test presenter + LoginOutputBoundary loginPresenter = new LoginOutputBoundary() { + @Override + public void prepareSuccessView(LoginOutputData outputData) { + assertEquals("existingUser", outputData.getUsername()); + } + @Override - public void prepareSuccessView(LoginOutputData user) { - // this should never be reached since the test case should fail - fail("Use case success is unexpected."); + public void prepareFailView(String errorMessage) { + fail("Expected login success, but login failed: " + errorMessage); } @Override - public void prepareFailView(String error) { - assertEquals("Incorrect password for \"Paul\".", error); + public void switchToSignUpView() { + fail("switchToSignUpView should not be called in this test."); } }; - LoginInputBoundary interactor = new LoginInteractor(userRepository, failurePresenter); - interactor.execute(inputData); + // Create a mock FileUserDataAccessObject + MockFileUserDataAccessObject dataAccessObject = new MockFileUserDataAccessObject(); + + // Create Interactor instance + LoginInteractor loginInteractor = new LoginInteractor(userDataAccessObject, loginPresenter); + + // Use reflection to set the private final field 'dataAccessObject' + try { + Field dataAccessObjectField = LoginInteractor.class.getDeclaredField("dataAccessObject"); + dataAccessObjectField.setAccessible(true); + dataAccessObjectField.set(loginInteractor, dataAccessObject); + } catch (NoSuchFieldException | IllegalAccessException e) { + fail("Failed to set dataAccessObject via reflection: " + e.getMessage()); + } + + // Execute test + loginInteractor.execute(inputData); + + // Verify that dataAccessObject methods were called + assertTrue(dataAccessObject.isUserLoggedIn()); + assertTrue(dataAccessObject.isSaveUserLoginStatusCalled()); + + // Verify that the current username was set + assertEquals("existingUser", userDataAccessObject.getCurrentUsername()); } @Test - void failureUserDoesNotExistTest() { - LoginInputData inputData = new LoginInputData("Paul", "password"); - LoginUserDataAccessInterface userRepository = new InMemoryUserDataAccessObject(); + void testSwitchToSignUpView() { + // Create a test presenter + TestLoginOutputBoundary loginPresenter = new TestLoginOutputBoundary(); - // Add Paul to the repo so that when we check later they already exist + // Use in-memory user data access object + UserFactory userFactory = new CommonUserFactory(); + InMemoryLoginUserDataAccess userDataAccessObject = new InMemoryLoginUserDataAccess(userFactory); - // This creates a presenter that tests whether the test case is as we expect. - LoginOutputBoundary failurePresenter = new LoginOutputBoundary() { - @Override - public void prepareSuccessView(LoginOutputData user) { - // this should never be reached since the test case should fail - fail("Use case success is unexpected."); - } + // Create Interactor instance + LoginInteractor loginInteractor = new LoginInteractor(userDataAccessObject, loginPresenter); - @Override - public void prepareFailView(String error) { - assertEquals("Paul: Account does not exist.", error); - } - }; + // Execute test + loginInteractor.switchToSignUpView(); + + // Verify that switchToSignUpView was called + assertTrue(loginPresenter.switchCalled); + } + + // In-memory user data access object implementation + static class InMemoryLoginUserDataAccess implements LoginUserDataAccessInterface { + private final Map users = new HashMap<>(); + private String currentUsername; + private final UserFactory userFactory; + + public InMemoryLoginUserDataAccess(UserFactory userFactory) { + this.userFactory = userFactory; + } + + @Override + public boolean existsByName(String username) { + return users.containsKey(username); + } + + @Override + public User get(String username) { + return users.get(username); + } + + @Override + public void save(User user) { + users.put(user.getName(), user); + } + + @Override + public void setCurrentUsername(String username) { + this.currentUsername = username; + } + + @Override + public String getCurrentUsername() { + return currentUsername; + } + } + + // Mock FileUserDataAccessObject + static class MockFileUserDataAccessObject extends FileUserDataAccessObject { + private boolean userLoggedIn = false; + private boolean saveUserLoginStatusCalled = false; + + @Override + public void setUserLoggedIn(boolean loggedIn) { + this.userLoggedIn = loggedIn; + } + + @Override + public void saveUserLoginStatus() { + this.saveUserLoginStatusCalled = true; + } + + public boolean isUserLoggedIn() { + return userLoggedIn; + } + + public boolean isSaveUserLoginStatusCalled() { + return saveUserLoginStatusCalled; + } + } + + // Test presenter class for switchToSignUpView test + static class TestLoginOutputBoundary implements LoginOutputBoundary { + boolean switchCalled = false; + + @Override + public void prepareSuccessView(LoginOutputData outputData) { + fail("Expected switchToSignUpView to be called, but prepareSuccessView was called."); + } + + @Override + public void prepareFailView(String errorMessage) { + fail("Expected switchToSignUpView to be called, but prepareFailView was called."); + } - LoginInputBoundary interactor = new LoginInteractor(userRepository, failurePresenter); - interactor.execute(inputData); + @Override + public void switchToSignUpView() { + switchCalled = true; + } } -} \ No newline at end of file +} diff --git a/src/test/java/use_case/logout/LogoutInteractorTest.java b/src/test/java/use_case/logout/LogoutInteractorTest.java index b005a402c..a38a4ba26 100644 --- a/src/test/java/use_case/logout/LogoutInteractorTest.java +++ b/src/test/java/use_case/logout/LogoutInteractorTest.java @@ -1,9 +1,5 @@ package use_case.logout; -import data_access.InMemoryUserDataAccessObject; -import entity.CommonUserFactory; -import entity.User; -import entity.UserFactory; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; @@ -11,34 +7,100 @@ class LogoutInteractorTest { @Test - void successTest() { - LogoutInputData inputData = new LogoutInputData("Paul"); - InMemoryUserDataAccessObject userRepository = new InMemoryUserDataAccessObject(); - - // For the success test, we need to add Paul to the data access repository before we log in. - UserFactory factory = new CommonUserFactory(); - User user = factory.create("Paul", "password"); - userRepository.save(user); - userRepository.setCurrentUsername("Paul"); - - // This creates a successPresenter that tests whether the test case is as we expect. - LogoutOutputBoundary successPresenter = new LogoutOutputBoundary() { - @Override - public void prepareSuccessView(LogoutOutputData user) { - // check that the output data contains the username of who logged out - assertEquals("Paul", user.getUsername()); - } - - @Override - public void prepareFailView(String error) { - fail("Use case failure is unexpected."); - } - }; - - LogoutInputBoundary interactor = new LogoutInteractor(userRepository, successPresenter); - interactor.execute(inputData); - // check that the user was logged out - assertNull(userRepository.getCurrentUsername()); + void testLogoutInputData() { + // Test the LogoutInputData class + LogoutInputData inputData = new LogoutInputData("testUser"); + assertEquals("testUser", inputData.getUsername()); } -} \ No newline at end of file + @Test + void testLogoutOutputData() { + // Test the LogoutOutputData class + LogoutOutputData outputData = new LogoutOutputData("testUser", false); + assertEquals("testUser", outputData.getUsername()); + assertFalse(outputData.isUseCaseFailed()); + } + + @Test + void testSuccessfulLogout() { + // Prepare input data + LogoutInputData inputData = new LogoutInputData("existingUser"); + + // Use in-memory user data access object + InMemoryLogoutUserDataAccess userDataAccessObject = new InMemoryLogoutUserDataAccess(); + userDataAccessObject.setCurrentUsername("existingUser"); + + // Create a test presenter + TestLogoutOutputBoundary logoutPresenter = new TestLogoutOutputBoundary(); + + // Create Interactor instance + LogoutInteractor logoutInteractor = new LogoutInteractor(userDataAccessObject, logoutPresenter); + + // Execute test + logoutInteractor.execute(inputData); + + // Verify that the current username is set to null + assertNull(userDataAccessObject.getCurrentUsername()); + + // Verify that the presenter was called correctly + assertTrue(logoutPresenter.successViewCalled); + assertEquals("existingUser", logoutPresenter.outputData.getUsername()); + } + + @Test + void testLogoutWhenNoUserLoggedIn() { + // Prepare input data with no user logged in + LogoutInputData inputData = new LogoutInputData(null); + + // Use in-memory user data access object + InMemoryLogoutUserDataAccess userDataAccessObject = new InMemoryLogoutUserDataAccess(); + + // Create a test presenter + TestLogoutOutputBoundary logoutPresenter = new TestLogoutOutputBoundary(); + + // Create Interactor instance + LogoutInteractor logoutInteractor = new LogoutInteractor(userDataAccessObject, logoutPresenter); + + // Execute test + logoutInteractor.execute(inputData); + + // Verify that the current username is still null + assertNull(userDataAccessObject.getCurrentUsername()); + + // Verify that the presenter was called correctly + assertTrue(logoutPresenter.successViewCalled); + assertNull(logoutPresenter.outputData.getUsername()); + } + + // In-memory user data access object implementation + static class InMemoryLogoutUserDataAccess implements LogoutUserDataAccessInterface { + private String currentUsername; + + @Override + public String getCurrentUsername() { + return currentUsername; + } + + @Override + public void setCurrentUsername(String username) { + this.currentUsername = username; + } + } + + // Test presenter class + static class TestLogoutOutputBoundary implements LogoutOutputBoundary { + boolean successViewCalled = false; + LogoutOutputData outputData; + + @Override + public void prepareSuccessView(LogoutOutputData outputData) { + this.successViewCalled = true; + this.outputData = outputData; + } + + @Override + public void prepareFailView(String errorMessage) { + fail("Expected success, but failure view was called with message: " + errorMessage); + } + } +} diff --git a/src/test/java/use_case/signup/SignupInteractorTest.java b/src/test/java/use_case/signup/SignupInteractorTest.java index 00f757c1c..40d5a7c4d 100644 --- a/src/test/java/use_case/signup/SignupInteractorTest.java +++ b/src/test/java/use_case/signup/SignupInteractorTest.java @@ -1,104 +1,204 @@ package use_case.signup; -import data_access.InMemoryUserDataAccessObject; import entity.CommonUserFactory; import entity.User; import entity.UserFactory; import org.junit.jupiter.api.Test; -import java.time.LocalDateTime; +import java.util.ArrayList; import static org.junit.jupiter.api.Assertions.*; class SignupInteractorTest { @Test - void successTest() { - SignupInputData inputData = new SignupInputData("Paul", "password", "password"); - SignupUserDataAccessInterface userRepository = new InMemoryUserDataAccessObject(); + void testSignupInputData() { + // Test the SignupInputData class + SignupInputData inputData = new SignupInputData("testUser", "testPassword", "testPassword"); + assertEquals("testUser", inputData.getUsername()); + assertEquals("testPassword", inputData.getPassword()); + assertEquals("testPassword", inputData.getRepeatPassword()); + } + + @Test + void testSignupOutputData() { + // Test the SignupOutputData class + SignupOutputData outputData = new SignupOutputData("testUser", false); + assertEquals("testUser", outputData.getUsername()); + assertFalse(outputData.isUseCaseFailed()); + } + + @Test + void testUserAlreadyExists() { + // Prepare input data + SignupInputData inputData = new SignupInputData("existingUser", "password123", "password123"); + + // Use in-memory user data access object with a UserFactory + UserFactory userFactory = new CommonUserFactory(); + InMemorySignupUserDataAccess userDataAccessObject = new InMemorySignupUserDataAccess(userFactory); + + // Add an existing user + User existingUser = userFactory.create("existingUser", "oldPassword", new ArrayList<>(), new ArrayList<>()); + userDataAccessObject.save(existingUser); - // This creates a successPresenter that tests whether the test case is as we expect. - SignupOutputBoundary successPresenter = new SignupOutputBoundary() { + // Create a test presenter + SignupOutputBoundary userPresenter = new SignupOutputBoundary() { @Override - public void prepareSuccessView(SignupOutputData user) { - // 2 things to check: the output data is correct, and the user has been created in the DAO. - assertEquals("Paul", user.getUsername()); - assertTrue(userRepository.existsByName("Paul")); + public void prepareSuccessView(SignupOutputData outputData) { + fail("Expected failure due to existing user, but success view was called."); } @Override - public void prepareFailView(String error) { - fail("Use case failure is unexpected."); + public void prepareFailView(String errorMessage) { + assertEquals("User already exists.", errorMessage); } @Override public void switchToLoginView() { - // This is expected + fail("switchToLoginView should not be called in this test."); } }; - SignupInputBoundary interactor = new SignupInteractor(userRepository, successPresenter, new CommonUserFactory()); + // Create Interactor instance + SignupInteractor interactor = new SignupInteractor(userDataAccessObject, userPresenter, userFactory); + + // Execute test interactor.execute(inputData); + + // No additional assertions needed; logic is verified in the presenter } @Test - void failurePasswordMismatchTest() { - SignupInputData inputData = new SignupInputData("Paul", "password", "wrong"); - SignupUserDataAccessInterface userRepository = new InMemoryUserDataAccessObject(); + void testPasswordsDoNotMatch() { + // Prepare input data + SignupInputData inputData = new SignupInputData("newUser", "password123", "password456"); - // This creates a presenter that tests whether the test case is as we expect. - SignupOutputBoundary failurePresenter = new SignupOutputBoundary() { + // Use in-memory user data access object with a UserFactory + UserFactory userFactory = new CommonUserFactory(); + InMemorySignupUserDataAccess userDataAccessObject = new InMemorySignupUserDataAccess(userFactory); + + // Create a test presenter + SignupOutputBoundary userPresenter = new SignupOutputBoundary() { @Override - public void prepareSuccessView(SignupOutputData user) { - // this should never be reached since the test case should fail - fail("Use case success is unexpected."); + public void prepareSuccessView(SignupOutputData outputData) { + fail("Expected failure due to password mismatch, but success view was called."); } @Override - public void prepareFailView(String error) { - assertEquals("Passwords don't match.", error); + public void prepareFailView(String errorMessage) { + assertEquals("Passwords don't match.", errorMessage); } @Override public void switchToLoginView() { - // This is expected + fail("switchToLoginView should not be called in this test."); } }; - SignupInputBoundary interactor = new SignupInteractor(userRepository, failurePresenter, new CommonUserFactory()); + // Create Interactor instance + SignupInteractor interactor = new SignupInteractor(userDataAccessObject, userPresenter, userFactory); + + // Execute test interactor.execute(inputData); + + // No additional assertions needed; logic is verified in the presenter } @Test - void failureUserExistsTest() { - SignupInputData inputData = new SignupInputData("Paul", "password", "wrong"); - SignupUserDataAccessInterface userRepository = new InMemoryUserDataAccessObject(); + void testSuccessfulSignup() { + // Prepare input data + SignupInputData inputData = new SignupInputData("newUser", "password123", "password123"); - // Add Paul to the repo so that when we check later they already exist - UserFactory factory = new CommonUserFactory(); - User user = factory.create("Paul", "pwd"); - userRepository.save(user); + // Use in-memory user data access object with a UserFactory + UserFactory userFactory = new CommonUserFactory(); + InMemorySignupUserDataAccess userDataAccessObject = new InMemorySignupUserDataAccess(userFactory); - // This creates a presenter that tests whether the test case is as we expect. - SignupOutputBoundary failurePresenter = new SignupOutputBoundary() { - @Override - public void prepareSuccessView(SignupOutputData user) { - // this should never be reached since the test case should fail - fail("Use case success is unexpected."); - } + // Create a test presenter + TestSignupOutputBoundary userPresenter = new TestSignupOutputBoundary(); - @Override - public void prepareFailView(String error) { - assertEquals("User already exists.", error); - } + // Create Interactor instance + SignupInteractor interactor = new SignupInteractor(userDataAccessObject, userPresenter, userFactory); - @Override - public void switchToLoginView() { - // This is expected + // Execute test + interactor.execute(inputData); + + // Verify that the user was saved + assertTrue(userDataAccessObject.existsByName("newUser")); + User savedUser = userDataAccessObject.get("newUser"); + assertEquals("password123", savedUser.getPassword()); + + // Verify that the presenter was called correctly + assertTrue(userPresenter.successViewCalled); + assertEquals("newUser", userPresenter.outputData.getUsername()); + } + + @Test + void testSwitchToLoginView() { + // Create a test presenter + TestSignupOutputBoundary userPresenter = new TestSignupOutputBoundary(); + + // Use in-memory user data access object + UserFactory userFactory = new CommonUserFactory(); + InMemorySignupUserDataAccess userDataAccessObject = new InMemorySignupUserDataAccess(userFactory); + + // Create Interactor instance + SignupInteractor interactor = new SignupInteractor(userDataAccessObject, userPresenter, userFactory); + + // Execute test + interactor.switchToLoginView(); + + // Verify that switchToLoginView was called + assertTrue(userPresenter.switchCalled); + } + + // In-memory user data access object implementation + static class InMemorySignupUserDataAccess implements SignupUserDataAccessInterface { + private final UserFactory userFactory; + private final ArrayList users = new ArrayList<>(); + + public InMemorySignupUserDataAccess(UserFactory userFactory) { + this.userFactory = userFactory; + } + + public void save(User user) { + users.add(user); + } + + public User get(String username) { + for (User user : users) { + if (user.getName().equals(username)) { + return user; + } } - }; + return null; + } - SignupInputBoundary interactor = new SignupInteractor(userRepository, failurePresenter, new CommonUserFactory()); - interactor.execute(inputData); + @Override + public boolean existsByName(String username) { + return get(username) != null; + } + } + + // Test presenter class + static class TestSignupOutputBoundary implements SignupOutputBoundary { + boolean successViewCalled = false; + boolean switchCalled = false; + SignupOutputData outputData; + + @Override + public void prepareSuccessView(SignupOutputData outputData) { + this.successViewCalled = true; + this.outputData = outputData; + } + + @Override + public void prepareFailView(String errorMessage) { + fail("Expected success, but failure view was called with message: " + errorMessage); + } + + @Override + public void switchToLoginView() { + this.switchCalled = true; + } } -} \ No newline at end of file +} diff --git a/src/test/java/use_case/stock_view/StockViewInteractorTest.java b/src/test/java/use_case/stock_view/StockViewInteractorTest.java new file mode 100644 index 000000000..a29c590c3 --- /dev/null +++ b/src/test/java/use_case/stock_view/StockViewInteractorTest.java @@ -0,0 +1,284 @@ +package use_case.stock; + +import entity.Stock; +import entity.CommonStock; +import org.junit.jupiter.api.Test; +import use_case.watchlist.WatchListDataAccessInterface; +import use_case.watchlist.WatchListModifyDataAccessInterface; +import java.util.ArrayList; +import static org.junit.jupiter.api.Assertions.*; + +public class StockViewInteractorTest { + + @Test + public void buyStockTest() { + // Arrange + StockOutputBoundary stockPresenter = new StockOutputBoundary() { + @Override + public void presentBuyView(Stock stock) { + assertEquals("AAPL", stock.getSymbol()); + } + + @Override + public void presentAddToWatchlist(Stock stock) {} + + @Override + public void presentRemoveFromWatchlist(Stock stock) {} + + @Override + public void updateFavouriteButton(Boolean isFavourite) { + + } + + public void updateFavouriteButton(boolean isFavourite) {} + }; + WatchListDataAccessInterface watchListDataAccess = new WatchListDataAccessInterface() { + @Override + public ArrayList getWatchList() { + return new ArrayList<>(); + } + + @Override + public void saveWatchList() { + + } + + @Override + public void createWatchList() { + + } + }; + WatchListModifyDataAccessInterface watchListModifyDataAccess = new WatchListModifyDataAccessInterface() { + @Override + public void addToWatchList(String symbol) {} + + @Override + public void removeFromWatchList(String symbol) {} + }; + StockInteractor stockInteractor = new StockInteractor(stockPresenter, watchListDataAccess, watchListModifyDataAccess); + Stock stock = new CommonStock("AAPL", 100.0, 120.0, 1000.0, 130.0, 95.0); + + // Act + stockInteractor.buyStock(stock); + } + + @Test + public void toggleWatchlist_AddToWatchlistTest() { + // Arrange + StockOutputBoundary stockPresenter = new StockOutputBoundary() { + @Override + public void presentBuyView(Stock stock) {} + + @Override + public void presentAddToWatchlist(Stock stock) { + assertEquals("AAPL", stock.getSymbol()); + } + + @Override + public void presentRemoveFromWatchlist(Stock stock) {} + + @Override + public void updateFavouriteButton(Boolean isFavourite) { + + } + + public void updateFavouriteButton(boolean isFavourite) { + assertTrue(isFavourite); + } + }; + WatchListDataAccessInterface watchListDataAccess = new WatchListDataAccessInterface() { + @Override + public ArrayList getWatchList() { + return new ArrayList<>(); + } + + @Override + public void saveWatchList() { + + } + + @Override + public void createWatchList() { + + } + }; + WatchListModifyDataAccessInterface watchListModifyDataAccess = new WatchListModifyDataAccessInterface() { + @Override + public void addToWatchList(String symbol) { + assertEquals("AAPL", symbol); + } + + @Override + public void removeFromWatchList(String symbol) {} + }; + StockInteractor stockInteractor = new StockInteractor(stockPresenter, watchListDataAccess, watchListModifyDataAccess); + Stock stock = new CommonStock("AAPL", 100.0, 120.0, 1000.0, 130.0, 95.0); + + // Act + stockInteractor.toggleWatchlist(stock, true); + } + + @Test + public void toggleWatchlist_RemoveFromWatchlistTest() { + // Arrange + StockOutputBoundary stockPresenter = new StockOutputBoundary() { + @Override + public void presentBuyView(Stock stock) {} + + @Override + public void presentAddToWatchlist(Stock stock) {} + + @Override + public void presentRemoveFromWatchlist(Stock stock) { + assertEquals("AAPL", stock.getSymbol()); + } + + @Override + public void updateFavouriteButton(Boolean isFavourite) { + + } + + public void updateFavouriteButton(boolean isFavourite) { + assertFalse(isFavourite); + } + }; + WatchListDataAccessInterface watchListDataAccess = new WatchListDataAccessInterface() { + @Override + public ArrayList getWatchList() { + ArrayList watchList = new ArrayList<>(); + watchList.add("AAPL"); + return watchList; + } + + @Override + public void saveWatchList() { + + } + + @Override + public void createWatchList() { + + } + }; + WatchListModifyDataAccessInterface watchListModifyDataAccess = new WatchListModifyDataAccessInterface() { + @Override + public void addToWatchList(String symbol) {} + + @Override + public void removeFromWatchList(String symbol) { + assertEquals("AAPL", symbol); + } + }; + StockInteractor stockInteractor = new StockInteractor(stockPresenter, watchListDataAccess, watchListModifyDataAccess); + Stock stock = new CommonStock("AAPL", 100.0, 120.0, 1000.0, 130.0, 95.0); + + // Act + stockInteractor.toggleWatchlist(stock, true); + } + + @Test + public void toggleWatchlist_UpdateFavouriteButtonWithoutModifyingData_AddToWatchlistTest() { + // Arrange + StockOutputBoundary stockPresenter = new StockOutputBoundary() { + @Override + public void presentBuyView(Stock stock) {} + + @Override + public void presentAddToWatchlist(Stock stock) {} + + @Override + public void presentRemoveFromWatchlist(Stock stock) {} + + @Override + public void updateFavouriteButton(Boolean isFavourite) { + + } + + public void updateFavouriteButton(boolean isFavourite) { + assertFalse(isFavourite); + } + }; + WatchListDataAccessInterface watchListDataAccess = new WatchListDataAccessInterface() { + @Override + public ArrayList getWatchList() { + return new ArrayList<>(); + } + + @Override + public void saveWatchList() { + + } + + @Override + public void createWatchList() { + + } + }; + WatchListModifyDataAccessInterface watchListModifyDataAccess = new WatchListModifyDataAccessInterface() { + @Override + public void addToWatchList(String symbol) {} + + @Override + public void removeFromWatchList(String symbol) {} + }; + StockInteractor stockInteractor = new StockInteractor(stockPresenter, watchListDataAccess, watchListModifyDataAccess); + Stock stock = new CommonStock("AAPL", 100.0, 120.0, 1000.0, 130.0, 95.0); + + // Act + stockInteractor.toggleWatchlist(stock, false); + } + + @Test + public void toggleWatchlist_UpdateFavouriteButtonWithoutModifyingData_RemoveFromWatchlistTest() { + // Arrange + StockOutputBoundary stockPresenter = new StockOutputBoundary() { + @Override + public void presentBuyView(Stock stock) {} + + @Override + public void presentAddToWatchlist(Stock stock) {} + + @Override + public void presentRemoveFromWatchlist(Stock stock) {} + + @Override + public void updateFavouriteButton(Boolean isFavourite) { + + } + + public void updateFavouriteButton(boolean isFavourite) { + assertTrue(isFavourite); + } + }; + WatchListDataAccessInterface watchListDataAccess = new WatchListDataAccessInterface() { + @Override + public ArrayList getWatchList() { + ArrayList watchList = new ArrayList<>(); + watchList.add("AAPL"); + return watchList; + } + + @Override + public void saveWatchList() { + + } + + @Override + public void createWatchList() { + + } + }; + WatchListModifyDataAccessInterface watchListModifyDataAccess = new WatchListModifyDataAccessInterface() { + @Override + public void addToWatchList(String symbol) {} + + @Override + public void removeFromWatchList(String symbol) {} + }; + StockInteractor stockInteractor = new StockInteractor(stockPresenter, watchListDataAccess, watchListModifyDataAccess); + Stock stock = new CommonStock("AAPL", 100.0, 120.0, 1000.0, 130.0, 95.0); + + // Act + stockInteractor.toggleWatchlist(stock, false); + } +} \ No newline at end of file