diff --git a/.gitignore b/.gitignore index 656c0ca098..e2945a8da8 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,6 @@ src/main/resources/docs/ bin/ # Customized -savedData.txt \ No newline at end of file +savedData.txt +savedWallet.txt +savedTask.txt diff --git a/build.gradle b/build.gradle index 8d5309ead4..c53983f31f 100644 --- a/build.gradle +++ b/build.gradle @@ -3,6 +3,7 @@ plugins { id 'application' id 'checkstyle' id 'com.github.johnrengelman.shadow' version '5.1.0' + id 'org.openjfx.javafxplugin' version '0.0.7' } group 'seedu.duke' @@ -19,7 +20,7 @@ dependencies { application { // Change this to your main class. - mainClassName = "Duke" + mainClassName = "main.Duke" } run { @@ -39,4 +40,9 @@ shadowJar { test { useJUnitPlatform() +} + +javafx { + version = "11.0.2" + modules = [ 'javafx.controls', 'javafx.fxml' ] } \ No newline at end of file diff --git a/savedWallet.txt b/savedWallet.txt index fd64fe57ef..f9a44637c2 100644 --- a/savedWallet.txt +++ b/savedWallet.txt @@ -1 +1,4 @@ -out $5.0 /date 2019-02-01 /tags food +1000.0 +out $250.0 /date 2019-11-07 /tags +out $100.0 /date 2019-11-07 /tags +in $100.0 /date 2019-11-07 /tags diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java deleted file mode 100644 index 5fe922699e..0000000000 --- a/src/main/java/Duke.java +++ /dev/null @@ -1,23 +0,0 @@ -import executor.task.TaskList; -import storage.StorageTask; -import storage.StorageWallet; -import ui.Ui; - -public class Duke { - protected static StorageTask storetask; - protected static StorageWallet storewallet; - protected static Ui ui; - protected static TaskList taskList; - - /** - * The Main method by which Duke will be launched. - */ - public static void main(String[] args) { - initialise(); - } - - private static void initialise() { - ui = new Ui("savedTask.txt", "savedWallet.txt"); - ui.initialise(); - } -} diff --git a/src/main/java/executor/command/Executor.java b/src/main/java/executor/Executor.java similarity index 51% rename from src/main/java/executor/command/Executor.java rename to src/main/java/executor/Executor.java index a6f9ac3870..aad10262e2 100644 --- a/src/main/java/executor/command/Executor.java +++ b/src/main/java/executor/Executor.java @@ -1,6 +1,13 @@ -package executor.command; +package executor; +import duke.exception.DukeException; +import executor.accessors.AccessDeny; +import executor.accessors.Accessor; +import executor.command.Command; +import executor.command.CommandError; +import executor.command.CommandType; import storage.StorageManager; +import utils.AccessType; import utils.InfoCapsule; public class Executor { @@ -15,7 +22,6 @@ public Executor(String taskPath, String walletPath) { /** * Parses the user input and executes the Command specified. - * * @param userInput User input from the CLI * @return True if the Command executed calls for an ExitRequest, false otherwise */ @@ -42,7 +48,33 @@ public static Command createCommand(CommandType commandType, String userInput) { return c; } - public void saveAllData() { - this.storageLayer.saveAllData(); + public InfoCapsule access(AccessType accessType, String argsStr) { + Accessor accessor = Executor.createAccessor(accessType, argsStr); + accessor.execute(this.storageLayer); + return accessor.getInfoCapsule(); + } + + public static Accessor createAccessor(AccessType accessType, String argsStr) { + Accessor accessor; + try { + accessor = (Accessor) accessType.getAccessClass().getDeclaredConstructor().newInstance(); + } catch (Exception e) { + accessor = new AccessDeny(argsStr); + } + return accessor; + } + + public InfoCapsule saveAllData() { + InfoCapsule infoCapsule = new InfoCapsule(); + try { + this.storageLayer.saveAllData(); + } catch (DukeException e) { + infoCapsule.setCodeError(); + infoCapsule.setOutputStr(e.getMessage()); + return infoCapsule; + } + infoCapsule.setCodeToast(); + infoCapsule.setOutputStr("Saved All Data Succesfully.\n"); + return infoCapsule; } } diff --git a/src/main/java/executor/accessors/AccessDeny.java b/src/main/java/executor/accessors/AccessDeny.java new file mode 100644 index 0000000000..0cedac5eee --- /dev/null +++ b/src/main/java/executor/accessors/AccessDeny.java @@ -0,0 +1,21 @@ +package executor.accessors; + +import storage.StorageManager; +import ui.UiCode; +import utils.AccessType; + +public class AccessDeny extends Accessor { + + public AccessDeny(String argsStr) { + super(); + this.accessType = AccessType.DENY; + this.argsStr = argsStr; + } + + @Override + public void execute(StorageManager storageManager) { + this.infoCapsule.setCodeError(); + this.infoCapsule.setOutputStr("Access Denied"); + + } +} diff --git a/src/main/java/executor/accessors/AccessPieChartData.java b/src/main/java/executor/accessors/AccessPieChartData.java new file mode 100644 index 0000000000..11d0301c8d --- /dev/null +++ b/src/main/java/executor/accessors/AccessPieChartData.java @@ -0,0 +1,41 @@ +package executor.accessors; + +import duke.exception.DukeException; +import javafx.collections.FXCollections; +import javafx.scene.chart.PieChart; +import storage.StorageManager; +import ui.UiCode; +import utils.AccessType; + +public class AccessPieChartData extends Accessor { + + /** + * Constructor for AccessPieChartData + */ + public AccessPieChartData() { + super(); + this.accessType = AccessType.PIE_CHART_DATA; + } + + @Override + public void execute(StorageManager storageManager) { + this.infoCapsule.setUiCode(UiCode.UPDATE); + Double walletBalance; + Double walletExpenses; + try { + walletBalance = storageManager.getWalletBalance(); + walletExpenses = storageManager.getWalletExpenses(); + } catch (DukeException e) { + e.printStackTrace(); + walletBalance = 0.0; + walletExpenses = 0.0; + this.infoCapsule.setUiCode(UiCode.ERROR); + this.infoCapsule.setOutputStr(e.getMessage()); + } + this.infoCapsule.setPieChartData(FXCollections.observableArrayList( + new PieChart.Data("Expenses", + walletExpenses), + new PieChart.Data("Balance", + walletBalance - walletExpenses))); + } +} diff --git a/src/main/java/executor/accessors/AccessTaskList.java b/src/main/java/executor/accessors/AccessTaskList.java new file mode 100644 index 0000000000..27e3da66cb --- /dev/null +++ b/src/main/java/executor/accessors/AccessTaskList.java @@ -0,0 +1,22 @@ +package executor.accessors; + +import storage.StorageManager; +import ui.UiCode; +import utils.AccessType; + +public class AccessTaskList extends Accessor { + + /** + * Constructor for AccessTaskList Class + */ + public AccessTaskList() { + super(); + this.accessType = AccessType.TASKLIST; + } + + @Override + public void execute(StorageManager storageManager) { + infoCapsule.setUiCode(UiCode.UPDATE); + infoCapsule.setTaskList(storageManager.getTaskList()); + } +} diff --git a/src/main/java/executor/accessors/AccessWallet.java b/src/main/java/executor/accessors/AccessWallet.java new file mode 100644 index 0000000000..3570e8aaea --- /dev/null +++ b/src/main/java/executor/accessors/AccessWallet.java @@ -0,0 +1,23 @@ +package executor.accessors; + +import duke.exception.DukeException; +import storage.StorageManager; +import ui.UiCode; +import utils.AccessType; + +public class AccessWallet extends Accessor { + + /** + * Constructor for AccessWallet Class + */ + public AccessWallet() { + super(); + this.accessType = AccessType.WALLET; + } + + @Override + public void execute(StorageManager storageManager) { + this.infoCapsule.setUiCode(UiCode.UPDATE); + this.infoCapsule.setWallet(storageManager.getWallet()); + } +} diff --git a/src/main/java/executor/accessors/AccessWalletBalance.java b/src/main/java/executor/accessors/AccessWalletBalance.java new file mode 100644 index 0000000000..b0314eb1d4 --- /dev/null +++ b/src/main/java/executor/accessors/AccessWalletBalance.java @@ -0,0 +1,27 @@ +package executor.accessors; + +import duke.exception.DukeException; +import storage.StorageManager; +import ui.UiCode; + +public class AccessWalletBalance extends Accessor { + + /** + * Constructor for AccessWalletBalance Class + */ + public AccessWalletBalance() { + super(); + } + + @Override + public void execute(StorageManager storageManager) { + try { + Double walletBalance = storageManager.getWalletBalance(); + this.infoCapsule.setUiCode(UiCode.UPDATE); + this.infoCapsule.setOutputDouble(walletBalance); + } catch (DukeException e) { + this.infoCapsule.setUiCode(UiCode.ERROR); + this.infoCapsule.setOutputStr(e.getMessage()); + } + } +} diff --git a/src/main/java/executor/accessors/AccessWalletExpenses.java b/src/main/java/executor/accessors/AccessWalletExpenses.java new file mode 100644 index 0000000000..5f02caacf5 --- /dev/null +++ b/src/main/java/executor/accessors/AccessWalletExpenses.java @@ -0,0 +1,31 @@ +package executor.accessors; + +import duke.exception.DukeException; +import storage.StorageManager; +import ui.UiCode; +import utils.AccessType; + +public class AccessWalletExpenses extends Accessor { + + /** + * Constructor for AccessExpenses + */ + public AccessWalletExpenses() { + super(); + this.accessType = AccessType.EXPENSES; + } + + @Override + public void execute(StorageManager storageManager) { + this.infoCapsule.setUiCode(UiCode.UPDATE); + Double expenses; + try { + expenses = storageManager.getWalletExpenses(); + } catch (DukeException e) { + this.infoCapsule.setUiCode(UiCode.ERROR); + this.infoCapsule.setOutputStr(e.getMessage()); + return; + } + this.infoCapsule.setOutputDouble(expenses); + } +} diff --git a/src/main/java/executor/accessors/Accessor.java b/src/main/java/executor/accessors/Accessor.java new file mode 100644 index 0000000000..741c37d7de --- /dev/null +++ b/src/main/java/executor/accessors/Accessor.java @@ -0,0 +1,39 @@ +package executor.accessors; + +import storage.StorageManager; +import ui.UiCode; +import utils.AccessType; +import utils.InfoCapsule; + +public abstract class Accessor { + protected AccessType accessType; + protected String argsStr; + protected InfoCapsule infoCapsule; + + public Accessor() { + this.accessType = AccessType.DENY; + this.infoCapsule = new InfoCapsule(); + infoCapsule.setUiCode(UiCode.ERROR); + infoCapsule.setOutputStr("Command was not executed.\n"); + } + + /** + * Executes the Accessor Method to Obtain Information for the UI + * @return InfoCapsule containing the desired information + */ + public abstract void execute(StorageManager storageManager); + + // Setters & Getters + + public InfoCapsule getInfoCapsule() { + return this.infoCapsule; + } + + public AccessType getAccessType() { + return this.accessType; + } + + public String getArgsStr() { + return this.argsStr; + } +} diff --git a/src/main/java/executor/command/Command.java b/src/main/java/executor/command/Command.java index 9494e182de..48cf0585f1 100644 --- a/src/main/java/executor/command/Command.java +++ b/src/main/java/executor/command/Command.java @@ -2,7 +2,7 @@ import executor.task.TaskList; import storage.StorageManager; -import ui.Wallet; +import ui.UiCode; import utils.InfoCapsule; import java.util.ArrayList; @@ -15,6 +15,7 @@ public abstract class Command { protected String userInput = null; protected CommandType commandType; protected String description = "NO DESCRIPTION"; + protected TaskList taskList; // Constructor @@ -24,7 +25,7 @@ public abstract class Command { */ public Command() { this.infoCapsule = new InfoCapsule(); - infoCapsule.setCodeError(); + infoCapsule.setUiCode(UiCode.ERROR); infoCapsule.setOutputStr("Command was not executed.\n"); } diff --git a/src/main/java/executor/command/CommandAdd.java b/src/main/java/executor/command/CommandAdd.java index cd32dbaf2f..ffd94fa554 100644 --- a/src/main/java/executor/command/CommandAdd.java +++ b/src/main/java/executor/command/CommandAdd.java @@ -10,8 +10,6 @@ public class CommandAdd extends Command { private double entryOne; private double entryTwo; - //Constructor - /** * Constructor for CommandListMonYear subCommand Class. * @param userInput String is the user input from the CLI diff --git a/src/main/java/executor/command/CommandAddReceipt.java b/src/main/java/executor/command/CommandAddReceipt.java index daeec17510..b41ab2777c 100644 --- a/src/main/java/executor/command/CommandAddReceipt.java +++ b/src/main/java/executor/command/CommandAddReceipt.java @@ -1,7 +1,6 @@ package executor.command; import interpreter.Parser; - import java.time.LocalDate; import java.util.ArrayList; import java.util.Arrays; diff --git a/src/main/java/executor/command/CommandBlank.java b/src/main/java/executor/command/CommandBlank.java index 51cf183e7b..d54ba036af 100644 --- a/src/main/java/executor/command/CommandBlank.java +++ b/src/main/java/executor/command/CommandBlank.java @@ -11,7 +11,7 @@ public class CommandBlank extends Command { public CommandBlank(String userInput) { super(); this.userInput = userInput; - this.description = "Prints a line separator"; + this.description = "Does Nothing"; this.commandType = CommandType.BLANK; } diff --git a/src/main/java/executor/command/CommandBye.java b/src/main/java/executor/command/CommandBye.java index 1fd87f656f..c4ed47e90a 100644 --- a/src/main/java/executor/command/CommandBye.java +++ b/src/main/java/executor/command/CommandBye.java @@ -16,7 +16,6 @@ public CommandBye(String userInput) { this.commandType = CommandType.BYE; } - @Override public void execute(StorageManager storageManager) { this.infoCapsule.setCodeExit(); } diff --git a/src/main/java/executor/command/CommandCliDisplay.java b/src/main/java/executor/command/CommandCliDisplay.java new file mode 100644 index 0000000000..1f8cd9a18b --- /dev/null +++ b/src/main/java/executor/command/CommandCliDisplay.java @@ -0,0 +1,22 @@ +package executor.command; + +import storage.StorageManager; +import ui.UiCode; + +public class CommandCliDisplay extends Command { + + /** + * Constructor for CommandCliDisplay subCommand Class. + * @param userInput The user input from the CLI + */ + public CommandCliDisplay(String userInput) { + this.userInput = userInput; + this.description = "Displays the Command Line Display"; + this.commandType = CommandType.CLI; + } + + @Override + public void execute(StorageManager storageManager) { + this.infoCapsule.setUiCode(UiCode.DISPLAY_CLI); + } +} diff --git a/src/main/java/executor/command/CommandDateList.java b/src/main/java/executor/command/CommandDateList.java index 23e59bb7dd..482caa1b0a 100644 --- a/src/main/java/executor/command/CommandDateList.java +++ b/src/main/java/executor/command/CommandDateList.java @@ -23,14 +23,8 @@ public CommandDateList(String userInput) { @Override public void execute(StorageManager storageManager) { - if (this.date == null || this.date.isEmpty()) { - this.infoCapsule.setCodeError(); - this.infoCapsule.setOutputStr("Date input is missing. FORMAT : datelist yyyy-mm-dd"); - return; - } - String outputStr; + String outputStr = "You have the following receipts for" + " " + date + "\n"; try { - outputStr = "You have the following receipts for" + " " + date; outputStr += storageManager.getReceiptsByDate(this.date).getPrintableReceipts(); } catch (DukeException e) { this.infoCapsule.setCodeError(); diff --git a/src/main/java/executor/command/CommandHelp.java b/src/main/java/executor/command/CommandHelp.java index eb926ddfb3..f2082132bc 100644 --- a/src/main/java/executor/command/CommandHelp.java +++ b/src/main/java/executor/command/CommandHelp.java @@ -1,5 +1,6 @@ package executor.command; +import executor.Executor; import interpreter.Parser; import storage.StorageManager; import java.util.ArrayList; @@ -33,7 +34,7 @@ public void execute(StorageManager storageManager) { break; } } - if (flag == false) { + if (!flag) { this.infoCapsule.setCodeError(); this.infoCapsule.setOutputStr("Command invalid. Enter 'help' to see all the available commands.\n"); return; diff --git a/src/main/java/executor/command/CommandHomeDisplay.java b/src/main/java/executor/command/CommandHomeDisplay.java new file mode 100644 index 0000000000..f1ed511f97 --- /dev/null +++ b/src/main/java/executor/command/CommandHomeDisplay.java @@ -0,0 +1,23 @@ +package executor.command; + +import storage.StorageManager; +import ui.UiCode; +import ui.gui.MainWindow; + +public class CommandHomeDisplay extends Command { + + /** + * Constructor for CommandHomeDisplay subCommand Class. + * @param userInput The user input from the CLI + */ + public CommandHomeDisplay(String userInput) { + this.userInput = userInput; + this.description = "Displays the Home Page"; + this.commandType = CommandType.HOME; + } + + @Override + public void execute(StorageManager storageManager) { + this.infoCapsule.setUiCode(UiCode.DISPLAY_HOME); + } +} diff --git a/src/main/java/executor/command/CommandMarkDone.java b/src/main/java/executor/command/CommandMarkDone.java index f818d4d02a..3f1ed4e5ee 100644 --- a/src/main/java/executor/command/CommandMarkDone.java +++ b/src/main/java/executor/command/CommandMarkDone.java @@ -17,7 +17,6 @@ public CommandMarkDone(String userInput) { this.description = "Marks a certain task as done \n" + "FORMAT : "; this.commandType = CommandType.DONE; - } @Override @@ -42,8 +41,6 @@ public void execute(StorageManager storageManager) { this.infoCapsule.setOutputStr(outputStr); } - - /** * Generates the standard duke reply to inform user that the Task is marked done. * @param index The index of the Task in the TaskList diff --git a/src/main/java/executor/command/CommandReminder.java b/src/main/java/executor/command/CommandReminder.java index 0e0a13e627..bdccf5f75a 100644 --- a/src/main/java/executor/command/CommandReminder.java +++ b/src/main/java/executor/command/CommandReminder.java @@ -5,7 +5,6 @@ import java.time.LocalDate; public class CommandReminder extends Command { - private LocalDate currentDate; /** diff --git a/src/main/java/executor/command/CommandType.java b/src/main/java/executor/command/CommandType.java index c46f6b3ed8..5f46399075 100644 --- a/src/main/java/executor/command/CommandType.java +++ b/src/main/java/executor/command/CommandType.java @@ -1,5 +1,7 @@ package executor.command; +import ui.gui.CommandLineDisplay; + public enum CommandType { TASK(CommandNewTask.class), BYE(CommandBye.class), @@ -31,6 +33,8 @@ public enum CommandType { DATELIST(CommandDateList.class), ERROR(CommandError.class), WEATHER(CommandWeather.class), + HOME(CommandHomeDisplay.class), + CLI(CommandCliDisplay.class), ADD(CommandAdd.class), SUB(CommandSub.class), DIV(CommandDiv.class), diff --git a/src/main/java/executor/command/CommandWeather.java b/src/main/java/executor/command/CommandWeather.java index 7d2041d7cc..7e3408720c 100644 --- a/src/main/java/executor/command/CommandWeather.java +++ b/src/main/java/executor/command/CommandWeather.java @@ -6,7 +6,6 @@ import duke.exception.DukeException; import interpreter.Parser; import storage.StorageManager; - import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.URL; diff --git a/src/main/java/executor/task/Task.java b/src/main/java/executor/task/Task.java index 4c62a4f826..15bf850d16 100644 --- a/src/main/java/executor/task/Task.java +++ b/src/main/java/executor/task/Task.java @@ -61,18 +61,12 @@ public void markNotDone() { * Generates a String Describing the Task Object. * Optimized for user's reading. * - * @return String detailing the Task Object, including type, isDone, taskName and taskDetails + * @return String detailing the Task Object, taskName and taskDetails */ public String genTaskDesc() { String generatedStr = ""; - if (!this.taskName.isEmpty()) { - generatedStr += "[" - + this.getStatusIcon() - + "]" - + "[" - + this.genTypeAcronym() - + "] " - + this.taskName; + if (this.taskName != null) { + generatedStr = this.taskName; } if (this.detailDesc != null || this.taskDetails != null) { generatedStr += " ("; diff --git a/src/main/java/executor/task/TaskList.java b/src/main/java/executor/task/TaskList.java index dd6cffdf24..aa823853ec 100644 --- a/src/main/java/executor/task/TaskList.java +++ b/src/main/java/executor/task/TaskList.java @@ -15,7 +15,6 @@ public TaskList() { /** * Deletes a task via its index. - * * @param index Index of the task to be deleted */ public void deleteTaskByIndex(int index) { @@ -24,7 +23,6 @@ public void deleteTaskByIndex(int index) { /** * Adds a new task to the List. - * * @param newTask The Task Object to be added */ public void addTask(Task newTask) { @@ -72,7 +70,6 @@ public Task getMostRecentTaskAdded() { /** * Initializes a 'Task' subclass based on TaskType. * TODO: Think about how this can be neater. - * * @param taskDesc The task description from the user input * @param taskType TaskType enum that specifies the subclass to create */ @@ -114,7 +111,7 @@ public static Task createTaskFromString(String userInput) throws DukeException { String[] parsedInput = Parser.parseStoredTaskDetails(userInput); TaskType taskType = TaskType.valueOf(parsedInput[0]); Task newTask = TaskList.createTask(taskType, parsedInput[1]); - if (Boolean.valueOf(parsedInput[2])) { + if (Boolean.parseBoolean(parsedInput[2])) { newTask.markDone(); } return newTask; diff --git a/src/main/java/interpreter/Interpreter.java b/src/main/java/interpreter/Interpreter.java index d755d92d89..0f31a70c08 100644 --- a/src/main/java/interpreter/Interpreter.java +++ b/src/main/java/interpreter/Interpreter.java @@ -1,7 +1,8 @@ package interpreter; +import utils.AccessType; import executor.command.CommandType; -import executor.command.Executor; +import executor.Executor; import utils.InfoCapsule; public class Interpreter { @@ -21,7 +22,11 @@ public InfoCapsule interpret(String userInput) { return this.executorLayer.runCommand(commandType, userInput); } - public void requestSave() { - this.executorLayer.saveAllData(); + public InfoCapsule requestSave() { + return this.executorLayer.saveAllData(); + } + + public InfoCapsule request(AccessType accessType, String argsStr) { + return this.executorLayer.access(accessType, argsStr); } } diff --git a/src/main/java/interpreter/Parser.java b/src/main/java/interpreter/Parser.java index 9f1fe75f14..3809a8b3d2 100644 --- a/src/main/java/interpreter/Parser.java +++ b/src/main/java/interpreter/Parser.java @@ -35,7 +35,7 @@ public static CommandType parseForCommandType(String userInput) { String commandStr = parseForEnum(userInput, CommandType.getNames()); // Type of command not as specified inside the enum types if (commandStr == "") { - return null; + return CommandType.ERROR; } return CommandType.valueOf(commandStr); } diff --git a/src/main/java/main/Duke.java b/src/main/java/main/Duke.java new file mode 100644 index 0000000000..ccbf80c0d1 --- /dev/null +++ b/src/main/java/main/Duke.java @@ -0,0 +1,25 @@ +package main; + +import executor.task.TaskList; +import ui.gui.MainGui; + +public class Duke { + protected static MainGui gui; + protected static TaskList taskList; + + /** + * The Main method by which main.Duke will be launched. + */ + public static void main(String[] args) { + initializeGui(args); + } + + private static void initializeGui(String[] args) { + gui = new MainGui(); + gui.initialize(args); + } + + private static void initializeUi() { + + } +} diff --git a/src/main/java/storage/StorageManager.java b/src/main/java/storage/StorageManager.java index 9e0d7df9b0..30ec123c74 100644 --- a/src/main/java/storage/StorageManager.java +++ b/src/main/java/storage/StorageManager.java @@ -5,6 +5,7 @@ import executor.task.Task; import executor.task.TaskList; import executor.task.TaskType; +import main.Duke; import ui.Receipt; import ui.ReceiptTracker; import ui.Wallet; @@ -35,7 +36,7 @@ public StorageManager() { this.initializationStatus = ""; } - public void saveAllData() { + public void saveAllData() throws DukeException{ this.taskStore.saveData(this.taskList); this.walletStore.saveData(this.wallet); } @@ -321,7 +322,7 @@ private String loadTasks(String taskPath) { this.taskStore = new StorageTask(taskPath); String outputStr; try { - this.taskList = this.taskStore.loadData(); + this.taskStore.loadData(this.taskList); outputStr = "Tasks loaded successfully.\n"; } catch (DukeException e) { outputStr = e.getMessage(); @@ -333,7 +334,7 @@ private String loadWallet(String walletPath) { String outputStr; this.walletStore = new StorageWallet(walletPath); try { - this.wallet = this.walletStore.loadData(); + this.walletStore.loadData(this.wallet); outputStr = "Wallet loaded successfully.\n"; } catch (DukeException e) { outputStr = e.getMessage(); diff --git a/src/main/java/storage/StorageTask.java b/src/main/java/storage/StorageTask.java index 23209677a2..8d9ab05bdc 100644 --- a/src/main/java/storage/StorageTask.java +++ b/src/main/java/storage/StorageTask.java @@ -14,7 +14,6 @@ public class StorageTask { /** * * Constrctor for the 'StorageTask' Class. - * * @param filePath The file path to be used to store and load data */ public StorageTask(String filePath) { @@ -23,10 +22,9 @@ public StorageTask(String filePath) { /** * Method to save the current list of tasks. - * - * @param taskList TaskList class + * @param taskList TaskList that houses all the Tasks to be saved */ - public void saveData(TaskList taskList) { + public void saveData(TaskList taskList) throws DukeException { try { FileWriter writer = new FileWriter(this.filePath); for (Task task : taskList) { @@ -35,17 +33,15 @@ public void saveData(TaskList taskList) { } writer.close(); } catch (Exception e) { - System.out.println(e); + throw new DukeException("Unable to load saved Task Data.\n"); } } /** * Method to load previously saved list of tasks. - * - * @return TaskList class + * @param taskList TaskList that will house all the Tasks */ - public TaskList loadData() throws DukeException { - TaskList taskList = new TaskList(); + public void loadData(TaskList taskList) throws DukeException { try { File file = new File(this.filePath); Scanner scanner = new Scanner(file); @@ -61,7 +57,6 @@ public TaskList loadData() throws DukeException { } catch (Exception e) { throw new DukeException("No Previously Saved Tasks.\n"); } - return taskList; } /** diff --git a/src/main/java/storage/StorageWallet.java b/src/main/java/storage/StorageWallet.java index 6ebb543669..e3ad96ba2a 100644 --- a/src/main/java/storage/StorageWallet.java +++ b/src/main/java/storage/StorageWallet.java @@ -17,8 +17,7 @@ public class StorageWallet { protected String filePath; /** - * * Constrctor for the 'StorageWallet' Class. - * + * Constrctor for the 'StorageWallet' Class. * @param filePath The file path to be used to store and load data */ public StorageWallet(String filePath) { @@ -27,34 +26,38 @@ public StorageWallet(String filePath) { /** * Method to save the current list of receipts. - * - * @param wallet Wallet class + * @param wallet Wallet Object that stores all the Receipts */ - public void saveData(Wallet wallet) { + public void saveData(Wallet wallet) throws DukeException { try { FileWriter writer = new FileWriter(this.filePath); + writer.write(wallet.getBalance().toString() + "\n"); for (Receipt receipt : wallet.getReceipts()) { String strSave = Parser.encodeReceipt(receipt); writer.write(strSave); - //writer.append(strSave); } writer.close(); } catch (Exception e) { - System.out.println(e); + throw new DukeException("Unable to save Wallet Data.\n"); } } /** * Method to load previously saved list of receipts. - * - * @return Wallet class + * @param wallet Wallet Object to be used to house all the Receipts */ - public Wallet loadData() throws DukeException { - Wallet wallet = new Wallet(); + public void loadData(Wallet wallet) throws DukeException { try { File file = new File(this.filePath); Scanner scanner = new Scanner(file); - Receipt newReceipt; + String storedBalanceStr = scanner.nextLine(); + double storedBalanceDouble = 0.0; + try { + storedBalanceDouble = Double.parseDouble(storedBalanceStr); + } catch (Exception e) { + throw new DukeException("Balance cannot be read"); + } + wallet.setBalance(storedBalanceDouble); while (scanner.hasNextLine()) { String loadedInput = scanner.nextLine(); if (loadedInput.equals("")) { @@ -65,11 +68,11 @@ public Wallet loadData() throws DukeException { } catch (Exception e) { throw new DukeException("No Previously Saved Wallet Data."); } - return wallet; } /** * Converts saved String in StorageWallet to actual Receipt object and saves in Wallet Object. + * @param wallet Wallet Object for the Receipt to be added in. * @param loadedInput The saved String to be converted */ private void parseAddReceiptFromStorageString(Wallet wallet, String loadedInput) { diff --git a/src/main/java/ui/Ui.java b/src/main/java/ui/Ui.java index 49944860ce..05ca2590b3 100644 --- a/src/main/java/ui/Ui.java +++ b/src/main/java/ui/Ui.java @@ -109,7 +109,8 @@ private void updateUi(InfoCapsule infoCapsule) { */ private void exitUi() { this.scanner.close(); - + InfoCapsule infoCapsule = this.interpreterLayer.requestSave(); + this.updateUi(infoCapsule); } /** diff --git a/src/main/java/ui/UiCode.java b/src/main/java/ui/UiCode.java index b4f838ae10..79c695984c 100644 --- a/src/main/java/ui/UiCode.java +++ b/src/main/java/ui/UiCode.java @@ -1,5 +1,5 @@ package ui; public enum UiCode { - TOAST, CLI, UPDATE, ERROR, EXIT; + TOAST, CLI, UPDATE, ERROR, EXIT, DISPLAY_HOME, DISPLAY_CLI; } diff --git a/src/main/java/ui/Wallet.java b/src/main/java/ui/Wallet.java index 6ca7cb7e1e..bc91716fbf 100644 --- a/src/main/java/ui/Wallet.java +++ b/src/main/java/ui/Wallet.java @@ -1,6 +1,6 @@ package ui; -import java.text.DecimalFormat; +import java.util.HashMap; public class Wallet { private Double balance; @@ -55,11 +55,9 @@ public Boolean isReceiptsEmpty() { /** * Setter for balance property of Wallet Object. * @param input The value to be set as balance - * @return */ - public DecimalFormat setBalance(Double input) { + public void setBalance(Double input) { this.balance = input; - return null; } /** @@ -94,6 +92,14 @@ public double getTotalExpenses() { return this.receipts.getTotalCashSpent(); } + /** + * Getter for the folders property of the ReceiptTracker Object housed in the Wallet Object. + * @return HashMap representing keys and the corresponding ReceiptTracker object + */ + public HashMap getFolders() { + return this.receipts.getFolders(); + } + /** * Accessor for method getReceiptsByDate in ReceiptTracker. * @param date String representing the date to look for diff --git a/src/main/java/ui/gui/CommandLineDisplay.java b/src/main/java/ui/gui/CommandLineDisplay.java new file mode 100644 index 0000000000..b804c62a41 --- /dev/null +++ b/src/main/java/ui/gui/CommandLineDisplay.java @@ -0,0 +1,46 @@ +package ui.gui; + +import javafx.fxml.FXML; +import javafx.scene.control.ScrollPane; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.VBox; +import javafx.scene.text.Text; + +public class CommandLineDisplay { + @FXML + AnchorPane cliDisplayRoot; + @FXML + ScrollPane cliDisplay; + @FXML + VBox container; + + public static final String LINE = "________________________________________________"; + + /** + * Prints string to CLI Display. + * @param outputStr String to be printed onto the display + */ + public void printToDisplay(String outputStr) { + Text newOutput = new Text(outputStr); + container.getChildren().add(newOutput); + } + + public void setStyle() { + this.cliDisplay.vvalueProperty().bind(container.heightProperty()); + } + + /** + * Helper method to indicate duke is saying something. + * @param string The message duke wants to say + */ + public void dukeSays(String string) { + printToDisplay("Duke: " + string + "\n"); + } + + /** + * Helper method to print Line Separator. + */ + public void printSeparator() { + this.printToDisplay(LINE); + } +} diff --git a/src/main/java/ui/gui/DisplayType.java b/src/main/java/ui/gui/DisplayType.java new file mode 100644 index 0000000000..6eb46263c7 --- /dev/null +++ b/src/main/java/ui/gui/DisplayType.java @@ -0,0 +1,5 @@ +package ui.gui; + +public enum DisplayType { + HOME, CLI, NONE; +} diff --git a/src/main/java/ui/gui/DonutChart.java b/src/main/java/ui/gui/DonutChart.java new file mode 100644 index 0000000000..9b1fb201d0 --- /dev/null +++ b/src/main/java/ui/gui/DonutChart.java @@ -0,0 +1,86 @@ +package ui.gui; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.geometry.Bounds; +import javafx.scene.Node; +import javafx.scene.chart.PieChart; +import javafx.scene.layout.Pane; +import javafx.scene.paint.Color; +import javafx.scene.shape.Circle; + +public class DonutChart extends PieChart { + private Circle hole; + + /** + * Constructor for GUI Component DonutChart. + */ + public DonutChart() { + super(FXCollections.observableArrayList()); + this.hole = new Circle(50); + this.hole.setFill(Color.valueOf("#ede7d1")); + } + + private DonutChart(ObservableList pieChartData) { + super(pieChartData); + this.hole = new Circle(50); + this.hole.setFill(Color.valueOf("#ede7d1")); + } + + @Override + protected void layoutChartChildren(double top, double left, double contentWidth, double contentHeight) { + super.layoutChartChildren(top, left, contentWidth, contentHeight); + if (this.getData().size() > 0) { + Node pie = this.getData().get(0).getNode(); + Pane parentChart = (Pane) pie.getParent(); + if (!parentChart.getChildren().contains(hole)) { + matchHoleToChart(); + parentChart.getChildren().add(hole); + } + } + } + + + // @@author {Mudaafi}-reused + // Solution below adapted from: + // https://stackoverflow.com/questions/24121580/can-piechart-from-javafx-be-displayed-as-a-doughnut + private void matchHoleToChart() { + double minX = Double.MAX_VALUE; + double minY = Double.MAX_VALUE; + double maxX = Double.MIN_VALUE; + double maxY = Double.MIN_VALUE; + for (PieChart.Data data: this.getData()) { + Node node = data.getNode(); + Bounds bounds = node.getBoundsInParent(); + if (bounds.getMinX() < minX) { + minX = bounds.getMinX(); + } + if (bounds.getMinY() < minY) { + minY = bounds.getMinY(); + } + if (bounds.getMaxX() > maxX) { + maxX = bounds.getMaxX(); + } + if (bounds.getMaxY() > maxY) { + maxY = bounds.getMaxY(); + } + } + this.hole.setCenterX(minX + (maxX - minX) / 2); + this.hole.setCenterY(minY + (maxY - minY) / 2); + this.hole.setRadius((maxX - minX) / 2.6); + } + + /** + * Creates a DonutChart GUI Component. + * @param budgetedAmt The amount in Gold + * @param expenditure The amount in Red + * @return DonutChart Object + */ + public static DonutChart createBalanceChart(double budgetedAmt, double expenditure) { + ObservableList pieChartData = + FXCollections.observableArrayList( + new PieChart.Data("Budget left", budgetedAmt - expenditure), + new PieChart.Data("Expenses", expenditure)); + return new DonutChart(pieChartData); + } +} diff --git a/src/main/java/ui/gui/HomeWindow.java b/src/main/java/ui/gui/HomeWindow.java new file mode 100644 index 0000000000..0f25c0b342 --- /dev/null +++ b/src/main/java/ui/gui/HomeWindow.java @@ -0,0 +1,196 @@ +package ui.gui; + +import duke.exception.DukeException; +import executor.task.Task; +import executor.task.TaskList; +import interpreter.Interpreter; +import javafx.application.Platform; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.chart.PieChart; +import javafx.scene.chart.StackedBarChart; +import javafx.scene.chart.XYChart; +import javafx.scene.control.Label; +import javafx.scene.control.TextField; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.VBox; +import ui.ReceiptTracker; +import ui.UiCode; +import ui.Wallet; +import utils.AccessType; +import utils.InfoCapsule; + +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +public class HomeWindow extends AnchorPane { + @FXML + private TextField userInput; + @FXML + private DonutChart balanceChart; + @FXML + private Label balanceFigure; + @FXML + private StackedBarChart breakdownChart; + @FXML + private VBox taskContainerLeft; + @FXML + private VBox taskContainerRight; + + private Boolean exitRequest = false; + private Interpreter interpreterLayer; + private ArrayList userInputHistory; + private ObservableList pieChartData; + + void initialize(ArrayList userInputHistory, Interpreter interpreterLayer) { + this.exitRequest = false; + this.userInputHistory = userInputHistory; + this.interpreterLayer = interpreterLayer; + } + + private void extractPieChartData() throws DukeException { + InfoCapsule infoCapsule = this.interpreterLayer.request(AccessType.PIE_CHART_DATA, null); + if (infoCapsule.getUiCode() == UiCode.ERROR) { + throw new DukeException(infoCapsule.getOutputStr()); + } + + this.pieChartData = infoCapsule.getPieChartData(); + } + + void updateBalanceChart() throws DukeException { + InfoCapsule balanceCapsule = this.interpreterLayer.request(AccessType.BALANCE, null); + InfoCapsule expensesCapsule = this.interpreterLayer.request(AccessType.EXPENSES, null); + if (balanceCapsule.getUiCode() == UiCode.ERROR) { + throw new DukeException(balanceCapsule.getOutputStr()); + } + if (expensesCapsule.getUiCode() == UiCode.ERROR) { + throw new DukeException(expensesCapsule.getOutputStr()); + } + this.pieChartData.get(0).setPieValue(expensesCapsule.getOutputDouble()); + this.pieChartData.get(1).setPieValue(balanceCapsule.getOutputDouble() + - expensesCapsule.getOutputDouble()); + DecimalFormat decimalFormat = new DecimalFormat("$#0"); + this.balanceFigure.setText(decimalFormat.format(balanceCapsule.getOutputDouble())); + } + + void displayBalanceChart() throws DukeException { + this.extractPieChartData(); + this.balanceChart.setData(this.pieChartData); + this.balanceChart.setLegendVisible(false); + this.balanceChart.setLabelsVisible(false); + this.balanceChart.setStartAngle(90.0); + String css = this.getClass().getResource("/css/PieChart.css").toExternalForm(); + this.balanceChart.getStylesheets().add(css); + + DecimalFormat decimalFormat = new DecimalFormat("$#0"); + Double walletBalance = getWalletBalance(); + this.balanceFigure.setText(decimalFormat.format(walletBalance)); + } + + private Double getWalletBalance() throws DukeException { + InfoCapsule infoCapsule = this.interpreterLayer.request(AccessType.BALANCE, null); + if (infoCapsule.getUiCode() == UiCode.UPDATE) { + return infoCapsule.getOutputDouble(); + } else if (infoCapsule.getUiCode() == UiCode.ERROR) { + throw new DukeException(infoCapsule.getOutputStr()); + } else { + throw new DukeException("Unable to Access Wallet Balance"); + } + } + + void displayTasks() throws DukeException { + InfoCapsule infoCapsule = this.interpreterLayer.request(AccessType.TASKLIST, null); + if (infoCapsule.getUiCode() == UiCode.ERROR) { + throw new DukeException(infoCapsule.getOutputStr()); + } + this.taskContainerRight.getChildren().clear(); + this.taskContainerLeft.getChildren().clear(); + for (Task task : infoCapsule.getTaskList()) { + int leftListLength = this.taskContainerLeft.getChildren().size(); + int rightListLength = this.taskContainerRight.getChildren().size(); + if (leftListLength > rightListLength) { + this.taskContainerRight.getChildren().add(TaskBox.getNewTaskBox(task)); + } else { + this.taskContainerLeft.getChildren().add(TaskBox.getNewTaskBox(task)); + } + } + } + + void displayBreakdownChart() { + InfoCapsule infoCapsule = this.interpreterLayer.request(AccessType.WALLET, null); + Wallet wallet = infoCapsule.getWallet(); + XYChart.Series expenditureSeries = new XYChart.Series<>(); + expenditureSeries.setName("Expenditure"); + XYChart.Series incomeSeries = new XYChart.Series<>(); + incomeSeries.setName("Income"); + updateBreakdownData(expenditureSeries, incomeSeries); + + XYChart.Series backdrop = new XYChart.Series<>(); + backdrop.setName("Backdrop"); + Double backdropValue = this.getBackdropValue(wallet); + HashMap backdropData = this.getBackdropData(backdropValue, expenditureSeries, incomeSeries); + + for (Map.Entry data : backdropData.entrySet()) { + backdrop.getData().add(new XYChart.Data(data.getKey(), data.getValue())); + } + + this.breakdownChart.getData().add(expenditureSeries); + this.breakdownChart.getData().add(incomeSeries); + this.breakdownChart.getData().add(backdrop); + this.breakdownChart.setScaleY(1.1); + String css = this.getClass().getResource("/css/BreakdownChart.css").toExternalForm(); + this.breakdownChart.getStylesheets().add(css); + } + + private void updateBreakdownData(XYChart.Series expenditureSeries, + XYChart.Series incomeSeries) { + InfoCapsule infoCapsule = this.interpreterLayer.request(AccessType.WALLET, null); + Wallet wallet = infoCapsule.getWallet(); + for (Map.Entry folder : wallet.getFolders().entrySet()) { + if (folder.getValue().getTotalCashSpent() > 0) { + expenditureSeries.getData().add(new XYChart.Data<>( + folder.getKey(), + folder.getValue().getTotalCashSpent()) + ); + } else if (-folder.getValue().getTotalCashSpent() > 0) { + incomeSeries.getData().add(new XYChart.Data<>( + folder.getKey(), + -folder.getValue().getTotalCashSpent()) + ); + } + } + } + + private Double getBackdropValue(Wallet wallet) { + double maxValue = 100.0; + for (ReceiptTracker folderContent : wallet.getFolders().values()) { + if (folderContent.getTotalCashSpent() > maxValue) { + maxValue = folderContent.getTotalCashSpent(); + } else if (-folderContent.getTotalCashSpent() > maxValue) { + maxValue = -folderContent.getTotalCashSpent(); + } + } + return maxValue; + } + + private HashMap getBackdropData(Double backdropValue, + XYChart.Series expenditureSeries, + XYChart.Series incomeSeries) { + HashMap backdropData = new HashMap<>(); + for (XYChart.Data data : expenditureSeries.getData()) { + backdropData.put(data.getXValue(), backdropValue - data.getYValue()); + } + for (XYChart.Data data : incomeSeries.getData()) { + if (backdropData.containsKey(data.getXValue())) { + backdropData.replace(data.getXValue(), backdropData.get(data.getXValue()) - data.getYValue()); + } else { + backdropData.put(data.getXValue(), backdropValue - data.getYValue()); + } + } + return backdropData; + } + +} diff --git a/src/main/java/ui/gui/Launcher.java b/src/main/java/ui/gui/Launcher.java new file mode 100644 index 0000000000..dd8ff794ca --- /dev/null +++ b/src/main/java/ui/gui/Launcher.java @@ -0,0 +1,9 @@ +package ui.gui; + +import javafx.application.Application; + +public class Launcher { + public static void main(String[] args) { + Application.launch(MainGui.class, args); + } +} diff --git a/src/main/java/ui/gui/MainGui.java b/src/main/java/ui/gui/MainGui.java new file mode 100644 index 0000000000..028331fc4e --- /dev/null +++ b/src/main/java/ui/gui/MainGui.java @@ -0,0 +1,48 @@ +package ui.gui; + +import executor.task.TaskList; +import javafx.application.Application; +import javafx.application.Platform; +import javafx.fxml.FXMLLoader; +import javafx.scene.Scene; +import javafx.scene.layout.AnchorPane; +import javafx.stage.Stage; +import main.Duke; +import storage.StorageTask; +import storage.StorageWallet; +import ui.Wallet; + +import java.io.IOException; + +public class MainGui extends Application { + private String taskPath = "savedTask.txt"; + private String walletPath = "savedWallet.txt"; + private MainWindow mainWindowController; + + public void initialize(String[] args) { + Application.launch(MainGui.class, args); + } + + @Override + public void start(Stage stage) { + try { + FXMLLoader loaderMain = new FXMLLoader(MainGui.class + .getResource("/view/MainWindow.fxml")); + AnchorPane anchorPane = loaderMain.load(); + this.mainWindowController = loaderMain.getController(); + this.mainWindowController.initialize(stage, this.taskPath, this.walletPath); + Scene scene = new Scene(anchorPane); + stage.setScene(scene); + stage.setTitle("Duke$$$"); + stage.show(); + + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public void stop() { + this.mainWindowController.saveAllData(); + } +} diff --git a/src/main/java/ui/gui/MainWindow.java b/src/main/java/ui/gui/MainWindow.java new file mode 100644 index 0000000000..12138a38de --- /dev/null +++ b/src/main/java/ui/gui/MainWindow.java @@ -0,0 +1,203 @@ +package ui.gui; + +import duke.exception.DukeException; +import executor.task.TaskList; +import interpreter.Interpreter; +import javafx.application.Platform; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; + +import javafx.scene.chart.XYChart; +import javafx.scene.control.TextField; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.AnchorPane; +import javafx.stage.Stage; +import storage.StorageTask; +import storage.StorageWallet; +import ui.UiCode; +import ui.Wallet; +import utils.InfoCapsule; + +import java.util.ArrayList; + +public class MainWindow extends AnchorPane { + + @FXML + private AnchorPane mainPane; + @FXML + private AnchorPane headerPane; + @FXML + private AnchorPane contentPane; + @FXML + private ImageView headerBackground; + @FXML + private TextField userInput; + + private Boolean exitRequest; + private ArrayList userInputHistory; + private Stage mainStage; + private DisplayType displayType; + private CommandLineDisplay cliController; + private HomeWindow homeController; + private Interpreter interpreterLayer; + + void initialize(Stage stage, String taskPath, String walletPath) { + this.exitRequest = false; + this.mainStage = stage; + this.interpreterLayer = new Interpreter(taskPath, walletPath); + this.displayType = DisplayType.NONE; + this.userInputHistory = new ArrayList<>(); + + this.fetchStoredImages(); + this.showHomeDisplay(); + this.displayToast("test"); + + } + + @FXML + private void handleUserInput() { + String input = this.userInput.getText(); + this.userInputHistory.add(input); + InfoCapsule infoCapsule = this.interpreterLayer.interpret(input); + this.updateGui(infoCapsule); + if (this.displayType == DisplayType.HOME) { + updateHomeDisplay(); + } + this.userInput.clear(); + if (this.exitRequest) { + Platform.exit(); + } + } + + /** + * Set the Graphical User Interface to the Home Display. + */ + private void showHomeDisplay() { + if (this.displayType == DisplayType.HOME) { + return; + } + try { + FXMLLoader loaderHomeDisplay = new FXMLLoader(MainGui.class + .getResource("/view/HomeWindow.fxml")); + AnchorPane homeDisplayRoot = loaderHomeDisplay.load(); + this.homeController = loaderHomeDisplay.getController(); + this.homeController.initialize(this.userInputHistory, this.interpreterLayer); + this.homeController.displayBalanceChart(); + this.homeController.displayBreakdownChart(); + this.homeController.displayTasks(); + this.contentPane.getChildren().add(homeDisplayRoot); + this.displayType = DisplayType.HOME; + } catch (DukeException e) { + this.displayToast(e.getMessage()); + } catch (Exception e) { + e.printStackTrace(); + } + if (this.contentPane.getChildren().size() > 1) { + this.contentPane.getChildren().remove(0); + } + } + + private void updateHomeDisplay() { + try { + this.homeController.updateBalanceChart(); + } catch (DukeException e) { + this.displayToast(e.getMessage()); + } + try { + this.homeController.displayTasks(); + } catch (DukeException e) { + this.displayToast(e.getMessage()); + } + } + + private void updateGui(InfoCapsule infoCapsule) { + UiCode uiCode = infoCapsule.getUiCode(); + try { + switch (uiCode) { + case CLI: + this.showCliDisplay(); + this.printToDisplay(infoCapsule.getOutputStr()); + break; + case TOAST: + this.displayToast(infoCapsule.getOutputStr()); + break; + case ERROR: + infoCapsule.throwError(); + break; + case EXIT: + this.exitRequest = true; + break; + case DISPLAY_HOME: + this.showHomeDisplay(); + break; + case DISPLAY_CLI: + this.showCliDisplay(); + break; + case UPDATE: + break; + default: + } + } catch (DukeException e) { + this.displayToast(e.getMessage()); + } + } + + void saveAllData() { + this.interpreterLayer.requestSave(); + } + + + /** + * Fetches Images stored in application for display in slots for features yet to be developed. + */ + @FXML + private void fetchStoredImages() { + Image headerBackgroundPic = new Image(this.getClass().getResourceAsStream("/images/headerBackground.png")); + this.headerBackground.setImage(headerBackgroundPic); + } + + /** + * Set the Graphical User Interface to the CLI Display. + */ + private void showCliDisplay() { + if (this.displayType == DisplayType.CLI) { + return; + } + try { + FXMLLoader loaderCliDisplay = new FXMLLoader(MainGui.class + .getResource("/view/CommandLineDisplay.fxml")); + AnchorPane cliDisplayRoot = loaderCliDisplay.load(); + this.cliController = loaderCliDisplay.getController(); + this.cliController.setStyle(); + this.contentPane.getChildren().add(cliDisplayRoot); + this.displayType = DisplayType.CLI; + } catch (Exception e) { + System.out.println(e.toString()); + } + if (this.contentPane.getChildren().size() > 1) { + this.contentPane.getChildren().remove(0); + } + } + + private void displayToast(String string) { + Toast.makeText(this.mainStage, string); + } + + private void dukeSays(String string) { + this.showCliDisplay(); + this.cliController.dukeSays(string); + } + + private void printToDisplay(String string) { + this.showCliDisplay(); + this.cliController.printToDisplay(string); + this.printSeparator(); + } + + private void printSeparator() { + this.showCliDisplay(); + this.cliController.printSeparator(); + } + +} diff --git a/src/main/java/ui/gui/TaskBox.java b/src/main/java/ui/gui/TaskBox.java new file mode 100644 index 0000000000..f03675f39c --- /dev/null +++ b/src/main/java/ui/gui/TaskBox.java @@ -0,0 +1,44 @@ +package ui.gui; + +import executor.task.Task; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.geometry.Pos; +import javafx.scene.control.Label; +import javafx.scene.layout.HBox; +import javafx.scene.text.Text; +import javafx.scene.text.TextFlow; + +import java.io.IOException; + +public class TaskBox extends HBox { + @FXML + private TextFlow taskText; + + private TaskBox(String taskTypeStr, String taskDescStr) { + try { + FXMLLoader loader = new FXMLLoader(MainWindow.class.getResource("/view/TaskBox.fxml")); + loader.setController(this); + loader.setRoot(this); + loader.load(); + } catch (IOException e) { + e.printStackTrace(); + } + Text boldText = new Text(taskTypeStr + " "); + boldText.setStyle("-fx-font-weight: bold"); + this.taskText.getChildren().addAll(boldText, new Text(taskDescStr)); + } + + /** + * Creates a new TaskBox GUI Component for a Task Object. + * @param task Task Object to be printed + * @return TaskBox Object + */ + public static TaskBox getNewTaskBox(Task task) { + String taskTypeStr = task.getTaskType().toString(); + String taskDescStr = task.genTaskDesc(); + TaskBox newTaskBox = new TaskBox(taskTypeStr, taskDescStr); + newTaskBox.setAlignment(Pos.CENTER_LEFT); + return newTaskBox; + } +} diff --git a/src/main/java/ui/gui/Toast.java b/src/main/java/ui/gui/Toast.java new file mode 100644 index 0000000000..9f172c46c3 --- /dev/null +++ b/src/main/java/ui/gui/Toast.java @@ -0,0 +1,60 @@ +package ui.gui; + + +import javafx.animation.KeyFrame; +import javafx.animation.KeyValue; +import javafx.animation.Timeline; +import javafx.event.EventHandler; +import javafx.scene.Scene; +import javafx.scene.control.Control; +import javafx.scene.control.Label; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.StackPane; +import javafx.scene.paint.Color; +import javafx.scene.text.Font; +import javafx.scene.text.Text; +import javafx.stage.Popup; +import javafx.stage.Stage; +import javafx.stage.StageStyle; +import javafx.stage.WindowEvent; +import javafx.util.Duration; + +import java.util.ArrayList; + +// @@author {Mudaafi}-reused +// Solution below adapted from: +// https://stackoverflow.com/questions/18669209/javafx-what-is-the-best-way-to-display-a-simple-message +public final class Toast { + private static int TOAST_TIMEOUT = 2000; + private static ArrayList toastedArr = new ArrayList<>(); + + private static Popup createPopup(final String message) { + final Popup popup = new Popup(); + popup.setAutoFix(true); + Label label = new Label(message); + label.getStylesheets().add("/css/mainStyles.css"); + label.getStyleClass().add("popup"); + popup.getContent().add(label); + return popup; + } + + /** + * Creates a popup message for the User on the Graphical User Interface. + * @param stage Stage the popup is to appear on + * @param message String to be printed to the User + */ + public static void makeText(final Stage stage, final String message) { + Popup popup = createPopup(message); + popup.setOnShown(e -> { + popup.setX(stage.getX() + stage.getWidth() / 2 - popup.getWidth() / 2); + popup.setY(stage.getHeight() + (1.5 - toastedArr.size()) * popup.getHeight()); + popup.setWidth(stage.getWidth()); + }); + popup.show(stage); + toastedArr.add(popup.toString()); + new Timeline(new KeyFrame(Duration.millis(TOAST_TIMEOUT), ae -> { + popup.hide(); + toastedArr.remove(popup.toString()); + })).play(); + } +} \ No newline at end of file diff --git a/src/main/java/utils/AccessType.java b/src/main/java/utils/AccessType.java new file mode 100644 index 0000000000..3f8173e79f --- /dev/null +++ b/src/main/java/utils/AccessType.java @@ -0,0 +1,27 @@ +package utils; + +import executor.accessors.*; + +public enum AccessType { + DENY(AccessDeny.class), + BALANCE(AccessWalletBalance.class), + EXPENSES(AccessWalletExpenses.class), + TASKLIST(AccessTaskList.class), + WALLET(AccessWallet.class), + PIE_CHART_DATA(AccessPieChartData.class); + + private final Class accessClass; + + /** + * Constructor for 'AccessType' enum. + */ + private AccessType(Class accessClass) { + this.accessClass = accessClass; + } + + // Setters & Getters + + public Class getAccessClass() { + return accessClass; + } +} diff --git a/src/main/java/utils/InfoCapsule.java b/src/main/java/utils/InfoCapsule.java index 9b34fe21de..2048f75db0 100644 --- a/src/main/java/utils/InfoCapsule.java +++ b/src/main/java/utils/InfoCapsule.java @@ -1,17 +1,26 @@ package utils; import duke.exception.DukeException; +import executor.task.TaskList; +import javafx.collections.ObservableList; +import javafx.scene.chart.PieChart; import ui.UiCode; +import ui.Wallet; public class InfoCapsule { private UiCode uiCode; private String outputStr; + private Double outputDouble; + private ObservableList pieChartData; + private TaskList taskList; + private Wallet wallet; public void throwError() throws DukeException { throw new DukeException(outputStr); } // Setters & Getters + public void setCodeToast() { this.uiCode = UiCode.TOAST; } @@ -32,6 +41,10 @@ public void setCodeExit() { this.uiCode = UiCode.EXIT; } + public void setUiCode(UiCode uiCode) { + this.uiCode = uiCode; + } + public UiCode getUiCode() { return this.uiCode; } @@ -43,4 +56,36 @@ public void setOutputStr(String outputStr) { public String getOutputStr() { return outputStr; } + + public void setOutputDouble(Double outputDouble) { + this.outputDouble = outputDouble; + } + + public Double getOutputDouble() { + return outputDouble; + } + + public void setPieChartData(ObservableList pieChartData) { + this.pieChartData = pieChartData; + } + + public ObservableList getPieChartData() { + return pieChartData; + } + + public void setTaskList(TaskList taskList) { + this.taskList = taskList; + } + + public TaskList getTaskList() { + return taskList; + } + + public void setWallet(Wallet wallet) { + this.wallet = wallet; + } + + public Wallet getWallet() { + return wallet; + } } diff --git a/src/main/resources/css/BreakdownChart.css b/src/main/resources/css/BreakdownChart.css new file mode 100644 index 0000000000..e831cddbbf --- /dev/null +++ b/src/main/resources/css/BreakdownChart.css @@ -0,0 +1,43 @@ +.chart { + -fx-background-insets: 0; + -fx-border-width: 0; +} +.chart-content { + -fx-padding: 0px; +} + +.chart-title { + -fx-text-fill: #4682b4; + -fx-font-size: 0.1em; +} + +.default-color0.chart-bar { -fx-bar-fill: #f2c1bf } +.default-color1.chart-bar { -fx-bar-fill: #f3c774 } +.default-color2.chart-bar { + -fx-bar-fill: #e5e0ca; + } + +.axis { + -fx-tick-label-fill: #264358; + -fx-tick-label-font-size: 1.1em; + -fx-tick-label-font-weight: bold; + -fx-tick-label-font-family: Constantia; +} + +.chart-plot-background { + -fx-background-color: transparent; +} + +.chart-vertical-grid-lines { + -fx-stroke: transparent; +} + +.chart-horizontal-grid-lines { + -fx-stroke: transparent; +} + +.chart-alternative-row-fill { + -fx-fill: transparent; + -fx-stroke: transparent; + -fx-stroke-width: 0; +} diff --git a/src/main/resources/css/PieChart.css b/src/main/resources/css/PieChart.css new file mode 100644 index 0000000000..0d7afbbe62 --- /dev/null +++ b/src/main/resources/css/PieChart.css @@ -0,0 +1,6 @@ +.chart-pie { + -fx-background-insets: 0; + -fx-border-width: 0; +} +.default-color0.chart-pie { -fx-pie-color: #e39a97; } +.default-color1.chart-pie { -fx-pie-color: #f9bd4d; } \ No newline at end of file diff --git a/src/main/resources/css/mainStyles.css b/src/main/resources/css/mainStyles.css new file mode 100644 index 0000000000..1d4edec93a --- /dev/null +++ b/src/main/resources/css/mainStyles.css @@ -0,0 +1,5 @@ +.popup { + -fx-background-color: cornsilk; + -fx-padding: 0; + -fx-font-size: 16; +} \ No newline at end of file diff --git a/src/main/resources/images/balanceChartExample.png b/src/main/resources/images/balanceChartExample.png new file mode 100644 index 0000000000..3302c459e0 Binary files /dev/null and b/src/main/resources/images/balanceChartExample.png differ diff --git a/src/main/resources/images/breakdownExample.png b/src/main/resources/images/breakdownExample.png new file mode 100644 index 0000000000..6ee41be0d3 Binary files /dev/null and b/src/main/resources/images/breakdownExample.png differ diff --git a/src/main/resources/images/headerBackground.png b/src/main/resources/images/headerBackground.png new file mode 100644 index 0000000000..0301b267a9 Binary files /dev/null and b/src/main/resources/images/headerBackground.png differ diff --git a/src/main/resources/view/CommandLineDisplay.fxml b/src/main/resources/view/CommandLineDisplay.fxml new file mode 100644 index 0000000000..e0729d4557 --- /dev/null +++ b/src/main/resources/view/CommandLineDisplay.fxml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/HomeWindow.fxml b/src/main/resources/view/HomeWindow.fxml new file mode 100644 index 0000000000..825edb344f --- /dev/null +++ b/src/main/resources/view/HomeWindow.fxml @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml new file mode 100644 index 0000000000..ed5b9d479c --- /dev/null +++ b/src/main/resources/view/MainWindow.fxml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/TaskBox.fxml b/src/main/resources/view/TaskBox.fxml new file mode 100644 index 0000000000..63b2eee8e1 --- /dev/null +++ b/src/main/resources/view/TaskBox.fxml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/java/CommandAddReceiptTest.java b/src/test/java/CommandAddReceiptTest.java index 63619969a9..dc41b3ee8e 100644 --- a/src/test/java/CommandAddReceiptTest.java +++ b/src/test/java/CommandAddReceiptTest.java @@ -1,3 +1,4 @@ +import com.sun.tools.javac.Main; import executor.command.CommandAddIncomeReceipt; import executor.command.CommandAddSpendingReceipt; import executor.command.CommandType; @@ -5,6 +6,7 @@ import org.junit.jupiter.api.Test; import storage.StorageManager; import ui.Wallet; +import ui.gui.MainWindow; import static org.junit.jupiter.api.Assertions.assertEquals; diff --git a/src/test/java/CommandDisplayBalanceTest.java b/src/test/java/CommandDisplayBalanceTest.java index 4655a14e7a..d65e824b6e 100644 --- a/src/test/java/CommandDisplayBalanceTest.java +++ b/src/test/java/CommandDisplayBalanceTest.java @@ -4,6 +4,7 @@ import org.junit.jupiter.api.Test; import storage.StorageManager; import ui.Wallet; +import ui.gui.MainWindow; import java.io.ByteArrayOutputStream; import java.io.PrintStream; diff --git a/src/test/java/CommandDisplayExpenditureTest.java b/src/test/java/CommandDisplayExpenditureTest.java index 834262efbc..384b0a88e4 100644 --- a/src/test/java/CommandDisplayExpenditureTest.java +++ b/src/test/java/CommandDisplayExpenditureTest.java @@ -4,6 +4,7 @@ import storage.StorageManager; import ui.Receipt; import ui.Wallet; +import ui.gui.MainWindow; import java.io.ByteArrayOutputStream; import java.io.PrintStream; diff --git a/src/test/java/CommandMarkDoneTest.java b/src/test/java/CommandMarkDoneTest.java index 4dbb65b40b..42909b86f3 100644 --- a/src/test/java/CommandMarkDoneTest.java +++ b/src/test/java/CommandMarkDoneTest.java @@ -5,7 +5,6 @@ import executor.task.TaskType; import org.junit.jupiter.api.Test; import storage.StorageManager; - import static org.junit.jupiter.api.Assertions.assertEquals; class CommandMarkDoneTest { diff --git a/src/test/java/StorageTaskTest.java b/src/test/java/StorageTaskTest.java index e0de4ee66f..867e9d64b6 100644 --- a/src/test/java/StorageTaskTest.java +++ b/src/test/java/StorageTaskTest.java @@ -17,13 +17,12 @@ public class StorageTaskTest { @Test void loadData() { StorageTask storagetask = new StorageTask("testDataLoad.txt"); - TaskList taskListResult = null; + TaskList taskListResult = new TaskList(); try { - taskListResult = storagetask.loadData(); + storagetask.loadData(taskListResult); } catch (DukeException e) { System.out.println(e.getMessage()); } - assert taskListResult != null; final SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy HHmm"); //TODO yoda things/ tmrw at last@|@false Task taskResult = taskListResult.get(0); @@ -75,14 +74,17 @@ void saveData() { // Follow the Storage Format when inputting new test cases StorageTask storageExpected = new StorageTask("testDataLoad.txt"); StorageTask storageSaved = new StorageTask("testDataSave.txt"); - TaskList taskList = null; + TaskList taskList = new TaskList(); try { - taskList = storageExpected.loadData(); + storageExpected.loadData(taskList); + } catch (DukeException e) { + System.out.println(e.getMessage()); + } + try { + storageSaved.saveData(taskList); } catch (DukeException e) { System.out.println(e.getMessage()); } - assert taskList != null; - storageSaved.saveData(taskList); File fileExpected = new File("testDataLoad.txt"); File fileSaved = new File("testDataSave.txt"); try { @@ -92,7 +94,7 @@ void saveData() { assertEquals(scannerExpected.nextLine(), scannerSaved.nextLine()); } } catch (Exception e) { - System.out.println(e); + System.out.println(e.toString()); } } } diff --git a/src/test/java/StorageWalletTest.java b/src/test/java/StorageWalletTest.java index bea36cbbcc..688f54b405 100644 --- a/src/test/java/StorageWalletTest.java +++ b/src/test/java/StorageWalletTest.java @@ -1,13 +1,9 @@ import duke.exception.DukeException; +import storage.StorageWallet; import ui.Wallet; -import ui.ReceiptTracker; import ui.Receipt; -import storage.StorageWallet; import org.junit.jupiter.api.Test; -import java.io.File; -import java.text.SimpleDateFormat; -import java.util.Scanner; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -16,13 +12,13 @@ public class StorageWalletTest { @Test void loadData() { StorageWallet storageWallet = new StorageWallet("testWalletDataLoad.txt"); - Wallet wallet = null; + Wallet wallet = new Wallet(); try { - wallet = storageWallet.loadData(); + storageWallet.loadData(wallet); } catch (DukeException e) { System.out.println(e.getMessage()); } - assert wallet != null; + //In 5.00 /date 2019-01-29 /tags street Receipt firstReceipt = wallet.getReceipts().get(0); assertEquals(-5.00, firstReceipt.getCashSpent(), @@ -70,14 +66,17 @@ void saveData() { // Follow the Storage Format when inputting new test cases StorageWallet storageExpected = new StorageWallet("testWalletDataLoad.txt"); StorageWallet storageSaved = new StorageWallet("testWalletDataSave.txt"); - Wallet wallet = null; + Wallet wallet = new Wallet(); try { - wallet = storageExpected.loadData(); + storageExpected.loadData(wallet); } catch (DukeException e) { System.out.println(e.getMessage()); } - assert wallet != null; // Check file content manually, as input may differ from standard format (Input: $5 Saved: $5.00) - storageSaved.saveData(wallet); + try { + storageSaved.saveData(wallet); + } catch (DukeException e) { + System.out.println(e.toString()); + } } } diff --git a/src/test/java/ui/MainGuiTest.java b/src/test/java/ui/MainGuiTest.java new file mode 100644 index 0000000000..bcd55b0018 --- /dev/null +++ b/src/test/java/ui/MainGuiTest.java @@ -0,0 +1,22 @@ +package ui; + +import javafx.application.Application; +import ui.gui.MainGui; +import ui.gui.MainWindow; + +public class MainGuiTest extends MainGui { + private String taskPath = "savedTask.txt"; + private String walletPath = "savedWallet.txt"; + private MainWindow mainWindowController; + + public void initialize(String taskPath, String walletPath) { + String[] args = {}; + this.taskPath = taskPath; + this.walletPath = walletPath; + Application.launch(MainGui.class); + } + + public MainWindow getMainWindowController() { + return mainWindowController; + } +} diff --git a/testWalletDataSave.txt b/testWalletDataSave.txt index f1905608c5..3f1a88a615 100644 --- a/testWalletDataSave.txt +++ b/testWalletDataSave.txt @@ -1,4 +1,4 @@ -in $5.0 /date 2019-08-30 /tags street +0.1 out $10.0 /date 2001-08-25 /tags out $15.0 /date 2019-12-10 /tags dogfood puppy monthly in $3.0 /date 2018-03-14 /tags refund clothes