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..9919e8ef7 100644 --- a/README.md +++ b/README.md @@ -1,181 +1,46 @@ -# Lab 5: Logout +# 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) + +## 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. -## 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. 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..c92fb89af 100644 --- a/src/main/java/app/AppBuilder.java +++ b/src/main/java/app/AppBuilder.java @@ -1,42 +1,66 @@ 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 entity.CommonUserFactory; -import entity.UserFactory; +import data_access.DBUserDataAccessObject; +import data_access.FileUserDataAccessObject; +import entity.*; 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.*; +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.*; +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 view.LoggedInView; -import view.LoginView; -import view.SignupView; -import view.ViewManager; +import use_case.stock.StockInputBoundary; +import use_case.stock.StockInteractor; +import use_case.stock.StockOutputBoundary; +import view.*; /** * The AppBuilder class is responsible for putting together the pieces of @@ -54,30 +78,56 @@ 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 +138,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; } @@ -98,12 +148,94 @@ public AppBuilder addLoginView() { * @return this builder */ public AppBuilder addLoggedInView() { - loggedInViewModel = new LoggedInViewModel(); + loggedInViewModel = new LoggedInViewModel(); loggedInView = new LoggedInView(loggedInViewModel); cardPanel.add(loggedInView, loggedInView.getViewName()); return this; } + /** + * Adds the WatchList View to the application. + * @return this builder + */ + public AppBuilder addWatchListView() { + watchListViewModel = new WatchListViewModel(); + watchListView = new WatchListView(watchListViewModel, viewManagerModel, dbUserDataAccessObject); + 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); + + final WatchlistController controller = new WatchlistController(watchlistInteractor); + watchListView.setwatchlistController(controller); + return this; + } + /** * Adds the Signup Use Case to the application. * @return this builder @@ -112,7 +244,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 +257,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 +275,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 +292,64 @@ 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; + } + + 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..326679c4a 100644 --- a/src/main/java/data_access/FileUserDataAccessObject.java +++ b/src/main/java/data_access/FileUserDataAccessObject.java @@ -6,112 +6,257 @@ 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 entity.*; +import use_case.buy.BuyUserDataAccessInterface; import use_case.change_password.ChangePasswordUserDataAccessInterface; import use_case.login.LoginUserDataAccessInterface; +import use_case.portfolio.PortfolioDataAccessInterface; import use_case.signup.SignupUserDataAccessInterface; +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(); + 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())) { + double newAmount = simulatedHolding.getPurchaseAmount() + data.getPurchaseAmount(); + 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; + // Method to get the login status of the user + 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); + // Method to save the login status of the user + 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(); + // Change the login status of the user + 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..e69de29bb diff --git a/src/main/java/data_access/userIsLoggedIn.txt b/src/main/java/data_access/userIsLoggedIn.txt new file mode 100644 index 000000000..02e4a84d6 --- /dev/null +++ b/src/main/java/data_access/userIsLoggedIn.txt @@ -0,0 +1 @@ +false \ 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/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..c01f1b643 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(); + } + } +} \ No newline at end of file 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..d97feb9ff --- /dev/null +++ b/src/main/java/interface_adapter/buy_view/BuyController.java @@ -0,0 +1,45 @@ +package interface_adapter.buy_view; + +import use_case.buy.BuyInputBoundary; +import use_case.buy.BuyInputData; +import use_case.login.LoginInputBoundary; +import use_case.login.LoginInputData; + +/** + * 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 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..9db07f193 --- /dev/null +++ b/src/main/java/interface_adapter/buy_view/BuyPresenter.java @@ -0,0 +1,56 @@ +package interface_adapter.buy_view; + +import entity.CommonSimulatedHoldingFactory; +import entity.SimulatedHolding; +import interface_adapter.ViewManagerModel; +import interface_adapter.change_password.IsLoggedIn; +import interface_adapter.change_password.LoggedInState; +import interface_adapter.home_view.HomeViewModel; +import interface_adapter.portfolio.PortfolioState; +import interface_adapter.portfolio.PortfolioViewModel; +import interface_adapter.stock_view.StockViewModel; +import use_case.buy.BuyOutputBoundary; +import use_case.buy.BuyOutputData; +import use_case.login.LoginOutputBoundary; +import use_case.login.LoginOutputData; +import view.BuyView; + +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("getPortfolioList"); + + 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"); + } +} \ No newline at end of file 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..1fc64cb96 --- /dev/null +++ b/src/main/java/interface_adapter/buy_view/BuyState.java @@ -0,0 +1,28 @@ +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 getQunatity() { + 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..3d5e66e62 --- /dev/null +++ b/src/main/java/interface_adapter/buy_view/BuyViewModel.java @@ -0,0 +1,20 @@ +package interface_adapter.buy_view; + +import interface_adapter.ViewModel; +import interface_adapter.signup.SignupState; +import interface_adapter.stock_view.StockViewState; + +/** + * 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..6fba92b33 --- /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 watchListSymbols) { + // Pass watchList symbols to watchList view + final WatchListViewState watchListViewState = new WatchListViewState(); + watchListViewState.setWatchlist(watchListSymbols); + 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..c11b1320e 100644 --- a/src/main/java/interface_adapter/login/LoginPresenter.java +++ b/src/main/java/interface_adapter/login/LoginPresenter.java @@ -1,10 +1,15 @@ package interface_adapter.login; +import data_access.FileUserDataAccessObject; import interface_adapter.ViewManagerModel; import interface_adapter.change_password.LoggedInState; import interface_adapter.change_password.LoggedInViewModel; +import interface_adapter.change_password.IsLoggedIn; +import interface_adapter.home_view.HomeViewModel; +import interface_adapter.signup.SignupViewModel; import use_case.login.LoginOutputBoundary; import use_case.login.LoginOutputData; +import use_case.login.LoginUserDataAccessInterface; /** * The Presenter for the Login Use Case. @@ -14,26 +19,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 +61,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..eb6f6a641 --- /dev/null +++ b/src/main/java/interface_adapter/stock_view/StockController.java @@ -0,0 +1,21 @@ +package interface_adapter.stock_view; + +import entity.Stock; +import use_case.stock.StockInputBoundary; + +public class StockController { + + private final StockInputBoundary stockInteractor; + + public StockController(StockInputBoundary stockInteractor) { + this.stockInteractor = stockInteractor; + } + + public void buyStock(Stock stock) { + stockInteractor.buyStock(stock); + } + + 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..9c86e70f4 --- /dev/null +++ b/src/main/java/interface_adapter/stock_view/StockPresenter.java @@ -0,0 +1,84 @@ +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.watchlist_view.WatchListViewModel; +import interface_adapter.stock_view.WatchListViewState; +import use_case.stock.StockOutputBoundary; +import view.StockView; + +import java.util.ArrayList; + +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.getSymbol()); + 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.getSymbol()); + 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..130b6618d --- /dev/null +++ b/src/main/java/interface_adapter/stock_view/StockViewState.java @@ -0,0 +1,23 @@ +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..f2aa3d0a5 --- /dev/null +++ b/src/main/java/interface_adapter/watchlist_view/WatchListViewModel.java @@ -0,0 +1,19 @@ +package interface_adapter.watchlist_view; + +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()); + } +} 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..f12809125 --- /dev/null +++ b/src/main/java/interface_adapter/watchlist_view/WatchListViewState.java @@ -0,0 +1,64 @@ +package interface_adapter.stock_view; + +import entity.Stock; + +import java.util.ArrayList; + +/** + * 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(String ticker) { + if (this.watchlist != null) { + this.watchlist.add(ticker); + } + } + + public void remove(String ticker) { + if (this.watchlist != null) { + this.watchlist.remove(ticker); + } + } + + public void resetWatchlist() { + if (this.watchlist != null) { + final ArrayList temp = new ArrayList<>(); + for (String ticker : this.watchlist) { + if (ticker.equals("AAPL") || ticker.equals("COST") || ticker.equals("NVDA")) { + temp.add(ticker); + } + } + + 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..3184d72ce --- /dev/null +++ b/src/main/java/interface_adapter/watchlist_view/WatchlistController.java @@ -0,0 +1,33 @@ +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); + } + + public void getWatchList() { + watchlistInteractor.getWatchListData(); + } +} \ 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..8a4c2a370 --- /dev/null +++ b/src/main/java/use_case/buy/BuyInputData.java @@ -0,0 +1,28 @@ +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; + } + +} \ No newline at end of file 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..d02617b90 --- /dev/null +++ b/src/main/java/use_case/buy/BuyInteractor.java @@ -0,0 +1,52 @@ +package use_case.buy; + +import entity.CommonSimulatedHoldingFactory; +import entity.SimulatedHolding; +import entity.Stock; +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..03dabcd7d 100644 --- a/src/main/java/use_case/change_password/ChangePasswordInteractor.java +++ b/src/main/java/use_case/change_password/ChangePasswordInteractor.java @@ -3,6 +3,8 @@ import entity.User; import entity.UserFactory; +import java.util.ArrayList; + /** * The Change Password Interactor. */ @@ -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..4def53f89 --- /dev/null +++ b/src/main/java/use_case/home_view/HomeInteractor.java @@ -0,0 +1,109 @@ +package use_case.home_view; + +import entity.CommonStockFactory; +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) { + 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"); + } +// // Default search symbol is NVDA +// String stockSymbol = searchInputData.getStockSymbol(); +// if (stockSymbol == null || stockSymbol.isEmpty()) { +// stockSymbol = "NVDA"; +// } +// else { +// stockSymbol = stockSymbol.toUpperCase(); +// } +// 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); + } + + @Override + public void switchToPortfolio() { + homePresenter.switchToPortfolio(); + } + + @Override + public void switchToWatchList() { + final ArrayList watchListData = watchListDataAccessInterface.getWatchList(); + homePresenter.switchToWatchList(watchListData); + } + + @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<>(); + + final StockFactory stockFactory = new CommonStockFactory(); + + int i = 0; + for (String symbol : watchListData) { + final Stock stock = stockFactory.create(symbol, i, i, 100002322, 500.1, 100.23); + watchList.add(stock); + i += 1; + } + +// 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..afef013e6 --- /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..8e6575742 100644 --- a/src/main/java/use_case/login/LoginInteractor.java +++ b/src/main/java/use_case/login/LoginInteractor.java @@ -1,11 +1,17 @@ package use_case.login; +import data_access.FileUserDataAccessObject; import entity.User; +import interface_adapter.change_password.IsLoggedIn; +import view.HomeView; /** * The Login Interactor. */ public class LoginInteractor implements LoginInputBoundary { + // Add DAO + private final FileUserDataAccessObject dataAccessObject; + private final LoginUserDataAccessInterface userDataAccessObject; private final LoginOutputBoundary loginPresenter; @@ -13,6 +19,7 @@ public LoginInteractor(LoginUserDataAccessInterface userDataAccessInterface, LoginOutputBoundary loginOutputBoundary) { this.userDataAccessObject = userDataAccessInterface; this.loginPresenter = loginOutputBoundary; + this.dataAccessObject = new FileUserDataAccessObject(); } @Override @@ -28,6 +35,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 +46,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..df3fee059 --- /dev/null +++ b/src/main/java/use_case/portfolio/PortfolioDataAccessInterface.java @@ -0,0 +1,19 @@ +package use_case.portfolio; + +import entity.SimulatedHolding; + +import java.util.ArrayList; + +public interface PortfolioDataAccessInterface { + + public ArrayList getPortfolioList(); + + public void savePortfolioList(); + + public void addToPortfolioList(SimulatedHolding simulatedHolding); + + public void removeFromPortfolioList(SimulatedHolding simulatedHolding); + + public 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..9a8f8ea3e --- /dev/null +++ b/src/main/java/use_case/portfolio/PortfolioInputBoundary.java @@ -0,0 +1,8 @@ +package use_case.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..3ae93281b --- /dev/null +++ b/src/main/java/use_case/portfolio/PortfolioInteractor.java @@ -0,0 +1,51 @@ +package use_case.portfolio; + +import entity.CommonStockFactory; +import entity.SimulatedHolding; +import entity.Stock; +import use_case.home_view.HomeDataAccessInterface; + +import java.util.ArrayList; + +public class PortfolioInteractor implements PortfolioInputBoundary{ + 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<>(); + +// // Get data from API +// for (SimulatedHolding simulatedHolding : simulatedHoldings) { +// final Stock stock = homeDataAccessObject.getStock(simulatedHolding.getSymbol()); +// stockList.add(stock); +// } + + // Using fake data + int i = 100; + final CommonStockFactory stockFactory = new CommonStockFactory(); + for (SimulatedHolding simulatedHolding : simulatedHoldings) { + final Stock stock = stockFactory.create(simulatedHolding.getSymbol(), + 0, + i + 50, + 10000000.2, + 0, + 0); + stockList.add(stock); + + i += 100; + } + + portfolioPresenter.presentPortfolioListData(simulatedHoldings, stockList); + } +} 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..879182309 --- /dev/null +++ b/src/main/java/use_case/portfolio/PortfolioOutputBoundary.java @@ -0,0 +1,15 @@ +package use_case.portfolio; + +import java.util.ArrayList; + +import entity.SimulatedHolding; +import entity.Stock; + +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..f44f039fa 100644 --- a/src/main/java/use_case/signup/SignupInteractor.java +++ b/src/main/java/use_case/signup/SignupInteractor.java @@ -3,6 +3,8 @@ import entity.User; import entity.UserFactory; +import java.util.ArrayList; + /** * The Signup Interactor. */ @@ -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..940f78478 --- /dev/null +++ b/src/main/java/use_case/stock/StockInputBoundary.java @@ -0,0 +1,18 @@ +package use_case.stock; + +import entity.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..e7889c46d --- /dev/null +++ b/src/main/java/use_case/stock/StockInteractor.java @@ -0,0 +1,54 @@ +package use_case.stock; + +import entity.Stock; +import use_case.watchlist.WatchListDataAccessInterface; +import use_case.watchlist.WatchListModifyDataAccessInterface; + +import java.util.ArrayList; + +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) { + // When user comes from HomeView or WatchlistView, we should only update favourite button UI based on watchlist Data. + // We update watchlist data iff when user clicked favourite button. + 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..85f82d92d --- /dev/null +++ b/src/main/java/use_case/stock/StockOutputBoundary.java @@ -0,0 +1,29 @@ +package use_case.stock; + +import entity.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..9373b1e3b --- /dev/null +++ b/src/main/java/use_case/watchlist/WatchlistInputBoundary.java @@ -0,0 +1,21 @@ +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 searchInputData the input data + */ + + void getWatchListData(); +} 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..8ecd5bc11 --- /dev/null +++ b/src/main/java/use_case/watchlist/WatchlistInteractor.java @@ -0,0 +1,48 @@ +package use_case.home_view; + +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; + +/** + * The Home View Interactor. + */ +public class WatchlistInteractor implements WatchlistInputBoundary { + + private final WatchListDataAccessInterface watchlistDataAccessInterface; + private final WatchlistOutputBoundary watchlistPresenter; + + public WatchlistInteractor(WatchListDataAccessInterface watchlistDataAccessInterface, + WatchlistOutputBoundary watchlistPresenter) { + this.watchlistDataAccessInterface = watchlistDataAccessInterface; + this.watchlistPresenter = watchlistPresenter; + } + + 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 void getWatchListData() { + + } +} \ 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..72c0bf630 --- /dev/null +++ b/src/main/java/view/BuyView.java @@ -0,0 +1,183 @@ +package view; + +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +import javax.swing.*; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JLabel; +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(BuyViewModel viewModel, 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); + + // Decide if the price can be changed by the user. + 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); + + buy.addActionListener( + new ActionListener() { + public void actionPerformed(ActionEvent evt) { + try { + double quantity = Double.parseDouble(quantityInputField.getText()); + if (quantity > 0) { + final BuyState buyState = buyViewModel.getState(); + // Get the price from the status, not from the input field + buyController.execute( + buyState.getSymbol(), + Double.parseDouble(buyState.getPrice()), + quantity); + } else { + JOptionPane.showMessageDialog(null, "Please enter a positive quantity."); + } + } catch (NumberFormatException e) { + JOptionPane.showMessageDialog(null, "Please enter a valid quantity."); + } + } + } + ); + + cancel.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + buyController.switchToStockView(); + } + }); + + 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(); + } + }); + + this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); + + priceInputField.getDocument().addDocumentListener(new DocumentListener() { + + private void documentListenerHelper() { + final BuyState currentState = buyViewModel.getState(); + currentState.setPrice(new String(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(); + } + }); + + this.add(title); + this.add(priceInfo); + this.add(quantityInfo); + this.add(buttons); + } + + /** + * React to a button click that results in evt. + * @param evt the ActionEvent to react to + */ + public void actionPerformed(ActionEvent evt) { + System.out.println("Click " + evt.getActionCommand()); + } + + @Override + public void propertyChange(PropertyChangeEvent evt) { + final BuyState state = (BuyState) evt.getNewValue(); + setFields(state); + setPrice(); + } + + private void setFields(BuyState state) { + priceInputField.setText(state.getPrice()); + quantityInputField.setText(state.getQunatity()); + } + + public String getViewName() { + return viewName; + } + + public void setBuyController(BuyController buyController) { + this.buyController = buyController; + } + + public void setPrice() { + this.priceInputField.setText(buyViewModel.getState().getPrice()); + } +} \ No newline at end of file diff --git a/src/main/java/view/HomeView.java b/src/main/java/view/HomeView.java new file mode 100644 index 000000000..72632a6d3 --- /dev/null +++ b/src/main/java/view/HomeView.java @@ -0,0 +1,347 @@ +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")) { + // We only display first three stocks + ArrayList watchList = state.getWatchList(); + if (watchList.size() != 3) { + watchList = new ArrayList(state.getWatchList().subList(0, 3)); + } + + 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(); + } + } + + public void updateLabelStyle(JLabel label, int fontSize) { + label.setFont(new Font(FONTFAMILY, Font.BOLD, fontSize)); + } + + 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; + } + + public void setHomeController(HomeController homeController) { + this.homeController = homeController; + + // Preload watchList data + homeController.getWatchList(); + } + + 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(); + } + } + + public void changeLoginButtonText() { + if (dataAccessObject.isUserLoggedIn()) { + loginButton.setText("Log Out"); + } else { + loginButton.setText("Log In"); + } + homeViewModel.firePropertyChanged(); + } + + 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..2a3ac190b 100644 --- a/src/main/java/view/LoggedInView.java +++ b/src/main/java/view/LoggedInView.java @@ -49,6 +49,7 @@ public LoggedInView(LoggedInViewModel loggedInViewModel) { final JLabel usernameInfo = new JLabel("Currently logged in: "); username = new JLabel(); + //123 final JPanel buttons = new JPanel(); logOut = new JButton("Log Out"); buttons.add(logOut); @@ -100,9 +101,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 +138,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..ea98143b7 100644 --- a/src/main/java/view/LoginView.java +++ b/src/main/java/view/LoginView.java @@ -12,9 +12,12 @@ import javax.swing.JPanel; import javax.swing.JPasswordField; import javax.swing.JTextField; +import javax.swing.*; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; +import interface_adapter.ViewManagerModel; +import interface_adapter.change_password.IsLoggedIn; import interface_adapter.login.LoginController; import interface_adapter.login.LoginState; import interface_adapter.login.LoginViewModel; @@ -33,12 +36,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 +55,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 +84,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..9cf283cfb --- /dev/null +++ b/src/main/java/view/PortfolioView.java @@ -0,0 +1,213 @@ +package view; + +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; + +import javax.swing.*; +import java.awt.*; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.ArrayList; + +import javax.swing.border.Border; +import javax.swing.BorderFactory; + +public class PortfolioView extends JPanel implements PropertyChangeListener { + + 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); + + // panel setup + final JPanel statisticsPanel = new JPanel(); + statisticsPanel.setLayout(new BoxLayout(statisticsPanel, BoxLayout.Y_AXIS)); + statisticsPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 20, 10)); + statisticsPanel.setBackground(Color.WHITE); + + // Container for return button and current amount + final JPanel topRowPanel = new JPanel(); + topRowPanel.setLayout(new BoxLayout(topRowPanel, BoxLayout.Y_AXIS)); + topRowPanel.setBackground(Color.WHITE); + + // Return button + 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 : 0; + double allTimePercentage = purchasePrice != 0 ? ((closePrice - purchasePrice) / purchasePrice) * 100 : 0; + + // 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(); + + 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(); + } + } +} 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..05c16c46f --- /dev/null +++ b/src/main/java/view/StockView.java @@ -0,0 +1,209 @@ +package view; + +import data_access.FileUserDataAccessObject; +import entity.Stock; +import interface_adapter.ViewManagerModel; +import interface_adapter.stock_view.StockViewModel; +import interface_adapter.stock_view.StockController; +import interface_adapter.stock_view.StockViewState; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +/** + * The View for displaying detailed information about a single stock. + */ +public class StockView extends AbstractViewWithBackButton implements PropertyChangeListener { + private final String viewName = "StockView"; + + 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 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, 20))); + add(createActionButtonsPanel()); + add(Box.createVerticalGlue()); + } + + public void setStockController(StockController stockController) { + this.stockController = stockController; + } + + private void initializeComponents(JPanel contentPanel) { + stockSymbolLabel = createLabel("", 24, Font.BOLD); + stockPriceLabel = createLabel("", 18, 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("", 16, Font.PLAIN); + closePriceLabel = createLabel("", 16, Font.PLAIN); + lowPriceLabel = createLabel("", 16, Font.PLAIN); + highPriceLabel = createLabel("", 16, Font.PLAIN); + volumeLabel = createLabel("", 16, Font.PLAIN); + + contentPanel.add(stockInfoPanel); + contentPanel.add(Box.createRigidArea(new Dimension(0, 10))); + 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() { + 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("SansSerif", Font.BOLD, 18)); + buyButton.setFocusPainted(false); + + // Buy Button Action: Invoke the controller to buy stock + buyButton.addActionListener(e -> { + Stock currentStock = stockViewModel.getState().getStock(); + if (currentStock != null && stockController != null) { + stockController.buyStock(currentStock); + } + }); + + // Star button for favorite + favoriteButton = new JButton("☆"); + favoriteButton.setFont(new Font("SansSerif", Font.BOLD, 18)); + favoriteButton.setFocusPainted(false); + + // Favorite Button Action: Add or remove stock from watchlist + favoriteButton.addActionListener(e -> { + Stock currentStock = stockViewModel.getState().getStock(); + if (currentStock != null && stockController != null) { + boolean isFavorite = "★".equals(favoriteButton.getText()); + stockController.toggleWatchlist(currentStock, true); + } + }); + + actionPanel.add(Box.createHorizontalGlue()); + actionPanel.add(buyButton); + actionPanel.add(Box.createRigidArea(new Dimension(20, 0))); + actionPanel.add(favoriteButton); + actionPanel.add(Box.createHorizontalGlue()); + + return actionPanel; + } + + 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()); + + double volume = stock.getVolume(); + String volumeText = (volume >= 1_000_000) ? (volume / 1_000_000) + " M" : + (volume >= 1_000) ? (volume / 1_000) + " K" : String.valueOf(volume); + volumeLabel.setText("Volume: " + volumeText); + + // 星星按钮的可见性 + updateFavoriteButtonVisibility(stock.getSymbol()); + } + + private void updateFavoriteButtonVisibility(String stockSymbol) { + // 隐藏 AAPL, COST, NVDA 的星星按钮 + if ("AAPL".equals(stockSymbol) || "COST".equals(stockSymbol) || "NVDA".equals(stockSymbol)) { + favoriteButton.setVisible(false); + } + else { + favoriteButton.setVisible(true); + } + } + + private void setButtonVisible(boolean visible) { + favoriteButton.setVisible(visible); + buyButton.setVisible(visible); + } + + private JLabel createLabel(String text, int fontSize, int fontStyle) { + final JLabel label = new JLabel(text); + label.setFont(new Font("SansSerif", fontStyle, fontSize)); + label.setAlignmentX(Component.LEFT_ALIGNMENT); + return label; + } + + public String getViewName() { + return viewName; + } + + @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 + favoriteButton.setText(stockViewState.getIsFavorite() ? "★" : "☆"); + } + 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..01e4199ea --- /dev/null +++ b/src/main/java/view/WatchListView.java @@ -0,0 +1,195 @@ +package view; + +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 DBUserDataAccessObject dbUserDataAccessObject; + private final ScrollablePanel contentPanel = new ScrollablePanel(); + private WatchlistController watchlistController; + + public WatchListView(WatchListViewModel viewModel, ViewManagerModel viewManagerModel, DBUserDataAccessObject dbUserDataAccessObject) { + this.viewManagerModel = viewManagerModel; + this.dbUserDataAccessObject = dbUserDataAccessObject; + 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); + +// addStockItem(contentPanel, "AAPL", "$226.97", "−$0.26", "+0.11%", "+$44.09"); +// addStockItem(contentPanel, "COST", "$953.20", "+$39.27", "+4.30%", "+$386.00"); +// addStockItem(contentPanel, "QQQ", "$514.07", "+$0.56", "+0.60%", "+$141.25"); + + 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); + + final JLabel dailyChangeLabel = new JLabel(dailyChange + " (" + dailyPercentage + ")"); + dailyChangeLabel.setFont(new Font("SansSerif", Font.PLAIN, 16)); + final JLabel volumeLabel = new JLabel("Volume: " + volume); + volumeLabel.setFont(new Font("SansSerif", Font.PLAIN, 16)); + + 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 (String stockcode: WatchList) { +// final Stock stock = dbUserDataAccessObject.getStock(stockcode); +// final String price = Double.toString(stock.getClosePrice()); +// final String dailyChange = Double.toString(stock.getDailyChange()); +// final String dailyPercentage = Double.toString((stock.getDailyChange() / stock.getOpenPrice()) * 100) + "%"; +// final String volume = Double.toString(stock.getVolume()); + +// addStockItem(contentPanel, stock.getSymbol(), "$" + price, dailyChange, dailyPercentage, volume); + addStockItem(contentPanel, stockcode, "$" + "123", "+$" + "234", "0.45%", "100000.23"); + } + } + + public String getViewName() { + return "WatchListView"; + } + + public void setwatchlistController(WatchlistController controller) { + this.watchlistController = controller; + controller.getWatchList(); + } +} + 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..fca1d8a1d --- /dev/null +++ b/src/test/java/use_case/home_view/HomeViewInteratorTest.java @@ -0,0 +1,183 @@ +package use_case.home_view; + +import data_access.DBUserDataAccessObject; +import data_access.FileUserDataAccessObject; +import entity.*; +import org.junit.Test; + +import java.util.ArrayList; + +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 watchList) { + // 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 watchList) { + // 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 watchList) { + // 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); + } +} diff --git a/src/test/java/use_case/login/LoginInteractorTest.java b/src/test/java/use_case/login/LoginInteractorTest.java index 9fc1bfc89..3f4c442e3 100644 --- a/src/test/java/use_case/login/LoginInteractorTest.java +++ b/src/test/java/use_case/login/LoginInteractorTest.java @@ -7,6 +7,7 @@ import org.junit.jupiter.api.Test; import java.time.LocalDateTime; +import java.util.ArrayList; import static org.junit.jupiter.api.Assertions.*; @@ -19,7 +20,7 @@ void successTest() { // For the success test, we need to add Paul to the data access repository before we log in. UserFactory factory = new CommonUserFactory(); - User user = factory.create("Paul", "password"); + User user = factory.create("Paul", "password", new ArrayList<>(), new ArrayList<>()); userRepository.save(user); // This creates a successPresenter that tests whether the test case is as we expect. @@ -33,6 +34,11 @@ public void prepareSuccessView(LoginOutputData user) { public void prepareFailView(String error) { fail("Use case failure is unexpected."); } + + @Override + public void switchToSignUpView() { + // Do nothing + } }; LoginInputBoundary interactor = new LoginInteractor(userRepository, successPresenter); @@ -46,7 +52,7 @@ void successUserLoggedInTest() { // For the success test, we need to add Paul to the data access repository before we log in. UserFactory factory = new CommonUserFactory(); - User user = factory.create("Paul", "password"); + User user = factory.create("Paul", "password", new ArrayList<>(), new ArrayList<>()); userRepository.save(user); // This creates a successPresenter that tests whether the test case is as we expect. @@ -60,6 +66,11 @@ public void prepareSuccessView(LoginOutputData user) { public void prepareFailView(String error) { fail("Use case failure is unexpected."); } + + @Override + public void switchToSignUpView() { + // Do nothing + } }; LoginInputBoundary interactor = new LoginInteractor(userRepository, successPresenter); @@ -76,7 +87,7 @@ void failurePasswordMismatchTest() { // 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"); + User user = factory.create("Paul", "password", new ArrayList<>(), new ArrayList<>()); userRepository.save(user); // This creates a presenter that tests whether the test case is as we expect. @@ -91,6 +102,11 @@ public void prepareSuccessView(LoginOutputData user) { public void prepareFailView(String error) { assertEquals("Incorrect password for \"Paul\".", error); } + + @Override + public void switchToSignUpView() { + // Do nothing + } }; LoginInputBoundary interactor = new LoginInteractor(userRepository, failurePresenter); @@ -116,6 +132,11 @@ public void prepareSuccessView(LoginOutputData user) { public void prepareFailView(String error) { assertEquals("Paul: Account does not exist.", error); } + + @Override + public void switchToSignUpView() { + // Do nothing + } }; LoginInputBoundary interactor = new LoginInteractor(userRepository, failurePresenter); diff --git a/src/test/java/use_case/logout/LogoutInteractorTest.java b/src/test/java/use_case/logout/LogoutInteractorTest.java index b005a402c..5906b27cd 100644 --- a/src/test/java/use_case/logout/LogoutInteractorTest.java +++ b/src/test/java/use_case/logout/LogoutInteractorTest.java @@ -6,6 +6,8 @@ import entity.UserFactory; import org.junit.jupiter.api.Test; +import java.util.ArrayList; + import static org.junit.jupiter.api.Assertions.*; class LogoutInteractorTest { @@ -17,7 +19,7 @@ void successTest() { // For the success test, we need to add Paul to the data access repository before we log in. UserFactory factory = new CommonUserFactory(); - User user = factory.create("Paul", "password"); + User user = factory.create("Paul", "password", new ArrayList<>(), new ArrayList<>()); userRepository.save(user); userRepository.setCurrentUsername("Paul"); diff --git a/src/test/java/use_case/signup/SignupInteractorTest.java b/src/test/java/use_case/signup/SignupInteractorTest.java index 00f757c1c..224fe78c0 100644 --- a/src/test/java/use_case/signup/SignupInteractorTest.java +++ b/src/test/java/use_case/signup/SignupInteractorTest.java @@ -7,6 +7,7 @@ import org.junit.jupiter.api.Test; import java.time.LocalDateTime; +import java.util.ArrayList; import static org.junit.jupiter.api.Assertions.*; @@ -76,7 +77,7 @@ void failureUserExistsTest() { // Add Paul to the repo so that when we check later they already exist UserFactory factory = new CommonUserFactory(); - User user = factory.create("Paul", "pwd"); + User user = factory.create("Paul", "pwd", new ArrayList<>(), new ArrayList<>()); userRepository.save(user); // This creates a presenter that tests whether the test case is as we expect. diff --git a/src/test/java/use_case/stock/StockInteractorTest.java b/src/test/java/use_case/stock/StockInteractorTest.java new file mode 100644 index 000000000..57d6d4f98 --- /dev/null +++ b/src/test/java/use_case/stock/StockInteractorTest.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 StockInteractorTest { + + @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); + } +}