diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000000..66da650660 --- /dev/null +++ b/build.gradle @@ -0,0 +1,55 @@ +plugins { + id 'java' + id 'application' + id 'checkstyle' + id 'com.github.johnrengelman.shadow' version '5.1.0' +} + +checkstyle { + toolVersion = '8.23' +} + +group 'seedu.duke' +version '0.1.0' + +repositories { + mavenCentral() +} + +application { + mainClassName = "Duke" +} + +shadowJar { + archiveBaseName = "duke" + archiveVersion = "0.1.3" + archiveClassifier = null + archiveAppendix = null +} + +dependencies { + testImplementation 'org.junit.jupiter:junit-jupiter:5.5.0' + + String javaFxVersion = '11' + + implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'linux' + implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'linux' + implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'linux' + implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'linux' +} + +test { + useJUnitPlatform() +} + +run { + standardInput = System.in +} \ No newline at end of file diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml new file mode 100644 index 0000000000..b1a57ba6c0 --- /dev/null +++ b/config/checkstyle/checkstyle.xml @@ -0,0 +1,257 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/task_data.txt b/data/task_data.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000..87b738cbd0 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..0e7e4a6d1e --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Thu Jan 30 23:59:49 SGT 2020 +distributionUrl=https\://services.gradle.org/distributions/gradle-5.5.1-all.zip +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew new file mode 100755 index 0000000000..af6708ff22 --- /dev/null +++ b/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000000..6d57edc706 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000000..d1e92fe5db --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'duke' diff --git a/src/main/java/Deadline.java b/src/main/java/Deadline.java new file mode 100644 index 0000000000..a359fb969f --- /dev/null +++ b/src/main/java/Deadline.java @@ -0,0 +1,22 @@ +/** + * Represents a deadline task. A Deadline object corresponds to a + * deadline task represented by task description and date-time information. + */ + +public class Deadline extends Task { + protected String dayTime; + + public Deadline(String description, String dayTime) { + super(description); + this.dayTime = Parser.parseTime(dayTime); + } + + /** + * Returns deadline day (and time). + * + * @return Day and time. + */ + public String getDayTime() { + return dayTime; + } +} diff --git a/src/main/java/DialogBox.java b/src/main/java/DialogBox.java new file mode 100644 index 0000000000..a9023ad24c --- /dev/null +++ b/src/main/java/DialogBox.java @@ -0,0 +1,60 @@ +import javafx.geometry.Pos; +import javafx.scene.control.Label; +import javafx.scene.image.ImageView; +import javafx.scene.layout.HBox; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.scene.Node; +import java.io.IOException; +import java.util.Collections; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.image.Image; + + +/** + * An example of a custom control using FXML. + * This control represents a dialog box consisting of an ImageView to represent the speaker's face and a label + * containing text from the speaker. + */ +public class DialogBox extends HBox { + @FXML + private Label dialog; + @FXML + private ImageView displayPicture; + + private DialogBox(String text, Image img) { + try { + FXMLLoader fxmlLoader = new FXMLLoader(MainWindow.class.getResource("/view/DialogBox.fxml")); + fxmlLoader.setController(this); + fxmlLoader.setRoot(this); + fxmlLoader.load(); + } catch (IOException e) { + e.printStackTrace(); + } + + dialog.setText(text); + displayPicture.setImage(img); + } + + /** + * Flips the dialog box such that the ImageView is on the left and text on the right. + */ + + private void flip() { + ObservableList tmp = FXCollections.observableArrayList(this.getChildren()); + Collections.reverse(tmp); + getChildren().setAll(tmp); + setAlignment(Pos.CENTER_LEFT); + } + + public static DialogBox getUserDialog(String text, Image img) { + return new DialogBox(text, img); + } + + public static DialogBox getDukeDialog(String text, Image img) { + var db = new DialogBox(text, img); + db.flip(); + return db; + } +} \ No newline at end of file diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java index 5d313334cc..38209373bb 100644 --- a/src/main/java/Duke.java +++ b/src/main/java/Duke.java @@ -1,10 +1,19 @@ public class Duke { - public static void main(String[] args) { - String logo = " ____ _ \n" - + "| _ \\ _ _| | _____ \n" - + "| | | | | | | |/ / _ \\\n" - + "| |_| | |_| | < __/\n" - + "|____/ \\__,_|_|\\_\\___|\n"; - System.out.println("Hello from\n" + logo); + static private Ui ui; + static private Parser parser; + + public static void main(String[] args) {} + + protected void linkResources(Ui ui, Parser parser) { + this.ui = ui; + this.parser = parser; } -} + + protected String getResponse(String input) { + try { + return parser.parseInput(input); + } catch (Exception ex) { + return ui.showError(ex); + } + } +} \ No newline at end of file diff --git a/src/main/java/DukeException.java b/src/main/java/DukeException.java new file mode 100644 index 0000000000..b1f2b78953 --- /dev/null +++ b/src/main/java/DukeException.java @@ -0,0 +1,10 @@ +public class DukeException extends Exception { + public DukeException() { + super(); + } + + @Override + public String getMessage(){ + return "\u2639 OOPS!!! I'm sorry, but I don't recognise that command :-("; + } +} diff --git a/src/main/java/EmptyDescriptionException.java b/src/main/java/EmptyDescriptionException.java new file mode 100644 index 0000000000..024033354c --- /dev/null +++ b/src/main/java/EmptyDescriptionException.java @@ -0,0 +1,10 @@ +public class EmptyDescriptionException extends DukeException { + public EmptyDescriptionException() { + super(); + } + + @Override + public String getMessage(){ + return "\u2639 OOPS!!! The description cannot be empty."; + } +} diff --git a/src/main/java/Event.java b/src/main/java/Event.java new file mode 100644 index 0000000000..e3b0728450 --- /dev/null +++ b/src/main/java/Event.java @@ -0,0 +1,22 @@ +/** + * Represents a event task. An Event object corresponds to an + * event task represented by task description and date-time information. + */ + +public class Event extends Task { + protected String dayTime; + + public Event(String description, String dayTime) { + super(description); + this.dayTime = Parser.parseTime(dayTime); + } + + /** + * Returns event day (and time). + * + * @return Day and time. + */ + public String getDayTime() { + return dayTime; + } +} diff --git a/src/main/java/Launcher.java b/src/main/java/Launcher.java new file mode 100644 index 0000000000..11dbf00c62 --- /dev/null +++ b/src/main/java/Launcher.java @@ -0,0 +1,10 @@ +import javafx.application.Application; + +/** + * A launcher class to workaround classpath issues. + */ +public class Launcher { + public static void main(String[] args) { + Application.launch(Main.class, args); + } +} \ No newline at end of file diff --git a/src/main/java/Main.java b/src/main/java/Main.java new file mode 100644 index 0000000000..6c722b613f --- /dev/null +++ b/src/main/java/Main.java @@ -0,0 +1,47 @@ +import java.io.IOException; +import java.nio.file.Paths; + +import javafx.application.Application; +import javafx.fxml.FXMLLoader; +import javafx.scene.Scene; +import javafx.scene.layout.AnchorPane; +import javafx.stage.Stage; + +/** + * A GUI for Duke using FXML. + */ +public class Main extends Application { + static private Ui ui; + static private Storage storage; + static private TaskList taskList; + static private Parser parser; + static String dataFilePath = Paths.get(Paths.get(System.getProperty("user.dir")).toString(), + "data/task_data.txt").toString(); + + private Duke duke = new Duke(); + + @Override + public void start(Stage stage) { + ui = new Ui(); + storage = new Storage(dataFilePath); + try { + taskList.setList(storage.getList()); + } catch (Exception e) { + taskList = new TaskList(ui, storage); + } + parser = new Parser(taskList); + duke.linkResources(ui, parser); + + try { + FXMLLoader fxmlLoader = new FXMLLoader(Main.class.getResource("/view/MainWindow.fxml")); + AnchorPane ap = fxmlLoader.load(); + Scene scene = new Scene(ap); + stage.setScene(scene); + fxmlLoader.getController().setDuke(duke); + stage.show(); + fxmlLoader.getController().showWelcomeMessage(); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/MainWindow.java b/src/main/java/MainWindow.java new file mode 100644 index 0000000000..126f8d0a5e --- /dev/null +++ b/src/main/java/MainWindow.java @@ -0,0 +1,65 @@ +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.TextField; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.VBox; +/** + * Controller for MainWindow. Provides the layout for the other controls. + */ +public class MainWindow extends AnchorPane { + @FXML + private ScrollPane scrollPane; + @FXML + private VBox dialogContainer; + @FXML + private TextField userInput; + @FXML + private Button sendButton; + + private Duke duke; + + private Image userImage = new Image(this.getClass().getResourceAsStream("/images/happyman.jpg")); + private Image dukeImage = new Image(this.getClass().getResourceAsStream("/images/happyduke.jpeg")); + + @FXML + public void initialize() { + scrollPane.vvalueProperty().bind(dialogContainer.heightProperty()); + } + + public void setDuke(Duke d) { + duke = d; + } + + /** + * Creates two dialog boxes, one echoing user input and the other containing Duke's reply and then appends them to + * the dialog container. Clears the user input after processing. + */ + @FXML + private void handleUserInput() { + String input = userInput.getText(); + String response = duke.getResponse(input); + dialogContainer.getChildren().addAll( + DialogBox.getUserDialog(input, userImage), + DialogBox.getDukeDialog(response, dukeImage) + ); + userInput.clear(); + } + + @FXML + protected void showWelcomeMessage() { + String logo = " ____ _ \n" + + "| _ \\ _ _| | _____ \n" + + "| | | | | | | |/ / _ \\\n" + + "| |_| | |_| | < __/\n" + + "|____/ \\__,_|_|\\_\\___|\n"; + String msg = "Hello from\n" + logo + "\n" + + "NOTE: For all date/time input, please use the DD-MM-YYYY HH:MM format.\n" + + "Hello! I'm Duke the dude.\n" + + "How can I serve you?"; + dialogContainer.getChildren().add(DialogBox.getDukeDialog(msg, dukeImage)); + } +} \ No newline at end of file diff --git a/src/main/java/Parser.java b/src/main/java/Parser.java new file mode 100644 index 0000000000..6501d85fe5 --- /dev/null +++ b/src/main/java/Parser.java @@ -0,0 +1,70 @@ +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.FormatStyle; + +/** + * Parses user input and translates it into action. A Parser object is associated + * with a TaskList object on which the action is applied. + */ +public class Parser { + private TaskList taskList; + + public Parser(TaskList taskList) { this.taskList = taskList; } + + /** + * Evaluates user input using first word (instruction). + * Adds or removes tasks from task list according to instruction details. + * + * @param input User string input. + * @return Display message. + * @throws DukeException If first word of input (instruction) is unrecognised. + */ + public String parseInput(String input) throws DukeException { + String msg = ""; + String[] inputParts = input.split(" "); + String command = inputParts[0]; + if (input.equals("list")) { + msg = taskList.showList(); + } else if (command.equals("done")) { + int taskNum = Integer.parseInt(inputParts[1]); + msg = taskList.done(taskNum); + } else if (command.equals("todo") || + command.equals("event") || + command.equals("deadline")) { + msg = taskList.add(input); + } else if (command.equals("delete")) { + int taskNum = Integer.parseInt(inputParts[1]); + msg = taskList.delete(taskNum); + } else if (command.equals("find")) { + String query = inputParts[1]; + msg = taskList.find(query); + } else if (command.equals("bye")) { + msg = "Bye. Hope to serve you again soon!"; + } else { + throw new UnrecognisedCommandException(); + } + assert ! msg.equals("") : "Response is not empty"; + return msg; + } + + /** + * Formats date and time from user input and returns formatted string. + * + * @param dayTime User input string. + * @return Formatted date-and-time string. + */ + public static String parseTime(String dayTime) { + String s; + if (dayTime.split(" ").length >= 2) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm"); + LocalDateTime dt = LocalDateTime.parse(dayTime, formatter); + s = dt.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM, FormatStyle.SHORT)); + } else { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy"); + LocalDate d = LocalDate.parse(dayTime, formatter); + s = d.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM)); + } + return s; + } +} diff --git a/src/main/java/Storage.java b/src/main/java/Storage.java new file mode 100644 index 0000000000..b448d40ec0 --- /dev/null +++ b/src/main/java/Storage.java @@ -0,0 +1,78 @@ +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Scanner; + +/** + * Represents a collection of methods to read and write files in the data folder. + * A Storage object corresponds to a file path represented as a String + * and a File object that accesses the file at the path. + */ +public class Storage { + private String filePath; + private File f; + + public Storage(String filePath) { + this.filePath = filePath; + f = new File(filePath); + } + + /** + * Retrieves and returns list of tasks from data folder. + * + * @return List of tasks. + * @throws FileNotFoundException If storage file cannot be found in data folder. + */ + public List getList() throws FileNotFoundException { + List list = new ArrayList<>(); + Scanner sc = new Scanner(f); + while (sc.hasNext()) { + String str = sc.nextLine(); + String[] elements = str.split(" \\| "); + Task t = new Task(); + switch (elements[0]) { + case "T": + t = new Todo(elements[2]); + break; + case "E": + t = new Event(elements[2], elements[3]); + break; + case "D": + t = new Deadline(elements[2], elements[3]); + } + if (Integer.parseInt(elements[1]) == 1) + t.markAsDone(); + list.add(t); + } + return list; + } + + /** + * Updates storage file in storage folder with list of tasks. + * + * @param list List of tasks. + * @throws IOException If there is an error with writing to storage file. + */ + public void writeTaskList(List list) throws IOException { + FileWriter fw = new FileWriter(filePath); + for (Task t : list) + fw.write(taskToString(t)); + fw.close(); + } + + private static String taskToString(Task t) { + String str = ""; + int done = t.getIsDone() ? 1 : 0; + if (t instanceof Todo) + str = "T | " + done + " | " + t.getDescription(); + if (t instanceof Event) + str = "E | " + done + " | " + t.getDescription() + " | " + ((Event) t).getDayTime(); + if (t instanceof Deadline) + str = "D | " + done + " | " + t.getDescription() + " | " + ((Deadline) t).getDayTime(); + assert ! str.equals("") : "String has information"; + return str + "\n"; + } +} diff --git a/src/main/java/Task.java b/src/main/java/Task.java new file mode 100644 index 0000000000..3622483a8a --- /dev/null +++ b/src/main/java/Task.java @@ -0,0 +1,58 @@ +/** + * Represents a task. A Task object corresponds to a generic task represented by + * task description and a boolean flag identifying if the task has been completed. + */ +public class Task { + protected String description; + protected boolean isDone; + + public Task() {} + + public Task(String description) { + this.description = description; + this.isDone = false; + } + + /** + * Returns status of task as an icon, represented in a string. + * + * @return Status of task as a string. + */ + public String getStatusIcon() { + return (isDone ? "OK!" : " X "); + } + + /** + * Returns task description. + * + * @return Task description. + */ + public String getDescription() { return description; } + + /** + * Returns status of task. + * + * @return Task status. + */ + public boolean getIsDone() { return isDone; } + + /** + * Change status of task to done. + */ + public void markAsDone() { + isDone = true; + } + + @Override + public String toString() { + String toReturn = ""; + if (this instanceof Todo) { + toReturn = "[T][" + getStatusIcon() + "] " + description; + } else if (this instanceof Event) { + toReturn = "[E][" + getStatusIcon() + "] " + description + " (at: " + ((Event) this).getDayTime() + ")"; + } else if (this instanceof Deadline) { + toReturn = "[D][" + getStatusIcon() + "] " + description + " (by: " + ((Deadline) this).getDayTime() + ")"; + } + return toReturn; + } +} \ No newline at end of file diff --git a/src/main/java/TaskList.java b/src/main/java/TaskList.java new file mode 100644 index 0000000000..dffa9ddf58 --- /dev/null +++ b/src/main/java/TaskList.java @@ -0,0 +1,142 @@ +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * Represents a list of tasks. A TaskList object corresponds to a + * list of Task objects which it represents, a Ui object + * to facilitate user input and output and a Storage object to access + * storage file in the data folder. + */ +public class TaskList { + private Ui ui; + private Storage storage; + private List taskList; + + public TaskList(Ui ui, Storage storage) { + this.ui = ui; + this.storage = storage; + taskList = new ArrayList<>(); + } + + /** + * Sets task list. + * + * @param list Task list. + */ + public void setList(List list) { taskList = list; } + + /** + * Adds task described in user input to task list. + * + * @param s Task to add. + * @return Display message. + * @throws DukeException If string format is invalid. + */ + public String add(String s) throws DukeException { + String[] taskParts = s.split(" ", 2); + String taskType = taskParts[0]; + Task toAdd = new Task(); + if (taskType.equals("todo")) { + try { + toAdd = new Todo(taskParts[1]); + } catch (ArrayIndexOutOfBoundsException e) { + throw new EmptyDescriptionException(); + } + } else if (taskType.equals("event")) { + try { + String[] taskInfo = taskParts[1].split(" /at "); + toAdd = new Event(taskInfo[0], taskInfo[1]); + } catch (ArrayIndexOutOfBoundsException e) { + throw new EmptyDescriptionException(); + } + } else if (taskType.equals("deadline")) { + try { + String[] taskInfo = taskParts[1].split(" /by "); + toAdd = new Deadline(taskInfo[0], taskInfo[1]); + } catch (ArrayIndexOutOfBoundsException e) { + throw new EmptyDescriptionException(); + } + } + taskList.add(toAdd); + String msg = "Gotcha! Added this task:\n" + + " " + toAdd + "\n" + + "Now you have " + taskList.size() + " tasks in the list."; + try { + storage.writeTaskList(taskList); + } catch (IOException e) { + msg = ui.showError(e); + } + return msg; + } + + /** + * Deletes task at index i (starts from 1) from task list. + * + * @param i Index of task to remove. + * @return Display message. + */ + public String delete(int i) { + Task t = taskList.remove(i-1); + String msg = "Poof! This task is gone:\n" + + " " + t + "\n" + + "Now you have " + taskList.size() + " tasks in the list."; + try { + storage.writeTaskList(taskList); + } catch (IOException e) { + msg = ui.showError(e); + } + return msg; + } + + /** + * Displays tasks in task list. + * + * @return Display message. + */ + public String showList() { + String msg = "Here are the tasks in your list:"; + int count = 1; + for (Task t : taskList) { + msg += "\n" + count + "." + t; + count++; + } + return msg; + } + + /** + * Finds and displays tasks that contain the query string. + * + * @param query The query string. + * @return Display message. + */ + public String find(String query) { + String msg = "Here are the matching tasks in your list:"; + int count = 1; + for (Task t : taskList) { + if (t.toString().contains(query)) { + msg += "\n" + count + "." + t; + count++; + } + } + return msg; + } + + /** + * Marks task at index i (starts at 1) in task list as done. + * + * @param i Index of task to mark as done. + * @return Display message. + */ + public String done(int i) { + taskList.get(i-1).markAsDone(); + String msg = "Nice! I've marked this task as done: \n" + + " " + taskList.get(i-1); + try { + storage.writeTaskList(taskList); + } catch (IOException e) { + msg = ui.showError(e); + } + return msg; + } +} diff --git a/src/main/java/Todo.java b/src/main/java/Todo.java new file mode 100644 index 0000000000..1ade1983b9 --- /dev/null +++ b/src/main/java/Todo.java @@ -0,0 +1,10 @@ +/** + * Represents a todo task. A Todo object corresponds to a + * todo task represented by task description. + */ + +public class Todo extends Task { + public Todo(String description) { + super(description); + } +} diff --git a/src/main/java/Ui.java b/src/main/java/Ui.java new file mode 100644 index 0000000000..e958222b33 --- /dev/null +++ b/src/main/java/Ui.java @@ -0,0 +1,15 @@ +/** + * Represents an interface to facilitate user input and output. + */ +public class Ui { + public Ui() {} + /** + * Prints error message. + * + * @param e Exception to print. + * @return Display message. + */ + public String showError(Exception e) { + return "Something went wrong: " + e.getMessage(); + } +} diff --git a/src/main/java/UnrecognisedCommandException.java b/src/main/java/UnrecognisedCommandException.java new file mode 100644 index 0000000000..2b98efd410 --- /dev/null +++ b/src/main/java/UnrecognisedCommandException.java @@ -0,0 +1,10 @@ +public class UnrecognisedCommandException extends DukeException { + public UnrecognisedCommandException() { + super(); + } + + @Override + public String getMessage(){ + return "\u2639 OOPS!!! I'm sorry, but I don't know what that means :-("; + } +} diff --git a/src/main/resources/images/happyduke.jpeg b/src/main/resources/images/happyduke.jpeg new file mode 100644 index 0000000000..2f239893e8 Binary files /dev/null and b/src/main/resources/images/happyduke.jpeg differ diff --git a/src/main/resources/images/happyman.jpg b/src/main/resources/images/happyman.jpg new file mode 100644 index 0000000000..d176e0e78f Binary files /dev/null and b/src/main/resources/images/happyman.jpg differ diff --git a/src/main/resources/view/DialogBox.fxml b/src/main/resources/view/DialogBox.fxml new file mode 100644 index 0000000000..ede775d4f9 --- /dev/null +++ b/src/main/resources/view/DialogBox.fxml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml new file mode 100644 index 0000000000..435f3ce0d8 --- /dev/null +++ b/src/main/resources/view/MainWindow.fxml @@ -0,0 +1,19 @@ + + + + + + + + + + + +