diff --git a/README.adoc b/README.adoc index 03eff3a4d191..ffeacbe7c328 100644 --- a/README.adoc +++ b/README.adoc @@ -1,11 +1,10 @@ -= Address Book (Level 4) += ProgressChecker ifdef::env-github,env-browser[:relfileprefix: docs/] -https://travis-ci.org/se-edu/addressbook-level4[image:https://travis-ci.org/se-edu/addressbook-level4.svg?branch=master[Build Status]] -https://ci.appveyor.com/project/damithc/addressbook-level4[image:https://ci.appveyor.com/api/projects/status/3boko2x2vr5cc3w2?svg=true[Build status]] -https://coveralls.io/github/se-edu/addressbook-level4?branch=master[image:https://coveralls.io/repos/github/se-edu/addressbook-level4/badge.svg?branch=master[Coverage Status]] -https://www.codacy.com/app/damith/addressbook-level4?utm_source=github.com&utm_medium=referral&utm_content=se-edu/addressbook-level4&utm_campaign=Badge_Grade[image:https://api.codacy.com/project/badge/Grade/fc0b7775cf7f4fdeaf08776f3d8e364a[Codacy Badge]] -https://gitter.im/se-edu/Lobby[image:https://badges.gitter.im/se-edu/Lobby.svg[Gitter chat]] +https://travis-ci.org/CS2103JAN2018-T09-B3/main[image:https://travis-ci.org/CS2103JAN2018-T09-B3/main.svg?branch=master[Build Status]] +https://coveralls.io/github/CS2103JAN2018-T09-B3/main?branch=master[image:https://coveralls.io/repos/github/CS2103JAN2018-T09-B3/main/badge.svg?branch=master[Coverage Status]] + +Missed a learning outcome because it was so deeply nested in the pool of information? Can't find the information you need from just a simple click? We know how that feels. With our Progress Checker, you'll never experience these issues in CS2103/T again. ifdef::env-github[] image::docs/images/Ui.png[width="600"] @@ -15,19 +14,14 @@ ifndef::env-github[] image::images/Ui.png[width="600"] endif::[] -* This is a desktop Address Book application. It has a GUI but most of the user interactions happen using a CLI (Command Line Interface). -* It is a Java sample application intended for students learning Software Engineering while using Java as the main programming language. -* It is *written in OOP fashion*. It provides a *reasonably well-written* code example that is *significantly bigger* (around 6 KLoC)than what students usually write in beginner-level SE modules. -* What's different from https://github.com/se-edu/addressbook-level3[level 3]: -** A more sophisticated GUI that includes a list panel and an in-built Browser. -** More test cases, including automated GUI testing. -** Support for _Build Automation_ using Gradle and for _Continuous Integration_ using Travis CI. +* This is a desktop Progress Checker application. It has a GUI but most of the user interactions happen using a CLI (Command Line Interface). +* It is a Java sample application intended for students taking module CS2103/T as a means of tracking both their learning outcomes and practice exercises. +* It is meant to be a helper software used together with the https://nus-cs2103-ay1718s2.github.io/website/index.html[official CS2103/T module website]. == Site Map * <> * <> -* <> * <> * <> @@ -35,6 +29,7 @@ endif::[] * Some parts of this sample application were inspired by the excellent http://code.makery.ch/library/javafx-8-tutorial/[Java FX tutorial] by _Marco Jakob_. -* Libraries used: https://github.com/TomasMikula/EasyBind[EasyBind], https://github.com/TestFX/TestFX[TextFX], https://bitbucket.org/controlsfx/controlsfx/[ControlsFX], https://github.com/FasterXML/jackson[Jackson], https://github.com/google/guava[Guava], https://github.com/junit-team/junit4[JUnit4] +* Libraries used: https://github.com/TomasMikula/EasyBind[EasyBind], https://github.com/TestFX/TestFX[TextFX], https://bitbucket.org/controlsfx/controlsfx/[ControlsFX], https://github.com/FasterXML/jackson[Jackson], https://github.com/google/guava[Guava], https://github.com/junit-team/junit4[JUnit4], https://developers.google.com/google-apps/tasks[Tasks] +* This project was built off from AddressBook-Level4 project created by https://github.com/se-edu/[SE-EDU] initiative. == Licence : link:LICENSE[MIT] diff --git a/build.gradle b/build.gradle index 50cd2ae52efc..511a26df78bf 100644 --- a/build.gradle +++ b/build.gradle @@ -16,7 +16,7 @@ plugins { } // Specifies the entry point of the application -mainClassName = 'seedu.address.MainApp' +mainClassName = 'seedu.progresschecker.MainApp' sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 @@ -46,6 +46,15 @@ dependencies { compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.7.0' compile group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: '2.7.4' compile group: 'com.google.guava', name: 'guava', version: '19.0' + compile ( + ['com.google.api-client:google-api-client:1.23.0'], + ['com.google.apis:google-api-services-tasks:v1-rev49-1.23.0'], + ['com.google.oauth-client:google-oauth-client-jetty:1.23.0'], + ) + + // https://mvnrepository.com/artifact/org.kohsuke/github-api + compile group: 'org.kohsuke', name: 'github-api', version: '1.92' + testCompile group: 'junit', name: 'junit', version: '4.12' testCompile group: 'org.testfx', name: 'testfx-core', version: testFxVersion @@ -57,13 +66,13 @@ dependencies { } shadowJar { - archiveName = "addressbook.jar" + archiveName = "progresschecker.jar" destinationDir = file("${buildDir}/jar/") } task wrapper(type: Wrapper) { - gradleVersion = '2.12' + gradleVersion = '4.6' } task coverage(type: JacocoReport) { @@ -132,16 +141,16 @@ test { } if (runNonGuiTests) { - test.include 'seedu/address/**' + test.include 'seedu/progresschecker/**' } if (runGuiTests) { test.include 'systemtests/**' - test.include 'seedu/address/ui/**' + test.include 'seedu/progresschecker/ui/**' } if (!runGuiTests) { - test.exclude 'seedu/address/ui/**' + test.exclude 'seedu/progresschecker/ui/**' } } } diff --git a/collated/functional/EdwardKSG.md b/collated/functional/EdwardKSG.md new file mode 100644 index 000000000000..dbb7138d8270 --- /dev/null +++ b/collated/functional/EdwardKSG.md @@ -0,0 +1,2261 @@ +# EdwardKSG +###### \java\seedu\progresschecker\commons\events\ui\LoadBarEvent.java +``` java +/** + * Represents a page change in the second Browser Panel which shows the progress bar. + */ +public class LoadBarEvent extends BaseEvent { + + + public final String content; + + public LoadBarEvent(String content) { + this.content = content; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + + public String getContent() { + return content; + } + +} +``` +###### \java\seedu\progresschecker\commons\events\ui\LoadTaskEvent.java +``` java +/** + * Represents a page change in the Browser Panel + */ +public class LoadTaskEvent extends BaseEvent { + + + public final String content; + + public LoadTaskEvent(String content) { + this.content = content; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + + public String getContent() { + return content; + } + +} +``` +###### \java\seedu\progresschecker\commons\events\ui\LoadUrlEvent.java +``` java +/** + * Represents a page change in the Browser Panel + */ +public class LoadUrlEvent extends BaseEvent { + + + public final String url; + + public LoadUrlEvent(String url) { + this.url = url; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + + public String getUrl() { + return url; + } + +} +``` +###### \java\seedu\progresschecker\logic\apisetup\ConnectTasksApi.java +``` java +/** + * Sets up Google Tasks API. + * i. Authorizes data access based on client credentials. + * ii. Builds service (initializes API). + */ +public class ConnectTasksApi { + private static final String appName = "ProgressChecker"; + private JsonFactory jsonFactory; + private HttpTransport transport; + private Credential credentials; + + public ConnectTasksApi() { + this.jsonFactory = new JacksonFactory(); + this.transport = new NetHttpTransport(); + this.credentials = null; + } + + /** + * Authorizes the data access requested by Tasks API by loading client secrets file. + */ + public void authorize() throws Exception { + final java.util.logging.Logger buggyLogger = java.util.logging.Logger.getLogger( + FileDataStoreFactory.class.getName()); + buggyLogger.setLevel(java.util.logging.Level.SEVERE); + + // Sets up files to store access token + DataStoreFactory datastore = new FileDataStoreFactory( + new File("tokens") + ); + + InputStream in = + ConnectTasksApi.class.getResourceAsStream("/client_id.json"); + + // Loads Client Secrets file downloaded from Google developer console. + GoogleClientSecrets clientSecrets = GoogleClientSecrets.load( + this.jsonFactory, + new InputStreamReader(in) + ); + + // Sets Up authorization code flow. + GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder( + this.transport, + this.jsonFactory, + clientSecrets, + Collections.singleton(TasksScopes.TASKS) + ).setDataStoreFactory(datastore).build(); + + // Authorizes with client credentials. + this.credentials = new AuthorizationCodeInstalledApp( + flow, + new LocalServerReceiver() + ).authorize("user"); + } + + /** + * Builds Google Tasks service. + */ + public Tasks getTasksService() { + return new Tasks.Builder( + this.transport, + this.jsonFactory, + this.credentials + ).setApplicationName(appName).build(); + } +} +``` +###### \java\seedu\progresschecker\logic\commands\AddDefaultTasksCommand.java +``` java +/** + * Adds a default task list to the user's Google account. + */ +public class AddDefaultTasksCommand extends Command { + + public static final String COMMAND_WORD = "newtasklist"; + public static final String COMMAND_ALIAS = "nl"; // short for "new list" + public static final String SOURCE_FILE_FOLDER = "/view"; + public static final String SOURCE_FILE = "/defaultTasks.txt"; + public static final String TEST_FILE = "/testTasks.txt"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Creates a new task list. " + + "Parameters: " + + "LISTNAME " + + "Example: " + COMMAND_WORD + " " + + "CS2103 LOs"; + + public static final String MESSAGE_SUCCESS = "New task list added: %1$s"; + public static final String DEFAULT_LIST_TITLE = "CS2103 LOs"; + public static final String FIRST_LIST_TITLE = "My List"; + public static final String DEFAULT_LIST_ID = "@default"; + public static final String CREATE_FAILURE = "Failed to create task list " + + "due to unexpected interrupt or API error: "; + public static final String START_MASSEGE = "We have a lot of tasks to initialize. " + + "The preparation may take a long time, please be patient :) Thank you!"; + public static final String LIST_FINISH_MASSEGE = "Task List created. Now pushing tasks into it: 0/"; + public static final String PUSH_TASK_ONGOING = "Pushing tasks into your task list: "; + + private String listTitle; + + /** + * Creates a AddDefaultTasksCommand to add the default task list with title {@code Sting} + */ + public AddDefaultTasksCommand(String title) { + requireNonNull(title); + listTitle = title; + } + + @Override + public CommandResult execute() throws CommandException { + try { + EventsCenter.getInstance().post(new NewResultAvailableEvent(START_MASSEGE)); + setTaskListTitle(DEFAULT_LIST_ID, DEFAULT_LIST_TITLE); + createTaskList(FIRST_LIST_TITLE); + copyTaskList(FIRST_LIST_TITLE, DEFAULT_LIST_ID); + clearTaskList(DEFAULT_LIST_ID); + + SimplifiedTask[] tasklist; + if (!listTitle.equals(DEFAULT_LIST_TITLE)) { + tasklist = getTestTasks(); + } else { + tasklist = getDefaultTasks(); + } + + addMultipleTask(tasklist, DEFAULT_LIST_ID); + + return new CommandResult(String.format(MESSAGE_SUCCESS, DEFAULT_LIST_TITLE)); + } catch (CommandException ce) { + throw ce; + } catch (Exception e) { + throw new CommandException(CREATE_FAILURE + DEFAULT_LIST_TITLE); + } + } +} +``` +###### \java\seedu\progresschecker\logic\commands\CompleteTaskCommand.java +``` java +/** + * Sets a task with given index as completed. + */ +public class CompleteTaskCommand extends Command { + + public static final String COMMAND_WORD = "complete"; + public static final String COMMAND_ALIAS = "ct"; // short for "complete task" + public static final String DATA_FOLDER = "data/"; + public static final String TASK_PAGE = "tasklist.html"; + public static final String FILE_FAILURE = "Something is wrong with the file system."; + public static final String COMMAND_FORMAT = COMMAND_WORD + "INDEX"; + public static final String MESSAGE_INDEX_CONSTRAINTS = "The index should be an index in the task list displayed" + + "to you. It must be an integer that does not exceed the number of tasks in the list."; + public static final int DUMMY_WEEK = 0; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Mark task with the given index in the list as completed.\n" + + "Parameters: INDEX (an index in the task list)\n " + + "Example: " + COMMAND_WORD + 1; + + public static final String MESSAGE_SUCCESS = "Keep it up! Completed task: %1$s"; + public static final String MESSAGE_NO_ACTION = "This task is already completed: %1$s"; + public static final String COMPLETE_FAILURE = "Error. Failed to mark it as completed. Index: %1$s"; + public static final String UNKNOWN_ERROR = "Unknow error in the system occurred"; + + public static final int ERROR = -1; + public static final int NO_ACTION = 0; + public static final int SUCCESS = 1; + + private int index; + + /** + * Complete the task with index {@code int} + */ + public CompleteTaskCommand(int index) { + requireNonNull(index); + this.index = index; + } + + @Override + public CommandResult execute() throws CommandException { + try { + Pair result = completeTask(index, DEFAULT_LIST_ID); + + if (result.getKey() == ERROR) { + return new CommandResult(String.format(result.getValue())); + } + + String titleWithCode = result.getValue(); // full file name + String[] parts = titleWithCode.split("&#"); // String array, each element is text between dots + + String title = parts[0]; + + if (result.getKey() == NO_ACTION) { + return new CommandResult(String.format(MESSAGE_NO_ACTION, index + ". " + title)); + } else if (result.getKey() == SUCCESS) { + ViewTaskListCommand view = LogicManager.getCurrentViewTask(); + view.updateView(); + return new CommandResult(String.format(MESSAGE_SUCCESS, index + ". " + title)); + } else { + // the command parser could never pass any value other than the above 4, thus we say "unknown error". + throw new CommandException(UNKNOWN_ERROR); + } + + } catch (CommandException ce) { + throw ce; + } catch (Exception e) { + throw new CommandException(COMPLETE_FAILURE + index); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof CompleteTaskCommand // instanceof handles nulls + && index == (((CompleteTaskCommand) other).index)); + } +} +``` +###### \java\seedu\progresschecker\logic\commands\GoToTaskUrlCommand.java +``` java +/** + * Goes to the webpage of task with given index. + */ +public class GoToTaskUrlCommand extends Command { + + public static final String COMMAND_WORD = "goto"; + public static final String COMMAND_ALIAS = "go"; + public static final String COMMAND_FORMAT = COMMAND_WORD + "INDEX"; + public static final String MESSAGE_INDEX_CONSTRAINTS = "The index should be an index in the task list displayed" + + "to you. It must be an integer that does not exceed the number of tasks in the list."; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Open the URL of task with the given index in the list.\n" + + "Parameters: INDEX (an index in the task list)\n " + + "Example: " + COMMAND_WORD + 1; + + public static final String MESSAGE_SUCCESS = "Viewing webpage of task: %1$s"; + public static final String MESSAGE_NO_URL = "This task does not have a webpage: %1$s"; + public static final String VIEW_URL_FAILURE = "Error. Cannot open the URL. Index: %1$s"; + + private int index; + + /** + * Gets URL of the task with index {@code int} + */ + public GoToTaskUrlCommand(int index) { + requireNonNull(index); + this.index = index; + } + + @Override + public CommandResult execute() throws CommandException { + try { + Pair result = getTaskUrl(index, DEFAULT_LIST_ID); + + String url = result.getKey(); + + String titleWithCode = result.getValue(); // full file name + String[] parts = titleWithCode.split("&#"); // String array, each element is text between dots + String title = parts[0]; + + if (url.equals("")) { + return new CommandResult(String.format(INDEX_OUT_OF_BOUND)); + } + + EventsCenter.getInstance().post(new LoadUrlEvent(url)); + EventsCenter.getInstance().post(new TabLoadChangedEvent(TASK_TAB)); + + return new CommandResult(String.format(MESSAGE_SUCCESS, index + ". " + title)); + } catch (CommandException ce) { + throw ce; + } catch (Exception e) { + throw new CommandException(VIEW_URL_FAILURE + index); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof GoToTaskUrlCommand // instanceof handles nulls + && index == (((GoToTaskUrlCommand) other).index)); + } +} +``` +###### \java\seedu\progresschecker\logic\commands\ResetTaskCommand.java +``` java +/** + * Sets a task with given index as incompleted. + */ +public class ResetTaskCommand extends Command { + + public static final String COMMAND_WORD = "reset"; + public static final String COMMAND_ALIAS = "rt"; // short for "reset task" + public static final String DATA_FOLDER = "data/"; + public static final String TASK_PAGE = "tasklist.html"; + public static final String FILE_FAILURE = "Something is wrong with the file system."; + public static final String COMMAND_FORMAT = COMMAND_WORD + "INDEX"; + public static final String MESSAGE_INDEX_CONSTRAINTS = "The index should be an index in the task list displayed" + + "to you. It must be an integer that does not exceed the number of tasks in the list."; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Mark task with the given index in the list as incompleted.\n" + + "Parameters: INDEX (an index in the task list)\n " + + "Example: " + COMMAND_WORD + 1; + + public static final String MESSAGE_SUCCESS = "Reset task: %1$s"; + public static final String MESSAGE_NO_ACTION = "This task is not completed yet: %1$s"; + public static final String RESET_FAILURE = "Error. Failed to mark it as incompleted. Index: %1$s"; + public static final String UNKNOWN_ERROR = "Unknow error in the system occurred"; + + public static final int ERROR = -1; + public static final int NO_ACTION = 0; + public static final int SUCCESS = 1; + + private int index; + + /** + * Reset the task with index {@code int} + */ + public ResetTaskCommand(int index) { + requireNonNull(index); + this.index = index; + } + + @Override + public CommandResult execute() throws CommandException { + try { + Pair result = undoTask(index, DEFAULT_LIST_ID); + + if (result.getKey() == ERROR) { + return new CommandResult(String.format(result.getValue())); + } + + String titleWithCode = result.getValue(); // full file name + String[] parts = titleWithCode.split("&#"); // String array, each element is text between dots + + String title = parts[0]; + + if (result.getKey() == NO_ACTION) { + return new CommandResult(String.format(MESSAGE_NO_ACTION, index + ". " + title)); + } else if (result.getKey() == SUCCESS) { + ViewTaskListCommand view = LogicManager.getCurrentViewTask(); + view.updateView(); + return new CommandResult(String.format(MESSAGE_SUCCESS, index + ". " + title)); + } else { + // the command parser could never pass any value other than the above 4, thus we say "unknown error". + throw new CommandException(UNKNOWN_ERROR); + } + } catch (CommandException ce) { + throw ce; + } catch (Exception e) { + throw new CommandException(RESET_FAILURE + index); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ResetTaskCommand // instanceof handles nulls + && index == (((ResetTaskCommand) other).index)); + } +} +``` +###### \java\seedu\progresschecker\logic\commands\TaskCommandUtil.java +``` java +/** + * Some util methods to be used by task-relevant commands. + */ +public class TaskCommandUtil { + + public static final String COMMAND_WORD = "viewtask"; + public static final String COMMAND_ALIAS = "vt"; + public static final String FILE_FAILURE = "Something is wrong with the file system."; + public static final String BAR_FAILURE = "Fail to get the progress bar."; + public static final String COMMAND_FORMAT = COMMAND_WORD; + public static final int COMPULSORY = -13; // parser returns -13 for compulsory tasks + public static final int SUBMISSION = -20; // parser returns -10 for tasks need submission + public static final String COMPULSORY_STR = " [Compulsory]"; + public static final String SUBMISSION_STR = " [Submission]"; + public static final String TITLE_COLOR_DAY = "grey"; + public static final String BACKGROUND_COLOR_DAY = "white"; + public static final String TITLE_COLOR_NIGHT = "white"; + public static final String BACKGROUND_COLOR_NIGHT = "#202226"; + + private final Logger logger = LogsCenter.getLogger(CommandBox.class); + + /** + * Writes the loaded task list to an html file. Loads the tasks. + * + * @param list task list serialized in a java List. + * @param indexList stores the corresponding index in the full list (the current showing list is a filtered + * result. + * @param file File object of the html file. + * @param targetWeek indicates the filter argument received for viewing task list + * @return progressInt the percentage of task completed (without the "%" sign) + */ + int writeToHtml(List list, List indexList, File file, int targetWeek) throws CommandException { + String backgroundColor = BACKGROUND_COLOR_DAY; + String titleColor = TITLE_COLOR_DAY; + double countCompleted = 0; + double countIncomp = 0; + + int size = list.size(); + + try { + FileUtil.createIfMissing(file); + + FileWriter fw1 = new FileWriter(file, false); + BufferedWriter bw1 = new BufferedWriter(fw1); + PrintWriter out1 = new PrintWriter(bw1); + + out1.print(""); + + FileWriter fw = new FileWriter(file, true); + BufferedWriter bw = new BufferedWriter(fw); + PrintWriter out = new PrintWriter(bw); + + out.print("\n" + + "\n" + + "\n" + + " Task List\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "\n" + + "" + + "
"); + if (targetWeek > 0) { + out.print("

" + + DEFAULT_LIST_TITLE + " Week: " + targetWeek + "

\n
\n"); + } else if (targetWeek == COMPULSORY) { + out.print("

" + + DEFAULT_LIST_TITLE + COMPULSORY_STR + "

\n
\n"); + } else if (targetWeek == SUBMISSION) { + out.print("

" + + DEFAULT_LIST_TITLE + SUBMISSION_STR + "

\n
\n"); + } else { + out.print("

" + + DEFAULT_LIST_TITLE + "

\n
\n"); + } + + for (int i = 0; i < size; i++) { + Task task = list.get(i); + int index = indexList.get(i); + + String status = task.getStatus(); + String notesWithUrl = task.getNotes(); + String[] parts = notesWithUrl.split(NOTE_TOKEN); + + String notes = parts[0]; + String url = parts[1]; + + if (status.length() >= 11) { + out.print("
\n" + + "
" + + index + ". " + task.getTitle() + "
\n" + + "
\n" + + "
⚠  " + + task.getDue().toString().substring(0, 10) + "
\n" + + "
⚑" + + "  Please work on it :) ☐
\n"); + countIncomp++; + } else { + out.print("
\n" + + "
" + + index + ". " + task.getTitle() + "
\n" + + "
\n" + + "
⚠  " + + task.getDue().toString().substring(0, 10) + "
\n" + + "
⚑" + + "  Completed! ☑
\n"); + countCompleted++; + } + + out.print("
✎  " + + notes + "
\n" + + "

" + + url + + "

\n" + + "
\n" + + "
\n"); + } + + out.print("
\n" + + "
\n" + + "\n"); + + double percent = countCompleted / (countCompleted + countIncomp); + int progressInt = (int) (percent * 100); + String progressDevision = (int) countCompleted + "/" + (int) (countCompleted + countIncomp); + + out.print("\n"); + + out.print("

" + "You have completed " + progressDevision + + " !" + "

"); + + out.print("\n" + + ""); + + out1.close(); + out.close(); + + return progressInt; + + } catch (IOException e) { + logger.info(FILE_FAILURE); + throw new CommandException(FILE_FAILURE); + + } + } + + /** + * Writes the progress bar to an html file. + * + * @param percentage the percentage of completed tasks. + * @param file File object of the html file. + * @param targetWeek indicates the filter argument received for viewing task list + */ + void writeToHtmlBar(int percentage, File file, int targetWeek) throws CommandException { + String backgroundColor = BACKGROUND_COLOR_DAY; + String titleColor = TITLE_COLOR_DAY; + + try { + FileUtil.createIfMissing(file); + + FileWriter fw1 = new FileWriter(file, false); + BufferedWriter bw1 = new BufferedWriter(fw1); + PrintWriter out1 = new PrintWriter(bw1); + + out1.print(""); + + FileWriter fw = new FileWriter(file, true); + BufferedWriter bw = new BufferedWriter(fw); + PrintWriter out = new PrintWriter(bw); + + out.print("\n" + + "\n" + + "\n" + + " progresschecker\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "\n" + + "\n" + + "
\n" + + "

Your Progress"); + + if (targetWeek > 0) { + out.print("(Week" + targetWeek + ")"); + } else if (targetWeek == COMPULSORY) { + out.print("([Compulsory])"); + } else if (targetWeek == SUBMISSION) { + out.print("([Submission])"); + } + + out.print(": " + + percentage + "%

\n" + + "
\n" + + "
\n" + + "
\n" + + " " + percentage + "% (on the way)\n" + + "
\n" + + "
\n" + + "
\n" + + "\n" + + "\n"); + + out1.close(); + out.close(); + + } catch (IOException e) { + logger.info(FILE_FAILURE); + throw new CommandException(BAR_FAILURE); + + } + } + + /** + * Writes an html file to involve both the progress bar and task list (the file is for backup, + * preview and debugging purposes. + * + * @param file File object of the html file. + */ + void writeToHtmlChecker(File file) throws CommandException { + try { + FileUtil.createIfMissing(file); + + FileWriter fw1 = new FileWriter(file, false); + BufferedWriter bw1 = new BufferedWriter(fw1); + PrintWriter out1 = new PrintWriter(bw1); + + out1.print(""); + + FileWriter fw = new FileWriter(file, true); + BufferedWriter bw = new BufferedWriter(fw); + PrintWriter out = new PrintWriter(bw); + + out.print("\n" + + "\n" + + "\n" + + " \n" + + " \n" + + "\n" + + "\n"); + + out1.close(); + out.close(); + + } catch (IOException e) { + logger.info(FILE_FAILURE); + throw new CommandException(FILE_FAILURE); + + } + } + + /** + * Reads the content of a text file to a String. + * + * @param path file path + * @param encoding the encoding standard, such as StandardCharsets.UTF_8. + */ + static String readFile(String path, Charset encoding) throws IOException { + byte[] encoded = Files.readAllBytes(Paths.get(path)); + return new String(encoded, encoding); + } +} +``` +###### \java\seedu\progresschecker\logic\commands\ViewTaskListCommand.java +``` java +/** + * View the web view of a particular TaskList (with the name provided). + */ +public class ViewTaskListCommand extends Command { + + public static final String COMMAND_WORD = "viewtask"; + public static final String COMMAND_ALIAS = "vt"; + public static final String DATA_FOLDER = "data/"; + public static final String TASK_PAGE = "tasklist.html"; + public static final String BAR_PAGE = "bar.html"; + public static final String CHECKER_PAGE = "progresschecker.html"; + public static final String PROGRESS_CHECKER_PAGE = "/view/progresschecker.html"; + public static final String FILE_FAILURE = "Something is wrong with the file system."; + public static final String BAR_FAILURE = "Fail to get the progress bar."; + public static final String UNKNOWN_ERROR = "Unknow error in the system occurred"; + public static final String COMMAND_FORMAT = COMMAND_WORD; + public static final String MESSAGE_TITLE_CONSTRAINTS = "The title of a task list should not exceed " + + "49 characters (as specified by Google Task."; + public static final String TASK_TAB = "task"; + public static final int MAX_TITLE_LENGTH = 49; + public static final int MAX_WEEK = 13; + public static final int ALL_WEEK = 0; + public static final int COMPULSORY = -13; // parser returns -13 for compulsory tasks + public static final int SUBMISSION = -20; // parser returns -10 for tasks need submission + public static final String COMPULSORY_STR = " [Compulsory]"; + public static final String SUBMISSION_STR = " [Submission]"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + // TODO: change description and parameter range when appropriate + + ": View tasks in the default task list, filtered to show only tasks at the input week or the" + + " input category. Only ONE filter keyword is allowed.\n" + + "Parameters: FILTER_KEYWORD (filter by week: must be an integer ranging from 1 to 13, or an asterisk (*)" + + " which means all weeks\n" + + " filter by category: \"compulsory\" or \"com\" means compulsory. " + + "\"submission\" or \"sub\" means to get the task that needs submission." + + "Example: " + COMMAND_WORD + "3\n" + + "Example: " + COMMAND_WORD + "sub"; + + public static final String MESSAGE_SUCCESS = "Viewing task list: %1$s"; + + // when the value of it is -13 or -20, it means the command is filtering compulsory or needsSubmission tasks + private final int targetWeek; + + private final Logger logger = LogsCenter.getLogger(CommandBox.class); + + public ViewTaskListCommand(int targetWeek) { + this.targetWeek = targetWeek; + } + + @Override + public CommandResult execute() throws CommandException { + updateView(); + + String result = chooseResult(); + return new CommandResult(result); + } + + /** + * Updates the HTML file and refresh the browser panel + * + * @throws CommandException + */ + public void updateView() throws CommandException { + List list = TaskListUtil.searchTaskListById(DEFAULT_LIST_ID); + List filteredList = new LinkedList(); + List indexList = new LinkedList(); + + try { + applyFilter (filteredList, indexList, list); + } catch (CommandException ce) { + throw ce; + } + + TaskCommandUtil util = new TaskCommandUtil(); + File htmlFile = new File(DATA_FOLDER + TASK_PAGE); + int progressInt = util.writeToHtml(filteredList, indexList, htmlFile, targetWeek); + File htmlBarFile = new File(DATA_FOLDER + BAR_PAGE); + util.writeToHtmlBar(progressInt, htmlBarFile, targetWeek); + File htmlCheckerFile = new File(DATA_FOLDER + CHECKER_PAGE); + util.writeToHtmlChecker(htmlCheckerFile); + try { + EventsCenter.getInstance().post(new LoadBarEvent(readFile(htmlBarFile.getAbsolutePath(), + StandardCharsets.UTF_8))); + EventsCenter.getInstance().post(new LoadTaskEvent(readFile(htmlFile.getAbsolutePath(), + StandardCharsets.UTF_8))); + EventsCenter.getInstance().post(new TabLoadChangedEvent(TASK_TAB)); + } catch (IOException ioe) { + logger.info(FILE_FAILURE); + throw new CommandException(FILE_FAILURE); + } + } + + /** + * Applies the filter argument to get the filtered list. + * + * @param filteredList the resulting list with filters applied. + * @param indexList the corresponding indices for {@code List filteredList}. + * @param list the raw list before processing. + * @throws CommandException + */ + private void applyFilter (List filteredList, List indexList, List list) + throws CommandException { + if (targetWeek > 0) { + int count = 1; + for (Task task : list) { + if (task.getTitle().contains("LO[W" + targetWeek)) { + filteredList.add(task); + indexList.add(count); + } + count++; + } + } else if (targetWeek == ALL_WEEK) { + int count = 1; + for (Task task : list) { + if (task.getTitle().contains("LO[W")) { + filteredList.add(task); + indexList.add(count); + } + count++; + } + } else if (targetWeek == COMPULSORY) { + int count = 1; + for (Task task : list) { + if (task.getTitle().contains("[Compulsory]")) { + filteredList.add(task); + indexList.add(count); + } + count++; + } + } else if (targetWeek == SUBMISSION) { + int count = 1; + for (Task task : list) { + if (task.getTitle().contains("[Submission]")) { + filteredList.add(task); + indexList.add(count); + } + count++; + } + } else { + throw new CommandException(UNKNOWN_ERROR); + } + } + + /** + * Choose a proper response message according to the filter argument. + * + * @return String the response message. + * @throws CommandException + */ + private String chooseResult () throws CommandException { + if (targetWeek > 0) { + return String.format(MESSAGE_SUCCESS, + DEFAULT_LIST_TITLE + " Week: " + targetWeek); + } else if (targetWeek == ALL_WEEK) { + return String.format(MESSAGE_SUCCESS, DEFAULT_LIST_TITLE); + } else if (targetWeek == COMPULSORY) { + return String.format(MESSAGE_SUCCESS, + DEFAULT_LIST_TITLE + COMPULSORY_STR); + } else if (targetWeek == SUBMISSION) { + return String.format(MESSAGE_SUCCESS, + DEFAULT_LIST_TITLE + SUBMISSION_STR); + } else { + // the command parser could never pass any value other than the above 4, thus we say "unknown error". + throw new CommandException(UNKNOWN_ERROR); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ViewTaskListCommand // instanceof handles nulls + && targetWeek == (((ViewTaskListCommand) other).targetWeek)); + } +} +``` +###### \java\seedu\progresschecker\logic\parser\CompleteTaskCommandParser.java +``` java +/** + * Parses input arguments and creates a new CompleteTaskCommand object + */ +public class CompleteTaskCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the CompleteTaskCommand + * and returns an CompleteTaskCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public CompleteTaskCommand parse(String args) throws ParseException { + try { + int index = ParserUtil.parseTaskIndex(args); + return new CompleteTaskCommand(index); + } catch (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_INDEX_OR_FORMAT, CompleteTaskCommand.MESSAGE_USAGE)); + } + } + +} +``` +###### \java\seedu\progresschecker\logic\parser\GoToTaskUrlCommandParser.java +``` java +/** + * Parses input arguments and creates a new GoToTaskUrlCommand object + */ +public class GoToTaskUrlCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the GoToTaskUrlCommand + * and returns a GoToTaskUrlCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public GoToTaskUrlCommand parse(String args) throws ParseException { + try { + int index = ParserUtil.parseTaskIndex(args); + return new GoToTaskUrlCommand(index); + } catch (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_INDEX_OR_FORMAT, GoToTaskUrlCommand.MESSAGE_USAGE)); + } + } + +} +``` +###### \java\seedu\progresschecker\logic\parser\ParserUtil.java +``` java + /** + * Parses {@code String} into an {@code int} and returns it. Leading and trailing whitespaces will be + * trimmed. + * @throws IllegalValueException if the specified index is invalid (not non-zero unsigned integer). + */ + public static int parseTaskIndex(String index) throws IllegalValueException { + String trimmedIndex = index.trim(); + if (!StringUtil.isNonZeroUnsignedInteger(trimmedIndex)) { + throw new IllegalValueException(MESSAGE_INVALID_INDEX); + } + return Integer.parseInt(trimmedIndex); + } + + /** + * Parses {@code String} into an {@code int} and returns it. Leading and trailing whitespaces will be + * trimmed. + * @throws IllegalValueException if the specified week number is invalid (neither an integer ranging from 1 to 13 + * nor an asterisk(*)). + */ + public static int parseTaskWeek(String week) throws IllegalValueException { + String trimmedWeek = week.trim(); + if (trimmedWeek.equals("*")) { + return ALL_WEEK; + } else if (trimmedWeek.equals("sub") || trimmedWeek.equals("submission")) { + return SUBMISSION; + } else if (trimmedWeek.equals("com") || trimmedWeek.equals("compulsory")) { + return COMPULSORY; + } else if (!StringUtil.isNonZeroUnsignedInteger(trimmedWeek)) { + throw new IllegalValueException(MESSAGE_INVALID_TASK_FILTER); + } + int intWeek = Integer.parseInt(trimmedWeek); + if (intWeek >= 1 && intWeek <= 13) { + return intWeek; + } else { + throw new IllegalValueException(MESSAGE_INVALID_TASK_FILTER); + } + } +``` +###### \java\seedu\progresschecker\logic\parser\ParserUtil.java +``` java + /** + * Parses a {@code String Title} into a {@code String}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code Title} is invalid. + */ + public static String parseTaskTitle(String title) throws IllegalValueException { + requireNonNull(title); + String trimmedTitle = title.trim(); + if (trimmedTitle.length() > MAX_TITLE_LENGTH) { + throw new IllegalValueException(MESSAGE_TITLE_CONSTRAINTS); + } + return trimmedTitle; + } +``` +###### \java\seedu\progresschecker\logic\parser\ParserUtil.java +``` java + /** + * Parses a {@code String username} into a {@code GithubUsername}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code username} is invalid. + */ + + public static GithubUsername parseUsername(String username) throws IllegalValueException { + requireNonNull(username); + String trimmedUsername = username.trim(); + if (!GithubUsername.isValidUsername(trimmedUsername)) { + throw new IllegalValueException(GithubUsername.MESSAGE_USERNAME_CONSTRAINTS); + } + return new GithubUsername(trimmedUsername); + } + + /** + * Parses a {@code Optional username} into an {@code Optional} + * if {@code username} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseUsername(Optional username) throws IllegalValueException { + requireNonNull(username); + return username.isPresent() ? Optional.of(parseUsername(username.get())) : Optional.empty(); + } +``` +###### \java\seedu\progresschecker\logic\parser\ParserUtil.java +``` java + /** + * Parses a {@code String major} into an {@code Major}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code major} is invalid. + */ + public static Major parseMajor(String major) throws IllegalValueException { + requireNonNull(major); + String trimmedMajor = major.trim(); + if (!Major.isValidMajor(trimmedMajor)) { + throw new IllegalValueException(Major.MESSAGE_MAJOR_CONSTRAINTS); + } + return new Major(trimmedMajor); + } + + /** + * Parses a {@code Optional major} into an {@code Optional} if {@code major} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseMajor(Optional major) throws IllegalValueException { + requireNonNull(major); + return major.isPresent() ? Optional.of(parseMajor(major.get())) : Optional.empty(); + } + + /** + * Parses a {@code String year} into an {@code Year}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code year} is invalid. + */ + public static Year parseYear(String year) throws IllegalValueException { + requireNonNull(year); + String trimmedYear = year.trim(); + if (!Year.isValidYear(trimmedYear)) { + throw new IllegalValueException(Year.MESSAGE_YEAR_CONSTRAINTS); + } + return new Year(trimmedYear); + } + + /** + * Parses a {@code Optional year} into an {@code Optional} if {@code year} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseYear(Optional year) throws IllegalValueException { + requireNonNull(year); + return year.isPresent() ? Optional.of(parseYear(year.get())) : Optional.empty(); + } +``` +###### \java\seedu\progresschecker\logic\parser\ResetTaskCommandParser.java +``` java +/** + * Parses input arguments and creates a new ResetTaskCommand object + */ +public class ResetTaskCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the ResetTaskCommand + * and returns an ResetTaskCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public ResetTaskCommand parse(String args) throws ParseException { + try { + int index = ParserUtil.parseTaskIndex(args); + return new ResetTaskCommand(index); + } catch (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_INDEX_OR_FORMAT, ResetTaskCommand.MESSAGE_USAGE)); + } + } + +} +``` +###### \java\seedu\progresschecker\logic\parser\ViewTaskListCommandParser.java +``` java +/** + * Parses input arguments and creates a new ViewTaskListCommand object + */ +public class ViewTaskListCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the ViewTaskListCommand + * and returns an ViewTaskListCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public ViewTaskListCommand parse(String args) throws ParseException { + try { + int week = ParserUtil.parseTaskWeek(args); + return new ViewTaskListCommand(week); + } catch (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_TASK_FILTER, ViewTaskListCommand.MESSAGE_USAGE)); + } + } +} +``` +###### \java\seedu\progresschecker\model\person\GithubUsername.java +``` java +/** + * Represents a Person's Github username in the ProgressChecker. + * Guarantees: immutable; is valid as declared in {@link #isValidUsername(String)} + */ +public class GithubUsername { + + public static final String MESSAGE_USERNAME_CONSTRAINTS = + "Person Github usernames should only contain alphanumeric characters and spaces, " + + "and it should not be blank"; + + /* + * The first character of the username must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String USERNAME_VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; + + public final String username; + + /** + * Constructs a {@code GithubUsername}. + * + * @param username A valid username. + */ + public GithubUsername(String username) { + requireNonNull(username); + checkArgument(isValidUsername(username), MESSAGE_USERNAME_CONSTRAINTS); + this.username = username; + } + + /** + * Returns true if a given string is a valid Github username. + */ + public static boolean isValidUsername(String test) { + return test.matches(USERNAME_VALIDATION_REGEX); + } + + + @Override + public String toString() { + return username; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof GithubUsername // instanceof handles nulls + && this.username.equals(((GithubUsername) other).username)); // state check + } + + @Override + public int hashCode() { + return username.hashCode(); + } + +} +``` +###### \java\seedu\progresschecker\model\person\Major.java +``` java +/** + * Represents a Person's major in the ProgressChecker. + * Guarantees: immutable; is valid as declared in {@link #isValidMajor(String)} + */ +public class Major { + + public static final String MESSAGE_MAJOR_CONSTRAINTS = + "Person majors can take any values, and it should not be blank"; + + /* + * The first character of the major must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String MAJOR_VALIDATION_REGEX = "[^\\s].*"; + + public final String value; + + /** + * Constructs an {@code Major}. + * + * @param major A valid major. + */ + public Major(String major) { + requireNonNull(major); + checkArgument(isValidMajor(major), MESSAGE_MAJOR_CONSTRAINTS); + this.value = major; + } + + /** + * Returns true if a given string is a valid person major. + */ + public static boolean isValidMajor(String test) { + return test.matches(MAJOR_VALIDATION_REGEX); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Major // instanceof handles nulls + && this.value.equals(((Major) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} +``` +###### \java\seedu\progresschecker\model\person\Person.java +``` java + public GithubUsername getUsername() { + return username; + } + + public Major getMajor() { + return major; + } + + public Year getYear() { + return year; + } +``` +###### \java\seedu\progresschecker\model\person\Year.java +``` java +/** + * Represents a Person's year of study in the ProgressChecker. + * Guarantees: immutable; is valid as declared in {@link #isValidYear(String)} + */ +public class Year { + + public static final String MESSAGE_YEAR_CONSTRAINTS = + "Person years of study can take digits ranging from 1 to 5, it can be left blank"; + + /* + * It accepts single digits ranging from 1 to 5. + * empty string will be accepted as well, as "year" is an optional field. + */ + public static final String YEAR_VALIDATION_REGEX = "(^$|^[1-5]$)"; + + public final String value; + + /** + * Constructs an {@code Year}. + * + * @param year A valid year. + */ + public Year(String year) { + requireNonNull(year); + checkArgument(isValidYear(year), MESSAGE_YEAR_CONSTRAINTS); + this.value = year; + } + + /** + * Returns true if a given string is a valid year of study. + */ + public static boolean isValidYear(String test) { + return test.matches(YEAR_VALIDATION_REGEX); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Year // instanceof handles nulls + && this.value.equals(((Year) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} +``` +###### \java\seedu\progresschecker\model\task\SimplifiedTask.java +``` java +/** + * Represents a simplified task in the ProgressChecker. + * It is called "simplified" because it extracts only the attributes we want to use from the Google Tasks + */ +public class SimplifiedTask { + public final String title; + public final String notes; + public final String due; + + public SimplifiedTask (String title, String notes, String due) { + this.title = title; + this.notes = notes; + this.due = due; + } + + public String getTitle () { + return this.title; + } + + public String getNotes () { + return this.notes; + } + + public String getDue () { + return this.due; + } + +} +``` +###### \java\seedu\progresschecker\model\task\TaskListUtil.java +``` java +/** + * Include customized methods (based on Google Tasks API) to manipulate task lists. + */ +public class TaskListUtil { + + public static final String AUTHORIZE_FAILURE = "Failed to authorize tasks api client credentials"; + public static final String ADD_FAILURE = "Failed to add new task list to account"; + public static final String LOAD_FAILURE = "Failed to load this task list (might be wrong title)"; + + /** + * Creates a new task list with title {@code String} and adds to the current list of task lists + * + * @param listTitle title of the task list we intend to create + */ + public static void createTaskList(String listTitle) throws CommandException { + ConnectTasksApi connection = new ConnectTasksApi(); + + try { + connection.authorize(); + } catch (Exception e) { + throw new CommandException(AUTHORIZE_FAILURE); + } + + com.google.api.services.tasks.Tasks service = connection.getTasksService(); + + try { + service.tasklists().insert( + new TaskList().setTitle(listTitle) + ).execute(); + } catch (IOException ioe) { + throw new CommandException(ADD_FAILURE); + } + } + + /** + * Finds the task list with title {@code String} from the current list of task lists + * + * @param listTitle title of the task list we look for + * @return the List instances containing all tasks in the specified task list + */ + public static List searchTaskList(String listTitle) throws CommandException { + List list = null; + + ConnectTasksApi connection = new ConnectTasksApi(); + + try { + connection.authorize(); + } catch (Exception e) { + throw new CommandException(AUTHORIZE_FAILURE); + } + + com.google.api.services.tasks.Tasks service = connection.getTasksService(); + + try { + + TaskLists taskLists = service.tasklists().list().execute(); + TaskList taskList = taskLists.getItems().stream() + .filter(t -> t.getTitle().equals(listTitle)) + .findFirst() + .orElse(null); + String id = taskList.getId(); + Tasks tasks = service.tasks().list(id).execute(); + list = tasks.getItems(); + } catch (IOException ioe) { + throw new CommandException(LOAD_FAILURE); + } + + return list; + } + + /** + * Finds the task list with ID {@code String} from the current list of task lists + * + * @param listId title of the task list we look for + * @return the List instances containing all tasks in the specified task list + */ + public static List searchTaskListById(String listId) throws CommandException { + List list = null; + + ConnectTasksApi connection = new ConnectTasksApi(); + + try { + connection.authorize(); + } catch (Exception e) { + throw new CommandException(AUTHORIZE_FAILURE); + } + + com.google.api.services.tasks.Tasks service = connection.getTasksService(); + + try { + + Tasks tasks = service.tasks().list(listId).execute(); + list = tasks.getItems(); + } catch (IOException ioe) { + throw new CommandException(LOAD_FAILURE); + } + + return list; + } + + /** + * Changes the name of task list with id {@code String} to {@code String} + * + * @param listId identifier of the target task list whose name will be changed + * @param listTitle title of the task list we look for + */ + public static void setTaskListTitle(String listId, String listTitle) throws CommandException { + ConnectTasksApi connection = new ConnectTasksApi(); + + try { + connection.authorize(); + } catch (Exception e) { + throw new CommandException(AUTHORIZE_FAILURE); + } + + com.google.api.services.tasks.Tasks service = connection.getTasksService(); + + try { + + TaskList taskList = service.tasklists().get(listId).execute(); + + taskList.setTitle(listTitle); + + TaskList result = service.tasklists().update( + taskList.getId(), + taskList + ).execute(); + + } catch (IOException ioe) { + throw new CommandException(LOAD_FAILURE); + } + } + + /** + * Copies tasks in the task list with id {@code String} to the task list with title {@code String} + * + * @param listId identifier of the target task list whose name will be changed + * @param listTitle title of the task list we look for + */ + public static void copyTaskList(String listTitle, String listId) throws CommandException { + ConnectTasksApi connection = new ConnectTasksApi(); + + try { + connection.authorize(); + } catch (Exception e) { + throw new CommandException(AUTHORIZE_FAILURE); + } + + com.google.api.services.tasks.Tasks service = connection.getTasksService(); + + try { + + TaskLists taskLists = service.tasklists().list().execute(); + + Tasks baseTasks = service.tasks().list(listId).execute(); + + TaskList targetTaskList = taskLists.getItems().stream() + .filter(t -> t.getTitle().equals(listTitle)) + .findFirst() + .orElse(null); + String id = targetTaskList.getId(); + + for (Task task : baseTasks.getItems()) { + Task t = new Task(); + t.setTitle(task.getTitle()); + t.setStatus(task.getStatus()); + t.setDue(task.getDue()); + t.setNotes(task.getNotes()); + service.tasks().insert(id, t).execute(); + } + + + } catch (IOException ioe) { + throw new CommandException(LOAD_FAILURE); + } + } + + /** + * Removes all tasks in the task list with id {@code String} + * + * @param listId identifier of the target task list whose content will be removed + */ + public static void clearTaskList(String listId) throws CommandException { + ConnectTasksApi connection = new ConnectTasksApi(); + + try { + connection.authorize(); + } catch (Exception e) { + throw new CommandException(AUTHORIZE_FAILURE); + } + + com.google.api.services.tasks.Tasks service = connection.getTasksService(); + + try { + + Tasks tasks = service.tasks().list(listId).execute(); + for (Task task : tasks.getItems()) { + service.tasks().delete(listId, task.getId()).execute(); + } + + } catch (IOException ioe) { + throw new CommandException(LOAD_FAILURE); + } + } +} +``` +###### \java\seedu\progresschecker\model\task\TaskUtil.java +``` java +/** + * Include customized methods (based on Google Tasks API) to manipulate tasks. + */ +public class TaskUtil { + + public static final String AUTHORIZE_FAILURE = "Failed to authorize tasks api client credentials"; + public static final String LOAD_FAILURE = "Failed to load this task list"; + public static final String INDEX_OUT_OF_BOUND = "The index is out of bound"; + public static final String DATE_FORMAT = "MM/dd/yyyy HH:mm"; + public static final String COMPLETED = "completed"; + public static final String NEEDS_ACTION = "needsAction"; + public static final int ERROR_NUMBER = -1; + public static final int TRUE = 1; + public static final int FALSE = 0; + public static final String ERROR_STRING = ""; + public static final String NOTE_TOKEN = "checkurl"; + + + /** + * Finds the task with title {@code String} in the tasklist with title {@code String} + * + * @param taskTitle title of the task we look for + * @param listTitle the title of the list to which the task belongs + * @return the Task instances + */ + public static Task searchTask(String taskTitle, String listTitle) throws CommandException { + Task task = null; + + ConnectTasksApi connection = new ConnectTasksApi(); + + try { + connection.authorize(); + } catch (Exception e) { + throw new CommandException(AUTHORIZE_FAILURE); + } + + com.google.api.services.tasks.Tasks service = connection.getTasksService(); + + try { + TaskLists taskLists = service.tasklists().list().execute(); + TaskList taskList = taskLists.getItems().stream() + .filter(t -> t.getTitle().equals(listTitle)) + .findFirst() + .orElse(null); + + Tasks tasks = service.tasks().list(taskList.getId()).execute(); + task = tasks.getItems().stream() + .filter(t -> t.getTitle().equals(taskTitle)) + .findFirst() + .orElse(null); + + } catch (IOException ioe) { + throw new CommandException(LOAD_FAILURE); + } + + return task; + } + + /** + * Creates a task with title {@code String} to the tasklist with ID {@code String} + * + * @param taskTitle title of the task we want to create + * @param listId the identifier of the list to which the task will be added + * @param notes description or relevant URL link to this task + * @param due the date and time of the deadline, in format "MM/dd/yyyy HH:mm" + */ + public static void createTask(String taskTitle, String listId, String notes, String due) + throws CommandException { + ConnectTasksApi connection = new ConnectTasksApi(); + + try { + connection.authorize(); + } catch (Exception e) { + throw new CommandException(AUTHORIZE_FAILURE); + } + + com.google.api.services.tasks.Tasks service = connection.getTasksService(); + + try { + TaskLists taskLists = service.tasklists().list().execute(); + + Task task = service.tasks().insert( + listId, + new Task().setTitle(taskTitle) + .setDue(getDate(due)) + .setNotes(notes) + .setStatus(NEEDS_ACTION) + ).execute(); + + } catch (IOException ioe) { + throw new CommandException(LOAD_FAILURE); + } + + } + + /** + * Adds a group of tasks to a Google Task List with Id {@code String listId} + * + * @param simpleList a list of {@code SimplifiedTask} + * @param listId the identifier of the list to which the task will be added + */ + public static void addMultipleTask(SimplifiedTask[] simpleList, String listId) + throws CommandException { + ConnectTasksApi connection = new ConnectTasksApi(); + + try { + connection.authorize(); + } catch (Exception e) { + throw new CommandException(AUTHORIZE_FAILURE); + } + + com.google.api.services.tasks.Tasks service = connection.getTasksService(); + + try { + for (SimplifiedTask simpleTask: simpleList) { + Task task = service.tasks().insert( + listId, + new Task().setTitle(simpleTask.getTitle()) + .setDue(getDate(simpleTask.getDue())) + .setNotes(simpleTask.getNotes()) + .setStatus(NEEDS_ACTION) + ).execute(); + } + + + } catch (IOException ioe) { + throw new CommandException(LOAD_FAILURE); + } + + } + + /** + * Converts a string in format "MM/dd/yyyy HH:mm" to a DateTime object + * + * @param s string in format "MM/dd/yyyy HH:mm", representing a date + * @return the DateTime instances, or null if encountered error when parsing + */ + public static DateTime getDate(String s) { + SimpleDateFormat simpleDateFormat = new SimpleDateFormat(DATE_FORMAT); + try { + Date date = simpleDateFormat.parse(s); + return new DateTime(date); + } catch (ParseException ex) { + return null; + } + } + + /** + * Marks the task with index {@code int index} in the tasklist with ID {@code String listId} as completed + * + * @param index title of the task we look for + * @param listId the identifier of the list to which the task belongs + * @return result whether this command made any change of the task list (0 means no change) and + * the title of the task with index {@code int} + */ + public static Pair completeTask(int index, String listId) throws CommandException { + ConnectTasksApi connection = new ConnectTasksApi(); + + try { + connection.authorize(); + } catch (Exception e) { + throw new CommandException(AUTHORIZE_FAILURE); + } + + com.google.api.services.tasks.Tasks service = connection.getTasksService(); + + try { + int isChanged = FALSE; + Tasks tasks = service.tasks().list(listId).execute(); + List list = tasks.getItems(); + if (list.size() < index) { + Pair result = new Pair(ERROR_NUMBER, INDEX_OUT_OF_BOUND); + return result; + } + Task task = list.get(index - 1); + + if (!task.getStatus().equals(COMPLETED)) { + task.setStatus(COMPLETED); + isChanged = TRUE; + } + + task = service.tasks().update( + listId, + task.getId(), + task + ).execute(); + + Pair result = new Pair(isChanged, task.getTitle()); + + return result; + + } catch (IOException ioe) { + throw new CommandException(LOAD_FAILURE); + } + } + + /** + * Marks the task with index {@code int index} in the tasklist with ID {@code String listId} as incompleted + * + * @param index title of the task we look for + * @param listId the identifier of the list to which the task belongs + * @return result whether this command made any change of the task list (0 means no change) and + * the title of the task with index {@code int} + */ + public static Pair undoTask(int index, String listId) throws CommandException { + ConnectTasksApi connection = new ConnectTasksApi(); + + try { + connection.authorize(); + } catch (Exception e) { + throw new CommandException(AUTHORIZE_FAILURE); + } + + com.google.api.services.tasks.Tasks service = connection.getTasksService(); + + try { + int isChanged = FALSE; + Tasks tasks = service.tasks().list(listId).execute(); + List list = tasks.getItems(); + if (list.size() < index) { + Pair result = new Pair(ERROR_NUMBER, INDEX_OUT_OF_BOUND); + return result; + } + Task task = list.get(index - 1); + + if (!task.getStatus().equals(NEEDS_ACTION)) { + task.setCompleted(null); + task.setStatus(NEEDS_ACTION); + isChanged = TRUE; + } + + task = service.tasks().update( + listId, + task.getId(), + task + ).execute(); + + Pair result = new Pair(isChanged, task.getTitle()); + + return result; + + } catch (IOException ioe) { + throw new CommandException(LOAD_FAILURE); + } + } + + /** + * Retrieve the URL of task with index {@code int index} in the tasklist with ID {@code String listId} + * + * @param index title of the task we look for + * @param listId the identifier of the list to which the task belongs + * @return the URL of task with index {@code int index} in the tasklist with ID {@code String listId} + * or error if index is out of bound. and the title of the task with index {@code int} + */ + public static Pair getTaskUrl(int index, String listId) throws CommandException { + ConnectTasksApi connection = new ConnectTasksApi(); + + try { + connection.authorize(); + } catch (Exception e) { + throw new CommandException(AUTHORIZE_FAILURE); + } + + com.google.api.services.tasks.Tasks service = connection.getTasksService(); + + try { + Tasks tasks = service.tasks().list(listId).execute(); + List list = tasks.getItems(); + if (list.size() < index) { + Pair result = new Pair(ERROR_STRING, ERROR_STRING); + return result; + } + Task task = list.get(index - 1); + + String title = task.getTitle(); + String notesWithUrl = task.getNotes(); + String[] parts = notesWithUrl.split(NOTE_TOKEN); + + Pair result = new Pair(parts[1], title); + return result; + + } catch (IOException ioe) { + throw new CommandException(LOAD_FAILURE); + } + } +} +``` +###### \java\seedu\progresschecker\storage\DefaultTasks.java +``` java +/** + * Contains information of the default tasks. + * Using an object to save data to reduce the cost of file I/O. + */ +public class DefaultTasks { + private static final String SUB = "[Submission]"; + private static final String COM = "[Compulsory]"; + private static final String STAR = "★"; + public static SimplifiedTask[] getDefaultTasks() { + return new SimplifiedTask[] { + // WEEK 2 + new SimplifiedTask("LO[W2.2]" + STAR, + "Can use basic features of an IDE (2.2 a~c): checkurlhttps:" + + "//nus-cs2103-ay1718s2.github.io/website/schedule/week2/outcomes.html", + "01/25/2018 23:59"), + new SimplifiedTask("LO[W2.3]" + SUB + STAR + STAR, + "Can use Java Collections: checkurlhttps:" + + "//github.com/nus-cs2103-AY1718S2/addressbook-level1#use-collections-lo-collections", + "01/25/2018 23:59"), + new SimplifiedTask("LO[W2.4]" + SUB + STAR + STAR + STAR, + "Can use Java varargs feature: checkurlhttps:" + + "//github.com/nus-cs2103-AY1718S2/addressbook-level1#use-varargs-lo-varargss", + "01/25/2018 23:59"), + new SimplifiedTask("LO[W2.5]" + STAR + STAR, + "Can automate simple regression testing of text UIs (2.5 a~d): checkurlhttps:" + + "//nus-cs2103-ay1718s2.github.io/website/schedule/week2/outcomes.html", + "01/25/2018 23:59"), + new SimplifiedTask("LO[W2.6]" + SUB + STAR, + "Can use Git to save history (2.6 a~f): checkurlhttps://www.sourcetreeapp.com/", + "01/25/2018 23:59"), + + // WEEK 3 + new SimplifiedTask("LO[W3.1]" + STAR + STAR, + "Can refactor code at a basic level (3.1 a~d, c needs submission): checkurlhttps:" + + "//nus-cs2103-ay1718s2.github.io/website/schedule/week3/outcomes.html", + "02/01/2018 23:59"), + new SimplifiedTask("LO[W3.2]" + SUB + STAR + STAR, + "Can follow a simple style guide (3.2 a~d, c needs submission): checkurlhttps:" + + "//nus-cs2103-ay1718s2.github.io/website/schedule/week3/outcomes.html", + "02/01/2018 23:59"), + new SimplifiedTask("LO[W3.3]" + STAR + STAR, + "Can improve code readability (3.3 a~d): checkurlhttps:" + + "//nus-cs2103-ay1718s2.github.io/website/schedule/week3/outcomes.html", + "02/01/2018 23:59"), + new SimplifiedTask("LO[W3.4]" + STAR + STAR, + "Can use good naming (3.4 a~b): checkurlhttps:" + + "//nus-cs2103-ay1718s2.github.io/website/schedule/week3/outcomes.html", + "02/01/2018 23:59"), + new SimplifiedTask("LO[W3.5]" + STAR + STAR, + "Can avoid unsafe coding practices (3.5 a~c): checkurlhttps:" + + "//nus-cs2103-ay1718s2.github.io/website/schedule/week3/outcomes.html", + "02/01/2018 23:59"), + new SimplifiedTask("LO[W3.6]" + STAR + STAR + STAR, + "Can write good code comments (3.6 a~c): checkurlhttps:" + + "//nus-cs2103-ay1718s2.github.io/website/schedule/week3/outcomes.html", + "02/01/2018 23:59"), + new SimplifiedTask("LO[W3.7]" + STAR + STAR + STAR, + "Can use intermediate level features of an IDE (3.7 a~c): checkurlhttps:" + + "//nus-cs2103-ay1718s2.github.io/website/schedule/week3/outcomes.html", + "02/01/2018 23:59"), + new SimplifiedTask("LO[W3.8]" + SUB + STAR, + "Can communicate with a remote repo (3.8 a~d, c&d need submission): checkurlhttps:" + + "//github.com/nus-cs2103-AY1718S2/samplerepo-things", + "02/01/2018 23:59"), + new SimplifiedTask("LO[W3.9]" + STAR + STAR + STAR, + "Can traverse Git history: checkurlhttps (3.9 a~d):" + + "//nus-cs2103-ay1718s2.github.io/website/schedule/week3/outcomes.html", + "02/01/2018 23:59"), + new SimplifiedTask("LO[W3.10]" + COM + SUB + STAR, + "Can work with a 1KLoC code base: checkurlhttps:" + + "//github.com/nus-cs2103-AY1718S2/addressbook-level1", + "02/01/2018 23:59"), + + // WEEK 4 + new SimplifiedTask("LO[W4.1]" + STAR + STAR + STAR, + "Can explain models (4.1 a~b): checkurlhttps:" + + "//nus-cs2103-ay1718s2.github.io/website/schedule/week4/outcomes.html", + "02/08/2018 23:59"), + new SimplifiedTask("LO[W4.2]" + STAR, + "Can explain OOP (4.2 a~e): checkurlhttps:" + + "//nus-cs2103-ay1718s2.github.io/website/schedule/week4/outcomes.html", + "02/08/2018 23:59"), + new SimplifiedTask("LO[W4.3]" + STAR, + "Can explain basic object/class structures (4.3 a~c): checkurlhttps:" + + "//nus-cs2103-ay1718s2.github.io/website/schedule/week4/outcomes.html", + "02/08/2018 23:59"), + new SimplifiedTask("LO[W4.4]" + STAR, + "Can implement (4.4 a~b): checkurlhttps:" + + "//nus-cs2103-ay1718s2.github.io/website/schedule/week4/outcomes.html", + "02/08/2018 23:59"), + new SimplifiedTask("LO[W4.5]" + SUB + STAR + STAR, + "Can do exception handling in code (4.5 a~d, c needs submission): checkurlhttps:" + + "//github.com/nus-cs2103-AY1718S2/addressbook-level2/blob/master/doc" + + "/LearningOutcomes.md#handle-exceptions-lo-exceptions", + "02/08/2018 23:59"), + new SimplifiedTask("LO[W4.6]" + SUB + STAR + STAR + STAR, + "Can use Java enumerations (4.6 a~b, b needs submission): checkurlhttps:" + + "//github.com/nus-cs2103-AY1718S2/addressbook-level1/#use-enums-lo-enums", + "02/08/2018 23:59"), + new SimplifiedTask("LO[W4.7]" + SUB + STAR, + "Can create PRs on GitHub (4.7 a~e, all need submission: checkurlhttps:" + + "//github.com/nus-cs2103-AY1718S2/samplerepo-pr-practice", + "02/08/2018 23:59"), + + // WEEK 5 + new SimplifiedTask("LO[W5.1]" + STAR + STAR + STAR, + "Can use intermediate-level class diagrams (5.1 a~e): checkurlhttps:" + + "//nus-cs2103-ay1718s2.github.io/website/schedule/week5/outcomes.html", + "02/15/2018 23:59"), + new SimplifiedTask("LO[W5.2]" + STAR + STAR + STAR, + "Can explain single responsibility principle: checkurlhttps:" + + "//github.com/nus-cs2103-AY1718S2/addressbook-level2/blob/master/doc" + + "/LearningOutcomes.md#follow-the-single-responsibility-principle-lo-srp", + "02/15/2018 23:59"), + new SimplifiedTask("LO[W5.3]" + SUB + STAR, + "Can implement inheritance (5.3 a~b, b needs submission): checkurlhttps:" + + "//github.com/nus-cs2103-AY1718S2/addressbook-level2/blob/master/doc" + + "/LearningOutcomes.md#use-inheritance-to-achieve-code-reuse-lo-inheritance", + "02/15/2018 23:59"), + new SimplifiedTask("LO[W5.4]" + SUB + STAR + STAR, + "Can implement class-level members (5.4 a~b, b needs submission): checkurlhttps:" + + "//github.com/nus-cs2103-AY1718S2/addressbook-level2/blob/master/doc" + + "/LearningOutcomes.md#use-class-level-members-lo-classlevel", + "02/15/2018 23:59"), + new SimplifiedTask("LO[W5.5]" + SUB + STAR + STAR + STAR, + "Can implement composition (5.5 a~b, b needs submission): checkurlhttps:" + + "//github.com/nus-cs2103-AY1718S2/addressbook-level2/blob/master/doc" + + "/LearningOutcomes.md#implement-a-class-lo-implementclass", + "02/15/2018 23:59"), + new SimplifiedTask("LO[W5.6]" + STAR + STAR + STAR, + "Can implement aggregation (5.6 a~b): checkurlhttps:" + + "//nus-cs2103-ay1718s2.github.io/website/schedule/week5/outcomes.html", + "02/15/2018 23:59"), + new SimplifiedTask("LO[W5.7]" + STAR + STAR + STAR, + "Can implement overloading (5.7 a~b): checkurlhttps:" + + "//nus-cs2103-ay1718s2.github.io/website/schedule/week5/outcomes.html", + "02/15/2018 23:59"), + new SimplifiedTask("LO[W5.8]" + STAR + STAR, + "Can explain requirements (5.8 a~d, b needs submission): checkurlhttps:" + + "//github.com/nus-cs2103-AY1718S2/addressbook-level3/blob/master/doc" + + "/LearningOutcomes.md#use-non-functional-requirements-lo-nfr", + "02/15/2018 23:59"), + new SimplifiedTask("LO[W5.9]" + STAR + STAR + STAR, + "Can explain some techniques for gathering requirements (5.9 a~g): checkurlhttps:" + + "//nus-cs2103-ay1718s2.github.io/website/schedule/week5/outcomes.html", + "02/15/2018 23:59"), + new SimplifiedTask("LO[W5.10]" + SUB + STAR, + "Can use some techniques for specifying requirements (5.10 a~k, c needs submission): " + + "checkurlhttps://github.com/nus-cs2103-AY1718S2/addressbook-level3/blob/master/doc" + + "/LearningOutcomes.md#utilize-user-stories-lo-userstories", + "02/15/2018 23:59"), + new SimplifiedTask("LO[W5.11]" + COM + SUB + STAR, + "Can work with a 2KLoC code base: checkurlhttps:" + + "//github.com/nus-cs2103-AY1718S2/addressbook-level2", + "02/15/2018 23:59"), + + // WEEK 6 + new SimplifiedTask("LO[W6.1]" + SUB + STAR, + "Can use simple JUnit tests (6.1 a~e, e needs submission): checkurlhttps:" + + "//github.com/nus-cs2103-AY1718S2/addressbook-level2/blob/master/doc" + + "/LearningOutcomes.md#use-junit-to-implement-unit-tests-lo-junit", + "02/22/2018 23:59"), + new SimplifiedTask("LO[W6.2]" + STAR, + "Can follow Forking Workflow (6.2 a~e): checkurlhttps:" + + "//nus-cs2103-ay1718s2.github.io/website/schedule/week6/outcomes.html", + "02/22/2018 23:59"), + new SimplifiedTask("LO[W6.3]" + STAR, + "Can interpret basic sequence diagrams (6.3 a~f): checkurlhttps:" + + "//nus-cs2103-ay1718s2.github.io/website/schedule/week6/outcomes.html", + "02/22/2018 23:59"), + new SimplifiedTask("LO[W6.4]" + SUB + STAR, + "Can implement polymorphism (6.4 a~h, d~h need submission): checkurlhttps:" + + "//github.com/nus-cs2103-AY1718S2/addressbook-level3/blob/master/doc" + + "/LearningOutcomes.md#use-polymorphism-lo-polymorphism", + "02/22/2018 23:59"), + new SimplifiedTask("LO[W6.5]" + SUB + STAR + STAR + STAR, + "Can use JavaFX to build a simple GUI: checkurlhttps:" + + "//github.com/nus-cs2103-AY1718S2/addressbook-level3/blob/master/doc" + + "/LearningOutcomes.md#use-java-fx-for-gui-programming-lo-javafx", + "02/22/2018 23:59"), + + // RECESS + + // WEEK 7 + new SimplifiedTask("LO[W7.1]" + STAR, + "Can record requirements of a product: checkurlhttps:" + + "//nus-cs2103-ay1718s2.github.io/website/schedule/week7/outcomes.html", + "03/08/2018 23:59"), + + // WEEK 8 + new SimplifiedTask("LO[W8.1]" + STAR + STAR + STAR, + "Can apply basic product design guidelines: checkurlhttps:" + + "//nus-cs2103-ay1718s2.github.io/website/schedule/week8/outcomes.html", + "03/15/2018 23:59"), + + // WEEK 9 + new SimplifiedTask("LO[W9.1]" + STAR + STAR, + "Can use models to conceptualize an OO solution: checkurlhttps:" + + "//nus-cs2103-ay1718s2.github.io/website/schedule/week9/outcomes.html", + "03/22/2018 23:59"), + + // WEEK 10 + new SimplifiedTask("LO[W10.1]" + STAR + STAR + STAR, + "Can explain SE principles: checkurlhttps:" + + "//nus-cs2103-ay1718s2.github.io/website/schedule/week10/outcomes.html", + "03/29/2018 23:59"), + + // WEEK 11 + new SimplifiedTask("LO[W11.1]" + STAR + STAR + STAR, + "Can explain object oriented domain models: checkurlhttps:" + + "//nus-cs2103-ay1718s2.github.io/website/schedule/week11/outcomes.html", + "04/05/2018 23:59"), + + // WEEK 12 + new SimplifiedTask("LO[W12.1]" + STAR + STAR + STAR, + "Can explain some UML models: checkurlhttps:" + + "//nus-cs2103-ay1718s2.github.io/website/schedule/week12/outcomes.html", + "04/12/2018 23:59"), + + // WEEK 13 + new SimplifiedTask("LO[W13.1]" + STAR, + "Can demo a product: checkurlhttps:" + + "//nus-cs2103-ay1718s2.github.io/website/schedule/week13/outcomes.html", + "04/19/2018 23:59"), + + }; + } +} +``` +###### \java\seedu\progresschecker\storage\TestTasks.java +``` java +/** + * Contains information of test tasks. + */ +public class TestTasks { + public static SimplifiedTask[] getTestTasks() { + return new SimplifiedTask[] { + new SimplifiedTask("LO[W3.10][Compulsory][Submission]★", + "Work with a 1KLoC code base: checkurlhttps://nus-cs2103-ay1718s2.github.io/website/schedule" + + "/week3/outcomes.html", + "02/01/2018 23:59"), + new SimplifiedTask("LO[W4.1]★★★", + "Can explain models: checkurlhttps://nus-cs2103-ay1718s2.github.io/website/schedule/week4" + + "/outcomes.html", + "02/08/2018 23:59"), + new SimplifiedTask("LO[W5.11][Compulsory][Submission]★", + "Work with a 2KLoC code base: checkurlhttps://nus-cs2103-ay1718s2.github.io/website/schedule" + + "/week5/outcomes.html", + "02/15/2018 23:59"), + new SimplifiedTask("LO[W6.5][Submission]★★★", + "Can use JavaFX to build a simple GUI: checkurlhttps://nus-cs2103-ay1718s2.github.io/website" + + "/schedule/week6/outcomes.html", + "02/22/2018 23:59") + }; + } + +} +``` +###### \java\seedu\progresschecker\ui\Browser2Panel.java +``` java + /** + * Loads the HTML file which contains task information. + */ + public void loadBarPage(String content) { + loadPageViaString(content); + } + + public void loadPageViaString(String content) { + Platform.runLater(() -> browser2.getEngine().loadContent(content)); + } + + /** + * Frees resources allocated to the browser. + */ + public void freeResources() { + browser2 = null; + } + + @Subscribe + private void handleLoadBarEvent(LoadBarEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + loadBarPage(event.getContent()); + } +``` +###### \java\seedu\progresschecker\ui\BrowserPanel.java +``` java + /** + * Loads the HTML file which contains task information. + */ + public void loadTaskPage(String content) { + loadPageViaString(content); + } + + public void loadPageViaString(String content) { + Platform.runLater(() -> browser.getEngine().loadContent(content)); + } +``` +###### \java\seedu\progresschecker\ui\BrowserPanel.java +``` java + @Subscribe + private void handleLoadTaskEvent(LoadTaskEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + loadTaskPage(event.getContent()); + } + + @Subscribe + private void handleLoadUrlEvent(LoadUrlEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + loadPage(event.getUrl()); + } +``` +###### \resources\view\MainWindow.fxml +``` fxml + + + + + + +``` +###### \resources\view\sampleTasklist.html +``` html +

CS2103 LOs

+
+ + +
+
1. LO[W6.5][Submission]★★★
+
+
⚠  2018-02-22
+
⚑  Completed! ☑
+
✎  Can use JavaFX to build a simple GUI:
+

https://nus-cs2103-ay1718s2.github.io/website/schedule/week6/outcomes.html

+
+
+ + +
+
2. LO[W5.11][Compulsory][Submission]★
+
+
⚠  2018-02-15
+
⚑  Please work on it :) ☐
+
✎  Work with a 2KLoC code base:
+

https://nus-cs2103-ay1718s2.github.io/website/schedule/week5/outcomes.html

+
+
+
+
3. LO[W4.1]★★★
+
+
⚠  2018-02-08
+
⚑  Please work on it :) ☐
+
✎  Can explain models:
+

https://nus-cs2103-ay1718s2.github.io/website/schedule/week4/outcomes.html

+
+
+
+
4. LO[W3.10][Compulsory][Submission]★
+
+
⚠  2018-02-01
+
⚑  Please work on it :) ☐
+
✎  Work with a 1KLoC code base:
+

https://nus-cs2103-ay1718s2.github.io/website/schedule/week3/outcomes.html

+
+
+
+
+ + +

You have completed 1/4 !

+ +``` +###### \resources\view\TaskListCard.fxml +``` fxml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` diff --git a/collated/functional/Livian1107.md b/collated/functional/Livian1107.md new file mode 100644 index 000000000000..5e262374fc99 --- /dev/null +++ b/collated/functional/Livian1107.md @@ -0,0 +1,899 @@ +# Livian1107 +###### \java\seedu\progresschecker\commons\events\ui\ChangeThemeEvent.java +``` java +/** + * Represents the change of the theme of ProgressChecker. + */ +public class ChangeThemeEvent extends BaseEvent { + public final String theme; + + public ChangeThemeEvent(String theme) { + this.theme = theme; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + + public String getTheme() { + return theme; + } +} +``` +###### \java\seedu\progresschecker\commons\util\FileUtil.java +``` java + /** + * Copies all the contents from the file in original path to the one in destination path. + * @param oriPath of the file to be copied + * @param destPath of the file to be pasted + * @return true if the file is successfully copied to the specified place. + */ + public static boolean copyFile(String oriPath, String destPath) throws IOException { + + //create a buffer to store content + byte[] buffer = new byte[3072]; + + //bufferedInputStream + FileInputStream fis = new FileInputStream(oriPath); + BufferedInputStream bis = new BufferedInputStream(fis); + + //bufferedOutputStream + FileOutputStream fos = new FileOutputStream(destPath); + BufferedOutputStream bos = new BufferedOutputStream(fos); + + int numBytes = bis.read(buffer); + while (numBytes > 0) { + bos.write(buffer, 0, numBytes); + numBytes = bis.read(buffer); + } + + //close input,output stream + bis.close(); + bos.close(); + + return true; + } +``` +###### \java\seedu\progresschecker\commons\util\FileUtil.java +``` java + /** + * Returns the extension information from the file path + * @param filePath + * @return extension String + */ + public static String getFileExtension(String filePath) { + return "." + filePath.split("\\.")[1]; + } + + /** + * Creates a new file if it does not exist + * @param file to created + * @throws IOException if the file cannot be created + */ + public static void createMissing(File file) throws IOException { + if (!file.exists()) { + createFile(file); + } + } + + /** + * Returns whether the uploaded file is a valid image file + * Valid image file should have extension: '.jpg', '.jepg' or 'png'. + * @param path of the uploaded image file + * @return true if the uploaded file has valid extension + */ + public static boolean isValidImageFile(String path) { + return path.matches(REGEX_VALID_IMAGE); + } + + /** + * Returns whether the path of uploaded file is under the specific folder + * @param path of the uploaded file + * @param parentFolder of the specific folder + * @return true if the file is under this specific folder + */ + public static boolean isUnderFolder(String path, String parentFolder) { + return path.startsWith(parentFolder); + } +``` +###### \java\seedu\progresschecker\logic\commands\SortCommand.java +``` java +/** + * Sorts all persons in the ProgressChecker in alphabetical order. + */ +public class SortCommand extends UndoableCommand { + public static final String COMMAND_WORD = "sort"; + public static final String COMMAND_ALIAS = "so"; + + public static final String MESSAGE_SUCCESS = "Sorted all persons in alphabetical order"; + + @Override + protected CommandResult executeUndoableCommand() { + requireNonNull(model); + model.sort(); + return new CommandResult(MESSAGE_SUCCESS); + } + +} +``` +###### \java\seedu\progresschecker\logic\commands\ThemeCommand.java +``` java +/** + * Changes the thmem of ProgressChecker. + */ +public class ThemeCommand extends UndoableCommand { + public static final String COMMAND_WORD = "theme"; + public static final String COMMAND_ALIAS = "t"; + + public static final String COMMAND_FORMAT = COMMAND_WORD + " " + + " THEME"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Change theme of ProgressChecker.\n" + + "Parameters: " + "Theme(either 'day' or 'night')\n" + + "Example: " + COMMAND_WORD + "day"; + + public static final String MESSAGE_SUCCESS = "Change to theme %1$s"; + + public final String theme; + + public ThemeCommand(String theme) { + this.theme = theme; + } + + @Override + protected CommandResult executeUndoableCommand() { + EventsCenter.getInstance().post(new ChangeThemeEvent(theme)); + return new CommandResult(String.format(MESSAGE_SUCCESS, theme)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ThemeCommand // instanceof handles nulls + && this.theme.equals(((ThemeCommand) other).theme)); // state check + } +} +``` +###### \java\seedu\progresschecker\logic\commands\UploadCommand.java +``` java +/** + * Uploads a photo to the profile. + */ +public class UploadCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "upload"; + public static final String COMMAND_ALIAS = "up"; + public static final String COMMAND_FORMAT = COMMAND_WORD + " " + "INDEX " + + "[PATH]"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Uploads a photo to the specified profile.\n" + + "The valid photo extensions are 'jpg', 'jpeg' or 'png'.\n" + + "Parameter: INDEX(must be a positive integer) PATH...\n" + + "Example: " + COMMAND_WORD + " 1 C:\\Users\\User\\Desktop\\photo.png\n"; + + public static final String MESSAGE_SUCCESS = "New photo uploaded!"; + public static final String MESSAGE_COPY_FAIL = "Cannot copy file!"; + public static final String MESSAGE_IMAGE_NOT_FOUND = "The image cannot be found!"; + public static final String MESSAGE_IMAGE_DUPLICATE = "Upload the same image!"; + public static final String MESSAGE_LOCAL_PATH_CONSTRAINTS = + "The photo path should be a valid path on your PC. " + + "It should start with the name of your PC user name, " + + "followed by several folders, e.g.\"C:\\Usres\\User\\Desktop\\photo.png\". \n" + + "The file should exist. \n" + + "The path of file cannot contain any whitespaces inside. \n" + + "The valid extensions of the file should be 'jpg', 'jpeg' or 'png'. \n"; + + public static final String REGEX_VALID_LOCAL_PATH = + "([a-zA-Z]:)?(\\\\\\w+)+\\\\" + REGEX_VALID_IMAGE; + public static final String PATH_SAVED_FILE = "src/main/resources/images/contact/"; + + private final Index targetIndex; + + private Person personToUpdate; + private PhotoPath photoPath; + private String localPath; + private String savePath; + + /** + * Creates an UploadCommand to upload the profile photo with specified {@code Path} + */ + public UploadCommand(Index index, String path) throws IllegalValueException, IOException { + requireNonNull(path); + requireNonNull(index); + if (isValidLocalPath(path)) { + this.localPath = path; + this.targetIndex = index; + this.savePath = copyLocalPhoto(localPath); + this.photoPath = new PhotoPath(savePath); + } else { + throw new IllegalValueException(MESSAGE_LOCAL_PATH_CONSTRAINTS); + } + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + requireNonNull(personToUpdate); + try { + model.addPhoto(photoPath); + model.uploadPhoto(personToUpdate, savePath); + return new CommandResult(MESSAGE_SUCCESS); + } catch (PersonNotFoundException pnfe) { + throw new AssertionError("The target person cannot be missing"); + } catch (DuplicatePhotoException e) { + throw new CommandException(MESSAGE_IMAGE_DUPLICATE); + } catch (DuplicatePersonException e) { + throw new CommandException(MESSAGE_IMAGE_DUPLICATE); + } + } + + @Override + protected void preprocessUndoableCommand() throws CommandException { + List lastShownList = model.getFilteredPersonList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + personToUpdate = lastShownList.get(targetIndex.getZeroBased()); + } + + /** + * Returns true when the String path provided is a valid local path + */ + public static boolean isValidLocalPath(String path) { + return path.matches(REGEX_VALID_LOCAL_PATH); + } + + /** + * Copies uploaded photo file to specified saved path + * @param localPath is the path of uploaded photo + * @return String of saved path of uploaded photo + */ + public String copyLocalPhoto(String localPath) throws IOException { + File localFile = new File(localPath); + String newPath = createSavePath(localPath); + + if (!localFile.exists()) { + throw new FileNotFoundException(MESSAGE_LOCAL_PATH_CONSTRAINTS); + } + + createSavedPhoto(newPath); + + try { + copyFile(localPath, newPath); + } catch (IOException e) { + throw new IOException(MESSAGE_COPY_FAIL); + } + return newPath; + } + + /** + * Create a new path for uploaded photo to save + * @param localPath is the String of uploaded photo on user PC + * @return savePath String of uploaded photo + */ + public static String createSavePath(String localPath) { + Date date = new Date(); + Long num = date.getTime(); + String createPath = PATH_SAVED_FILE + num.toString() + getFileExtension(localPath); + return createPath; + } + + /** + * Creates a new file to save profile photo + * @param path to save photo + */ + public void createSavedPhoto(String path) { + File savedPhoto = new File(path); + try { + createMissing(savedPhoto); + } catch (IOException e) { + assert false : "Fail to create the file!"; + } + } +} +``` +###### \java\seedu\progresschecker\logic\parser\ParserUtil.java +``` java + /** + * Parses {@code type} into a {@code String} and returns it. Leading and trailing whitespaces will be + * trimmed. + * @throws IllegalValueException if the specified theme is invalid (not of string "day" or "night"). + */ + public static String parseTheme(String theme) throws IllegalValueException { + String trimmedType = theme.trim(); + if (!trimmedType.equals("day") && !trimmedType.equals("night")) { + throw new IllegalValueException(MESSAGE_INVALID_TAB_TYPE); + } + return trimmedType; + } + +``` +###### \java\seedu\progresschecker\logic\parser\ThemeCommandParser.java +``` java +/** + * Parses input arguments and creates a new ThemeCommand object + */ +public class ThemeCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the ViewCommand + * and returns an ViewCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public ThemeCommand parse(String args) throws ParseException { + try { + String theme = ParserUtil.parseTheme(args); + return new ThemeCommand(theme); + } catch (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ThemeCommand.MESSAGE_USAGE)); + } + } +} +``` +###### \java\seedu\progresschecker\logic\parser\UploadCommandParser.java +``` java +/** + * Parses input arguments and creates a new UploadCommand object + */ +public class UploadCommandParser implements Parser { + /** + * Parses the given {@code String} of arguments in the context of the UploadCommand + * and returns an UploadCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public UploadCommand parse(String args) throws ParseException { + requireNonNull(args); + Index index; + String[] content = args.trim().split(" "); + try { + index = ParserUtil.parseIndex(content[0]); + return new UploadCommand(index, content[1]); + } catch (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, UploadCommand.MESSAGE_USAGE)); + } catch (IOException e) { + throw new ParseException( + UploadCommand.MESSAGE_IMAGE_NOT_FOUND); + } + + } +} +``` +###### \java\seedu\progresschecker\model\ModelManager.java +``` java + @Override + public void uploadPhoto(Person target, String path) + throws PersonNotFoundException, DuplicatePersonException { + progressChecker.uploadPhoto(target, path); + updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + indicateProgressCheckerChanged(); + } + + @Override + public void addPhoto(PhotoPath photoPath) throws DuplicatePhotoException { + progressChecker.addPhotoPath(photoPath); + indicateProgressCheckerChanged(); + } +``` +###### \java\seedu\progresschecker\model\photo\exceptions\DuplicatePhotoException.java +``` java +/** + * Signals that the operation will result in duplicate PhotoPath objects. + */ +public class DuplicatePhotoException extends DuplicateDataException { + public DuplicatePhotoException() { + super("Operation would result in duplicate photos"); + } +} +``` +###### \java\seedu\progresschecker\model\photo\exceptions\PhotoNotFoundException.java +``` java +/** + * Signals that the operation is unable to find the specified photo. + */ +public class PhotoNotFoundException extends Exception {} +``` +###### \java\seedu\progresschecker\model\photo\PhotoPath.java +``` java +/** + * Represents a Path of Photo in ProgressChecker + */ +public class PhotoPath { + + public static final String PHOTO_SAVED_PATH = "src/main/resources/images/contact/"; + public static final String MESSAGE_PHOTOPATH_CONSTRAINTS = + "The path of the profile photo should start with '" + PHOTO_SAVED_PATH + + "'. The extensions of the file to upload should be 'jpg', 'jpeg' or 'png'."; + + public final String value; + + /** + * Builds the path of profile photo in the ProgressChecker + * Validates the given String of path + * @param path is the String of the profile photo path + * @trhows IllegalValueException if the String violates the constraints of photo path + */ + public PhotoPath(String path) throws IllegalValueException { + requireNonNull(path); + if (isValidPhotoPath(path)) { + this.value = path; + } else { + throw new IllegalValueException(MESSAGE_PHOTOPATH_CONSTRAINTS); + } + } + + /** + * Validates the given photo path + */ + public static boolean isValidPhotoPath (String path) { + if (path.isEmpty()) { //empty path + return true; + } + boolean isValidImage = isValidImageFile(path); + boolean isUnderFolder = isUnderFolder(path, PHOTO_SAVED_PATH); + return isValidImage && isUnderFolder; + } + + @Override + public String toString() { + return this.value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof PhotoPath // instanceof handles nulls + && this.value.equals(((PhotoPath) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } +} +``` +###### \java\seedu\progresschecker\model\photo\UniquePhotoList.java +``` java +/** + * A list of photo paths that enforces uniqueness between its elements and does not allow nulls. + * + * Supports a minimal set of list operations. + * + * @see PhotoPath#equals(Object) + * @see CollectionUtil#elementsAreUnique(Collection) + */ +public class UniquePhotoList implements Iterable { + + private final ObservableList internalList = FXCollections.observableArrayList(); + + /** + * Returns true if the list contains an equivalent photo path as the given argument. + */ + public boolean contains(PhotoPath toCheck) { + requireNonNull(toCheck); + return internalList.contains(toCheck); + } + + /** + * Adds a photo path to the list. + * + * @throws DuplicatePhotoException if the photo path to add is a duplicate of an existing photo path in the list. + */ + public void add(PhotoPath toAdd) throws DuplicatePhotoException { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicatePhotoException(); + } + internalList.add(toAdd); + } + + /** + * Replaces the photo path {@code target} in the list with {@code editedPhoto}. + * + * @throws DuplicatePhotoException if the replacement is equivalent to another existing photo path in the list. + * @throws PhotoNotFoundException if {@code target} could not be found in the list. + */ + public void setPhoto(PhotoPath target, PhotoPath editedPhoto) + throws DuplicatePhotoException, PhotoNotFoundException { + requireNonNull(editedPhoto); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new PhotoNotFoundException(); + } + + if (!target.equals(editedPhoto) && internalList.contains(editedPhoto)) { + throw new DuplicatePhotoException(); + } + + internalList.set(index, editedPhoto); + } + + /** + * Removes the equivalent photo path from the list. + * + * @throws PhotoNotFoundException if no such person could be found in the list. + */ + public boolean remove(PhotoPath toRemove) throws PhotoNotFoundException { + requireNonNull(toRemove); + final boolean photoFoundAndDeleted = internalList.remove(toRemove); + if (!photoFoundAndDeleted) { + throw new PhotoNotFoundException(); + } + return photoFoundAndDeleted; + } + + public void setPhotos(UniquePhotoList replacement) { + this.internalList.setAll(replacement.internalList); + } + + public void setPhotos(List photos) throws DuplicatePhotoException { + requireAllNonNull(photos); + final UniquePhotoList replacement = new UniquePhotoList(); + for (final PhotoPath photo : photos) { + replacement.add(photo); + } + setPhotos(replacement); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList asObservableList() { + return FXCollections.unmodifiableObservableList(internalList); + } + + @Override + public Iterator iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniquePhotoList // instanceof handles nulls + && this.internalList.equals(((UniquePhotoList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } +} +``` +###### \java\seedu\progresschecker\model\ProgressChecker.java +``` java + /** + * Sorts the existing {@code UniquePersonList} of this {@code ProgressChecker} + * with their names in alphabetical order. + */ + public void sort() { + requireNonNull(persons); + persons.sort(); + } + + /** + * Adds a new uploaded photo path to the the list of profile photos + * @param photoPath of a new uploaded photo + * @throws DuplicatePhotoException if there already exists the same photo path + */ + public void addPhotoPath(PhotoPath photoPath) throws DuplicatePhotoException { + photos.add(photoPath); + } +``` +###### \java\seedu\progresschecker\model\ProgressChecker.java +``` java + /** + * Uploads the profile photo path of target person + * @param target + * @param path + * @throws PersonNotFoundException + * @throws DuplicatePersonException + */ + public void uploadPhoto(Person target, String path) throws PersonNotFoundException, DuplicatePersonException { + Person tempPerson = target; + target.updatePhoto(path); + persons.setPerson(tempPerson, target); + } +``` +###### \java\seedu\progresschecker\ui\MainWindow.java +``` java + /** + * Switches to the Night Theme. + */ + @FXML + public void handleNightTheme() { + Scene scene = primaryStage.getScene(); + scene.getStylesheets().setAll(DARK_THEME); + primaryStage.setScene(scene); + show(); + } + + /** + * Switches to the Day Theme. + */ + @FXML + public void handleDayTheme() { + Scene scene = primaryStage.getScene(); + scene.getStylesheets().setAll(DAY_THEME); + primaryStage.setScene(scene); + show(); + } +``` +###### \java\seedu\progresschecker\ui\MainWindow.java +``` java + /** + * Sets the icon of Main Window + * @param icon with given path + */ + private void setIcon(String icon) { + primaryStage.getIcons().setAll(AppUtil.getImage(icon)); + } + + /** + * Sets the minimum size of the main window + */ + private void setWindowMinSize() { + primaryStage.setMinHeight(MIN_HEIGHT); + primaryStage.setMinWidth(MIN_WIDTH); + } + + @Subscribe + private void handleChangeThemeEvent(ChangeThemeEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + switch (event.getTheme()) { + case "day": + handleDayTheme(); + break; + case "night": + handleNightTheme(); + break; + default: + handleDayTheme(); + } + } +``` +###### \java\seedu\progresschecker\ui\ProfilePanel.java +``` java + +/** + * Panel contains the information of person + */ +public class ProfilePanel extends UiPart { + + private static final String FXML = "ProfilePanel.fxml"; + private static String DEFAULT_PHOTO = "/images/profile_photo.jpg"; + + private static final String[] TAG_COLORS = { "red", "orange", "yellow", "green", "blue", "purple" }; + /** + * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. + * As a consequence, UI elements' variable names cannot be set to such keywords + * or an exception will be thrown by JavaFX during runtime. + * + * @see The issue on AddressBook level 4 + */ + + private Person person; + private Person currentlyViewedPerson; + + private final Logger logger = LogsCenter.getLogger(this.getClass()); + + @FXML + private Label name; + @FXML + private Label phone; + @FXML + private Label major; + @FXML + private Label year; + @FXML + private Label email; + @FXML + private Label username; + @FXML + private FlowPane tags; + @FXML + private Ellipse profile; + + public ProfilePanel() { + super(FXML); + this.person = null; + loadDefaultPerson(); + registerAsAnEventHandler(this); + + } + +``` +###### \java\seedu\progresschecker\ui\ProfilePanel.java +``` java + /** + * Loads the default person + */ + private void loadDefaultPerson() { + name.setText("Person X"); + phone.setText(""); + username.setText(""); + email.setText(""); + year.setText(""); + major.setText(""); + tags.getChildren().clear(); + + setDefaultInfoPhoto(); + currentlyViewedPerson = null; + logger.info("Currently Viewing: Default Person"); + } + + /** + * Loads the info of the selected person + */ + public void loadPerson(Person person) { + this.person = person; + tags.getChildren().clear(); + name.setText(person.getName().fullName); + phone.setText(person.getPhone().value); + major.setText(person.getMajor().value); + year.setText(person.getYear().value); + email.setText(person.getEmail().value); + username.setText(person.getUsername().username); +``` +###### \java\seedu\progresschecker\ui\ProfilePanel.java +``` java + loadPhoto(); + + currentlyViewedPerson = person; + logger.info("Currently Viewing: " + currentlyViewedPerson.getName()); + } + + /** + * Sets the default info photo. + */ + public void setDefaultInfoPhoto() { + Image defaultImage = new Image(MainApp.class.getResourceAsStream(DEFAULT_PHOTO)); + profile.setFill(new ImagePattern(defaultImage)); + } + + /** + * Loads profile photo + */ + private void loadPhoto() { + String photoPath = person.getPhotoPath(); + Image profilePhoto; + if (photoPath.contains("contact")) { + File photo = new File(photoPath); + if (photo.exists() && !photo.isDirectory()) { + String url = photo.toURI().toString(); + profilePhoto = new Image(url); + profile.setFill(new ImagePattern(profilePhoto)); + } + } else { + profilePhoto = new Image( + MainApp.class.getResourceAsStream(person.getDefaultPath())); + profile.setFill(new ImagePattern(profilePhoto)); + } + } + + @Subscribe + private void handlePersonPanelSelectionChangeEvent(PersonPanelSelectionChangedEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + this.loadPerson(event.getNewSelection().person); + } +} +``` +###### \resources\view\MainWindow.fxml +``` fxml + + + + +``` +###### \resources\view\MainWindow.fxml +``` fxml + + + + + + +``` +###### \resources\view\MainWindow.fxml +``` fxml + + + + + + + + + +``` +###### \resources\view\ProfilePanel.fxml +``` fxml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` diff --git a/collated/functional/adityaa1998.md b/collated/functional/adityaa1998.md new file mode 100644 index 000000000000..a8fa9e4101d0 --- /dev/null +++ b/collated/functional/adityaa1998.md @@ -0,0 +1,2468 @@ +# adityaa1998 +###### \java\seedu\progresschecker\logic\CommandFormatListUtil.java +``` java +/** + * Initialises and returns a list which contains different command formats + */ +public final class CommandFormatListUtil { + private static ArrayList commandFormatList; + + public static ArrayList getCommandFormatList () { + commandFormatList = new ArrayList<>(); + createCommandFormatList(); + return commandFormatList; + } + + /** + * Creates commandFormatList for existing commands + */ + private static void createCommandFormatList() { + commandFormatList.add(AddCommand.COMMAND_FORMAT); + commandFormatList.add(AnswerCommand.COMMAND_FORMAT); + commandFormatList.add(ClearCommand.COMMAND_WORD); + commandFormatList.add(DeleteCommand.COMMAND_FORMAT); + commandFormatList.add(CompleteTaskCommand.COMMAND_WORD); + commandFormatList.add(EditCommand.COMMAND_FORMAT); + commandFormatList.add(ExitCommand.COMMAND_WORD); + commandFormatList.add(FindCommand.COMMAND_FORMAT); + commandFormatList.add(GoToTaskUrlCommand.COMMAND_WORD); + commandFormatList.add(HelpCommand.COMMAND_WORD); + commandFormatList.add(ListCommand.COMMAND_WORD); + commandFormatList.add(RedoCommand.COMMAND_WORD); + commandFormatList.add(ResetTaskCommand.COMMAND_WORD); + commandFormatList.add(SelectCommand.COMMAND_FORMAT); + commandFormatList.add(SortCommand.COMMAND_WORD); + commandFormatList.add(UndoCommand.COMMAND_WORD); + commandFormatList.add(UploadCommand.COMMAND_FORMAT); + commandFormatList.add(ViewCommand.COMMAND_FORMAT); + commandFormatList.add(ViewTaskListCommand.COMMAND_FORMAT); + commandFormatList.add(CreateIssueCommand.COMMAND_FORMAT); + commandFormatList.add(EditIssueCommand.COMMAND_FORMAT); + commandFormatList.add(ReopenIssueCommand.COMMAND_FORMAT); + commandFormatList.add(CloseIssueCommand.COMMAND_FORMAT); + commandFormatList.add(GitLoginCommand.COMMAND_FORMAT); + commandFormatList.add(ThemeCommand.COMMAND_FORMAT); + commandFormatList.add(GitLogoutCommand.COMMAND_WORD); + + //sorting the commandFormatList + Collections.sort(commandFormatList); + } +} +``` +###### \java\seedu\progresschecker\logic\commands\CloseIssueCommand.java +``` java +/** + * Close an issue on github + */ +public class CloseIssueCommand extends Command { + + public static final String COMMAND_WORD = "-issue"; + public static final String COMMAND_ALIAS = "cli"; + public static final String COMMAND_FORMAT = COMMAND_WORD + " ISSUE-INDEX"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + "\nParameters: ISSUE_INDEX (must be a positive valid index number)" + + "Example: \n" + COMMAND_WORD + " 2"; + + public static final String MESSAGE_SUCCESS = "Issue #%1$s closed successfully"; + public static final String MESSAGE_FAILURE = "Issue wasn't closed. Enter correct index number."; + public static final String MESSAGE_AUTHENTICATION_FAILURE = "Github isn't authenticated. " + + "Use 'gitlogin' command to authenticate first"; + + private final Index targetIndex; + + public CloseIssueCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute() throws CommandException { + try { + model.closeIssueOnGithub(targetIndex); + } catch (IOException ie) { + throw new CommandException(MESSAGE_FAILURE); + } catch (CommandException ce) { + throw new CommandException(MESSAGE_AUTHENTICATION_FAILURE); + } + + return new CommandResult(String.format(MESSAGE_SUCCESS, targetIndex.getOneBased())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof CloseIssueCommand // instanceof handles nulls + && this.targetIndex.equals(((CloseIssueCommand) other).targetIndex)); // state check + } +} +``` +###### \java\seedu\progresschecker\logic\commands\CreateIssueCommand.java +``` java +/** + * Create an issue on github + */ +public class CreateIssueCommand extends Command { + + public static final String COMMAND_WORD = "+issue"; + public static final String COMMAND_ALIAS = "ci"; + public static final String COMMAND_FORMAT = COMMAND_WORD + " " + + PREFIX_TITLE + "TITLE " + + PREFIX_ASSIGNEES + "ASSIGNEES " + + PREFIX_MILESTONE + "MILESTONE " + + PREFIX_BODY + "BODY " + + PREFIX_LABEL + "LABELS"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Create an issue in your team organisation. " + + "Parameters: " + + PREFIX_TITLE + "TITLE " + + PREFIX_ASSIGNEES + "ASSIGNEES " + + PREFIX_MILESTONE + "MILESTONE " + + PREFIX_BODY + "BODY " + + PREFIX_LABEL + "LABELS/n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_TITLE + "Add new create issue command " + + PREFIX_ASSIGNEES + "johndoe " + + PREFIX_MILESTONE + "v1.1 " + + PREFIX_BODY + "This is a test issue " + + PREFIX_LABEL + "bug"; + public static final String MESSAGE_SUCCESS = "Issue successfully created on Github"; + public static final String MESSAGE_FAILURE = "Please log into github first"; + + private final Issue toCreate; + + /** + * Creates an CreateIssueCommand to create the specified {@code Issue} + */ + public CreateIssueCommand(Issue issue) { + requireNonNull(issue); + toCreate = issue; + } + @Override + public CommandResult execute() throws CommandException { + + try { + model.createIssueOnGitHub(toCreate); + return new CommandResult(MESSAGE_SUCCESS); + } catch (IOException | CommandException e) { + throw new CommandException(MESSAGE_FAILURE); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof CreateIssueCommand // instanceof handles nulls + && toCreate.equals(((CreateIssueCommand) other).toCreate)); + } +} +``` +###### \java\seedu\progresschecker\logic\commands\EditIssueCommand.java +``` java +/** + * Edits the details of an existing issue on Github. + */ +public class EditIssueCommand extends Command { + public static final String COMMAND_WORD = "editissue"; + public static final String COMMAND_ALIAS = "edi"; + public static final String COMMAND_FORMAT = COMMAND_WORD + " " + "INDEX " + + "[" + PREFIX_TITLE + "TITLE] " + + "[" + PREFIX_ASSIGNEES + "ASSIGNEES] " + + "[" + PREFIX_MILESTONE + "MILESTONE] " + + "[" + PREFIX_BODY + "BODY] " + + "[" + PREFIX_LABEL + "LABEL]..."; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of an existing issue " + + "by the index number used in the issue listing. " + + "Existing values will be overwritten by the input values.\n" + + "Parameters: INDEX (must be a positive integer) " + + "[" + PREFIX_TITLE + "TITLE] " + + "[" + PREFIX_ASSIGNEES + "ASSIGNEES] " + + "[" + PREFIX_MILESTONE + "MILESTONE] " + + "[" + PREFIX_BODY + "BODY] " + + "[" + PREFIX_LABEL + "LABEL]..." + + "[" + PREFIX_TAG + "TAG]...\n" + + "Example: " + COMMAND_WORD + " 5 " + + PREFIX_TITLE + "Make a new attribute " + + PREFIX_MILESTONE + "v1.3"; + + public static final String MESSAGE_EDIT_ISSUE_SUCCESS = "Issue #%d was successfully edited."; + public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; + public static final String MESSAGE_ISSUE_INVALID = "Issue doesn't exist, enter correct index"; + + private final String repoName = new String("AdityaA1998/samplerepo-pr-practice"); + private final String userLogin = new String("anminkang"); + private final String userAuthentication = new String("aditya2018"); + + private final Index index; + private final EditIssueCommand.EditIssueDescriptor editIssueDescriptor; + + private Issue issueToEdit; + private Issue editedIssue; + + /** + * @param index of the issue on github that is to be edited + * @param editIssueDescriptor details to edit the issue with + */ + public EditIssueCommand(Index index, EditIssueCommand.EditIssueDescriptor editIssueDescriptor) + throws CommandException, IOException { + requireNonNull(index); + requireNonNull(editIssueDescriptor); + + this.index = index; + this.editIssueDescriptor = new EditIssueCommand.EditIssueDescriptor(editIssueDescriptor); + preprocess(); + } + + @Override + public CommandResult execute() throws CommandException { + try { + model.updateIssue(index, editedIssue); + } catch (IOException io) { + throw new CommandException(io.getLocalizedMessage()); + } + return new CommandResult(String.format(MESSAGE_EDIT_ISSUE_SUCCESS, index.getOneBased())); + } + + /** + * Preprocess data for existing issue + * @throws CommandException is thrown when invalid issue index is used + * @throws IOException when the authentication fails + */ + private void preprocess() throws CommandException, IOException { + GitHub github = GitHub.connectUsingPassword(userLogin, userAuthentication); + GHRepository repository = github.getRepository(repoName); + GHIssue issue; + try { + issue = repository.getIssue(index.getOneBased()); + } catch (IOException ie) { + throw new CommandException(MESSAGE_ISSUE_INVALID); + } + List gitAssigneeList = issue.getAssignees(); + ArrayList gitLabelsList = new ArrayList<>(issue.getLabels()); + List assigneesList = new ArrayList<>(); + List labelsList = new ArrayList<>(); + Milestone existingMilestone = null; + Body existingBody = new Body(""); + + if (issue.getMilestone() == null) { + existingMilestone = null; + } else { + existingMilestone = new Milestone(issue.getMilestone().getTitle()); + } + + for (int i = 0; i < gitAssigneeList.size(); i++) { + assigneesList.add(new Assignees(gitAssigneeList.get(i).getLogin())); + } + + for (int i = 0; i < labelsList.size(); i++) { + labelsList.add(new Labels(gitLabelsList.get(i).getName())); + } + + issueToEdit = new Issue(new Title(issue.getTitle()), assigneesList, existingMilestone, + existingBody, labelsList); + editedIssue = createEditedIssue(issueToEdit, editIssueDescriptor); + + } + /** + * Creates and returns a {@code Issue} with the details of {@code issueToEdit} + * edited with {@code editIssueDescriptor}. + */ + private static Issue createEditedIssue(Issue issueToEdit, + EditIssueCommand.EditIssueDescriptor editIssueDescriptor) { + assert issueToEdit != null; + + Title updatedTitle = editIssueDescriptor.getTitle().orElse(issueToEdit.getTitle()); + Set updatedAssignees = editIssueDescriptor.getAssignees() + .orElse(new HashSet<>(issueToEdit.getAssignees())); + Milestone updatedMilestone = editIssueDescriptor.getMilestone().orElse(issueToEdit.getMilestone()); + Body updatedBody = editIssueDescriptor.getBody().orElse(issueToEdit.getBody()); + Set updatedLabels = editIssueDescriptor.getLabels().orElse(new HashSet<>(issueToEdit.getLabelsList())); + + List updatedAssigneesList = new ArrayList<>(updatedAssignees); + List updatedLabelsList = new ArrayList<>(updatedLabels); + + return new Issue(updatedTitle, updatedAssigneesList, updatedMilestone, updatedBody, updatedLabelsList); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditIssueCommand)) { + return false; + } + + // state check + EditIssueCommand e = (EditIssueCommand) other; + return index.equals(e.index) + && editIssueDescriptor.equals(e.editIssueDescriptor) + && Objects.equals(issueToEdit, e.issueToEdit); + } + + /** + * Stores the details to edit the issue with. Each non-empty field value will replace the + * corresponding field value of the Issue. + */ + public static class EditIssueDescriptor { + private Title title; + private Set assignees; + private Milestone milestone; + private Body body; + private Set labels; + + public EditIssueDescriptor() {} + + /** + * Copy constructor. + * A defensive copy of {@code labels} is used internally. + */ + public EditIssueDescriptor(EditIssueCommand.EditIssueDescriptor toCopy) { + setTitle(toCopy.title); + setAssignees(toCopy.assignees); + setMilestone(toCopy.milestone); + setBody(toCopy.body); + setLabels(toCopy.labels); + } + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(this.title, this.assignees, this.milestone, this.body, + this.labels); + } + + public void setTitle(Title title) { + this.title = title; + } + + public Optional getTitle() { + return Optional.ofNullable(title); + } + + /** + * Sets {@code assignees} to this object's {@code assignees}. + * A defensive copy of {@code assignees} is used internally. + */ + public void setAssignees(Set<Assignees> assignees) { + this.assignees = (assignees != null) ? new HashSet<>(assignees) : null; + } + + /** + * Returns an unmodifiable assignees set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * Returns {@code Optional#empty()} if {@code labels} is null. + */ + public Optional<Set<Assignees>> getAssignees() { + return (assignees != null) ? Optional.of(Collections.unmodifiableSet(assignees)) : Optional.empty(); + } + + public void setMilestone(Milestone milestone) { + this.milestone = milestone; + } + + public Optional<Milestone> getMilestone() { + return Optional.ofNullable(milestone); + } + + public void setBody(Body body) { + this.body = body; + } + + public Optional<Body> getBody() { + return Optional.ofNullable(body); + } + + /** + * Sets {@code labels} to this object's {@code labels}. + * A defensive copy of {@code labels} is used internally. + */ + public void setLabels(Set<Labels> labels) { + this.labels = (labels != null) ? new HashSet<>(labels) : null; + } + + /** + * Returns an unmodifiable labels set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * Returns {@code Optional#empty()} if {@code labels} is null. + */ + public Optional<Set<Labels>> getLabels() { + return (labels != null) ? Optional.of(Collections.unmodifiableSet(labels)) : Optional.empty(); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditCommand.EditPersonDescriptor)) { + return false; + } + + // state check + EditIssueCommand.EditIssueDescriptor e = (EditIssueCommand.EditIssueDescriptor) other; + + return getTitle().equals(e.getTitle()) + && getAssignees().equals(e.getAssignees()) + && getMilestone().equals(e.getMilestone()) + && getBody().equals(e.getBody()) + && getLabels().equals(e.getLabels()); + } + } +} +``` +###### \java\seedu\progresschecker\logic\commands\GitLoginCommand.java +``` java +/** + * Logins into github from app for issue creation + */ +public class GitLoginCommand extends Command { + + public static final String COMMAND_WORD = "gitlogin"; + public static final String COMMAND_ALIAS = "gl"; + public static final String COMMAND_FORMAT = COMMAND_WORD + " " + + PREFIX_GIT_USERNAME + "USERNAME " + + PREFIX_GIT_PASSCODE + "PASSCODE " + + PREFIX_GIT_REPO + "REPOSITORY "; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Logs into github \n" + + "Parameters: " + + PREFIX_GIT_USERNAME + "USERNAME " + + PREFIX_GIT_PASSCODE + "PASSCODE " + + PREFIX_GIT_REPO + "REPOSITORY \n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_GIT_USERNAME + "johndoe " + + PREFIX_GIT_PASSCODE + "dummy123 " + + PREFIX_GIT_REPO + "CS2103/main "; + public static final String MESSAGE_SUCCESS = "You have successfully authenticated github!"; + public static final String MESSAGE_FAILURE = "Oops? Maybe the password or the username is incorrect"; + + private final GitDetails toAuthenticate; + + /** + * Creates an GitDetails object to authenticate with github {@code GitDetails} + */ + public GitLoginCommand(GitDetails gitDetails) { + requireNonNull(gitDetails); + toAuthenticate = gitDetails; + } + + @Override + public CommandResult execute() throws CommandException { + + try { + model.loginGithub(toAuthenticate); + return new CommandResult(MESSAGE_SUCCESS); + } catch (IOException e) { + throw new CommandException(MESSAGE_FAILURE); + } catch (CommandException ce) { + throw new CommandException(ce.getMessage()); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof GitLoginCommand // instanceof handles nulls + && toAuthenticate.equals(((GitLoginCommand) other).toAuthenticate)); + } + +} +``` +###### \java\seedu\progresschecker\logic\commands\GitLogoutCommand.java +``` java +/** + * Logs out of github + */ +public class GitLogoutCommand extends Command { + + public static final String COMMAND_WORD = "gitlogout"; + public static final String COMMAND_ALIAS = "glo"; + public static final String COMMAND_FORMAT = COMMAND_WORD; + + public static final String MESSAGE_USAGE = COMMAND_WORD; + public static final String MESSAGE_SUCCESS = "You have successfully logged out of github!"; + public static final String MESSAGE_FAILURE = "You are currently not logged in"; + + @Override + public CommandResult execute() throws CommandException { + + try { + model.logoutGithub(); + return new CommandResult(MESSAGE_SUCCESS); + } catch (CommandException e) { + throw new CommandException(MESSAGE_FAILURE); + } + } + +} +``` +###### \java\seedu\progresschecker\logic\commands\ListIssuesCommand.java +``` java +/** + * Finds and lists all issues from github with the specified state in the argument. + * Keyword matching is case insensitive. + */ +public class ListIssuesCommand extends Command { + + public static final String COMMAND_WORD = "listissue"; + public static final String COMMAND_ALIAS = "lis"; + public static final String COMMAND_FORMAT = COMMAND_WORD + " STATE"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Lists all the issues " + + "of the specified state with the respective github issue index.\n" + + "Parameters: KEYWORD\n" + + "Example: " + COMMAND_WORD + " CLOSE"; + private static final String MESSAGE_INVALID_STATE = "Please enter correct issue state"; + private static final String MESSAGE_VALIDATION_FAILURE = "Please log into github first"; + private static final String tabType = "issues"; + private static final String MESSAGE_SUCCESS = "All the %s issues are being viewed"; + + private static String state; + + public ListIssuesCommand(String state) { + this.state = state; + } + + @Override + public CommandResult execute() throws CommandException { + try { + model.listIssues(state); + EventsCenter.getInstance().post(new TabLoadChangedEvent(tabType)); + return new CommandResult(String.format(MESSAGE_SUCCESS, state)); + } catch (IllegalValueException ie) { + throw new CommandException(MESSAGE_INVALID_STATE); + } catch (IOException ie) { + throw new CommandException(MESSAGE_INVALID_STATE); + } catch (CommandException ce) { + throw new CommandException(MESSAGE_VALIDATION_FAILURE); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ListIssuesCommand // instanceof handles nulls + && this.state.equals(((ListIssuesCommand) other).state)); // state check + } +} +``` +###### \java\seedu\progresschecker\logic\commands\ReopenIssueCommand.java +``` java +/** + * Reopens an issue on github + */ +public class ReopenIssueCommand extends Command { + + public static final String COMMAND_WORD = "reopenissue"; + public static final String COMMAND_ALIAS = "ri"; + public static final String COMMAND_FORMAT = COMMAND_WORD + " ISSUE-INDEX"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + "\nParameters: ISSUE_INDEX (must be a positive valid index number)" + + "Example: \n" + COMMAND_WORD + " 2"; + + public static final String MESSAGE_SUCCESS = "Issue #%1$s was reopened successfully"; + public static final String MESSAGE_FAILURE = "Issue wasn't reopened. Enter correct index number."; + public static final String MESSAGE_AUTHENTICATION_FAILURE = "Github isn't authenticated. " + + "Use 'gitlogin' command to authenticate first"; + + private final Index targetIndex; + + public ReopenIssueCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute() throws CommandException { + try { + model.reopenIssueOnGithub(targetIndex); + } catch (IOException ie) { + throw new CommandException(MESSAGE_FAILURE); + } catch (CommandException ce) { + throw new CommandException(MESSAGE_AUTHENTICATION_FAILURE); + } + + return new CommandResult(String.format(MESSAGE_SUCCESS, targetIndex.getOneBased())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ReopenIssueCommand // instanceof handles nulls + && this.targetIndex.equals(((ReopenIssueCommand) other).targetIndex)); // state check + } +} + +``` +###### \java\seedu\progresschecker\logic\LogicManager.java +``` java + @Override + public ObservableList<Issue> getFilteredIssueList() { + return model.getFilteredIssueList(); + } + +``` +###### \java\seedu\progresschecker\logic\parser\CloseIssueCommandParser.java +``` java +/** + * Parses input arguments and creates a new CloseIssueCommand object + */ +public class CloseIssueCommandParser implements Parser<CloseIssueCommand> { + + /** + * Parses the given {@code String} of arguments in the context of the CloseIssueCommand + * and returns an CloseIssueCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public CloseIssueCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new CloseIssueCommand(index); + } catch (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, CloseIssueCommand.MESSAGE_USAGE)); + } + } +} + +``` +###### \java\seedu\progresschecker\logic\parser\CreateIssueParser.java +``` java +/** + * Parses input arguments and creates a new CreateIssueCommand object + */ +public class CreateIssueParser implements Parser<CreateIssueCommand> { + + /** + * Parses the given {@code String} of arguments in the context of the CreateIssueCommand + * and returns an createIssue object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public CreateIssueCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_TITLE, PREFIX_ASSIGNEES, + PREFIX_MILESTONE, PREFIX_BODY, PREFIX_LABEL); + + if (!arePrefixesPresent(argMultimap, PREFIX_TITLE) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, CreateIssueCommand.MESSAGE_USAGE)); + } + + try { + Title title = ParserUtil.parseTitle(argMultimap.getValue(PREFIX_TITLE)).get(); + Set<Assignees> assigneeSet = ParserUtil.parseAssignees(argMultimap.getAllValues(PREFIX_ASSIGNEES)); + Milestone milestone = ParserUtil.parseMilestone(argMultimap.getValue(PREFIX_MILESTONE)).orElse(null); + Body body = ParserUtil.parseBody(argMultimap.getValue(PREFIX_BODY).orElse("")); + Set<Labels> labelSet = ParserUtil.parseLabels(argMultimap.getAllValues(PREFIX_LABEL)); + + List<Assignees> assigneesList = new ArrayList<>(assigneeSet); + List<Labels> labelsList = new ArrayList<>(labelSet); + + Issue issue = new Issue(title, assigneesList, milestone, body, labelsList); + + return new CreateIssueCommand(issue); + } catch (IllegalValueException ive) { + throw new ParseException(ive.getMessage(), ive); + } + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + +} +``` +###### \java\seedu\progresschecker\logic\parser\EditIssueCommandParser.java +``` java +/** + * Parses input arguments and creates a new EditIssueCommand object + */ +public class EditIssueCommandParser implements Parser<EditIssueCommand> { + + /** + * Parses the given {@code String} of arguments in the context of the EditIssueCommand + * and returns an EditIssueCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public EditIssueCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_TITLE, PREFIX_ASSIGNEES, PREFIX_MILESTONE, PREFIX_BODY, + PREFIX_LABEL); + + Index index; + + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (IllegalValueException ive) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditIssueCommand.MESSAGE_USAGE)); + } + + EditIssueCommand.EditIssueDescriptor editIssueDescriptor = new EditIssueCommand.EditIssueDescriptor(); + try { + ParserUtil.parseTitle(argMultimap.getValue(PREFIX_TITLE)).ifPresent(editIssueDescriptor::setTitle); + parseAssigneesForEdit(argMultimap.getAllValues(PREFIX_ASSIGNEES)) + .ifPresent(editIssueDescriptor::setAssignees); + ParserUtil.parseMilestone(argMultimap.getValue(PREFIX_MILESTONE)) + .ifPresent(editIssueDescriptor::setMilestone); + ParserUtil.parseBody(argMultimap.getValue(PREFIX_BODY)) + .ifPresent(editIssueDescriptor::setBody); + parseLabelsForEdit(argMultimap.getAllValues(PREFIX_LABEL)).ifPresent(editIssueDescriptor::setLabels); + } catch (IllegalValueException ive) { + throw new ParseException(ive.getMessage(), ive); + } + + if (!editIssueDescriptor.isAnyFieldEdited()) { + throw new ParseException(EditIssueCommand.MESSAGE_NOT_EDITED); + } + + try { + return new EditIssueCommand(index, editIssueDescriptor); + } catch (CommandException ce) { + throw new ParseException(EditIssueCommand.MESSAGE_NOT_EDITED); + } catch (IOException ie) { + throw new ParseException(EditIssueCommand.MESSAGE_NOT_EDITED); + } + } + + /** + * Parses {@code Collection<String> labels} into a {@code Set<Labels>} if {@code labels} is non-empty. + * If {@code labels} contain only one element which is an empty string, it will be parsed into a + * {@code Set<Labels>} containing zero labels. + */ + private Optional<Set<Labels>> parseLabelsForEdit(Collection<String> labels) throws IllegalValueException { + assert labels != null; + + if (labels.isEmpty()) { + return Optional.empty(); + } + Collection<String> labelSet = labels.size() == 1 && labels.contains("") ? Collections.emptySet() : labels; + return Optional.of(ParserUtil.parseLabels(labels)); + } + + /** + * Parses {@code Collection<String> assignees} into a {@code Set<Assignees>} if {@code assignees} is non-empty. + * If {@code assignees} contain only one element which is an empty string, it will be parsed into a + * {@code Set<Assignees>} containing zero assignees. + */ + private Optional<Set<Assignees>> parseAssigneesForEdit(Collection<String> assignees) throws IllegalValueException { + assert assignees != null; + + if (assignees.isEmpty()) { + return Optional.empty(); + } + Collection<String> assigneesSet = assignees.size() == 1 + && assignees.contains("") ? Collections.emptySet() : assignees; + return Optional.of(ParserUtil.parseAssignees(assignees)); + } +} +``` +###### \java\seedu\progresschecker\logic\parser\GitLoginCommandParser.java +``` java +/** + * Parses input arguments and creates a new GitDetails object + */ +public class GitLoginCommandParser implements Parser<GitLoginCommand> { + + /** + * Parses the given {@code String} of arguments in the context of the GitLoginCommand + * and returns an GitLoginCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public GitLoginCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_GIT_USERNAME, PREFIX_GIT_PASSCODE, PREFIX_GIT_REPO); + + if (!arePrefixesPresent(argMultimap, PREFIX_GIT_USERNAME, PREFIX_GIT_PASSCODE, PREFIX_GIT_REPO) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, GitLoginCommand.MESSAGE_USAGE)); + } + + try { + Username username = ParserUtil.parseGitUsername(argMultimap.getValue(PREFIX_GIT_USERNAME)).get(); + Passcode passcode = ParserUtil.parsePasscode(argMultimap.getValue(PREFIX_GIT_PASSCODE)).get(); + Repository repository = ParserUtil.parseRepository(argMultimap.getValue(PREFIX_GIT_REPO)).get(); + + GitDetails details = new GitDetails(username, passcode, repository); + + return new GitLoginCommand(details); + } catch (IllegalValueException ive) { + throw new ParseException(ive.getMessage(), ive); + } + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + +} +``` +###### \java\seedu\progresschecker\logic\parser\ListIssuesCommandParser.java +``` java +/** + * Parses input arguments and creates a new ListIssuesCommand object + */ +public class ListIssuesCommandParser implements Parser<ListIssuesCommand> { + + /** + * Parses the given {@code String} of arguments in the context of the ListIssueCommand + * and returns an ListIssueCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public ListIssuesCommand parse(String args) throws ParseException { + try { + String issueState = ParserUtil.parseStateType(args); + return new ListIssuesCommand(issueState); + } catch (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ListIssuesCommand.MESSAGE_USAGE)); + } + } +} +``` +###### \java\seedu\progresschecker\logic\parser\ParserUtil.java +``` java + /** + * Parses a {@code String title} into a {@code Title}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code title} is invalid. + */ + + public static Title parseTitle(String title) throws IllegalValueException { + requireNonNull(title); + String trimmedTitle = title.trim(); + if (!Title.isValidTitle(trimmedTitle)) { + throw new IllegalValueException(Title.MESSAGE_TITLE_CONSTRAINTS); + } + return new Title(trimmedTitle); + } + + /** + * Parses a {@code Optional<String> title} into an {@code Optional<Name>} if {@code title} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional<Title> parseTitle(Optional<String> title) throws IllegalValueException { + requireNonNull(title); + return title.isPresent() ? Optional.of(parseTitle(title.get())) : Optional.empty(); + } + + /** + * Parses a {@code String assignees} into a {@code Assignees}. + * Leading and trailing whitespaces will be trimmed. + */ + + public static Assignees parseAssignees(String assignees) throws IllegalValueException { + requireNonNull(assignees); + String trimmedAssignees = assignees.trim(); + if (!Assignees.isValidAssignee(trimmedAssignees)) { + throw new IllegalValueException(Assignees.MESSAGE_ASSIGNEES_CONSTRAINTS); + } + return new Assignees(trimmedAssignees); + } + + /** + * Parses {@code Collection<String> assignees} into a {@code Set<Assignees>}. + */ + public static Set<Assignees> parseAssignees(Collection<String> assignees) throws IllegalValueException { + requireNonNull(assignees); + final Set<Assignees> assigneesSet = new HashSet<>(); + for (String assigneeName : assignees) { + assigneesSet.add(parseAssignees(assigneeName)); + } + return assigneesSet; + } + + /** + * Parses a {@code String labels} into a {@code Labels}. + * Leading and trailing whitespaces will be trimmed. + */ + + public static Labels parseLabels(String labels) throws IllegalValueException { + requireNonNull(labels); + String trimmedLabels = labels.trim(); + if (!Labels.isValidLabel(trimmedLabels)) { + throw new IllegalValueException(Labels.MESSAGE_LABEL_CONSTRAINTS); + } + return new Labels(trimmedLabels); + } + + /** + * Parses {@code Collection<String> labels} into a {@code Set<Labels>}. + */ + public static Set<Labels> parseLabels(Collection<String> labels) throws IllegalValueException { + requireNonNull(labels); + final Set<Labels> labelsSet = new HashSet<>(); + for (String labelName : labels) { + labelsSet.add(parseLabels(labelName)); + } + return labelsSet; + } + + + /** + * Parses a {@code String milestone} into a {@code Milestone}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code milestone} is invalid. + */ + + public static Milestone parseMilestone(String milestone) throws IllegalValueException { + requireNonNull(milestone); + String trimmedMilestone = milestone.trim(); + return new Milestone(trimmedMilestone); + } + + /** + * Parses a {@code Optional<String> milestone} into an {@code Optional<Milestone>} if {@code milestone} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional<Milestone> parseMilestone(Optional<String> milestone) throws IllegalValueException { + requireNonNull(milestone); + return milestone.isPresent() ? Optional.of(parseMilestone(milestone.get())) : Optional.empty(); + } + + /** + * Parses a {@code String body} into a {@code Body}. + * Leading and trailing whitespaces will be trimmed. + */ + + public static Body parseBody(String body) throws IllegalValueException { + requireNonNull(body); + String trimmedBody = body.trim(); + if (!Body.isValidBody(trimmedBody)) { + throw new IllegalValueException(Body.MESSAGE_BODY_CONSTRAINTS); + } + return new Body(trimmedBody); + } + + /** + * Parses a {@code Optional<String> body} into an {@code Optional<Body>} if {@code body} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional<Body> parseBody(Optional<String> body) throws IllegalValueException { + requireNonNull(body); + return body.isPresent() ? Optional.of(parseBody(body.get())) : Optional.empty(); + } + + /** + * Parses a {@code String username} into a {@code Username}. + * Leading and trailing whitespaces will be trimmed. + */ + public static Username parseGitUsername(String username) throws IllegalValueException { + requireNonNull(username); + String trimmedUsername = username.trim(); + if (!Username.isValidUsername(trimmedUsername)) { + throw new IllegalValueException(Username.MESSAGE_GITUSERNAME_CONSTRAINTS); + } + return new Username(trimmedUsername); + } + + /** + Parses a {@code Optional<String> username} into an {@code Optional<Username>} if {@code username} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional<Username> parseGitUsername(Optional<String> username) throws IllegalValueException { + requireNonNull(username); + return username.isPresent() ? Optional.of(parseGitUsername(username.get())) : Optional.empty(); + } + + /** + * Parses a {@code String passcode} into a {@code Passcode}. + */ + public static Passcode parsePasscode(String passcode) throws IllegalValueException { + requireNonNull(passcode); + if (!Passcode.isValidPasscode(passcode)) { + throw new IllegalValueException(Passcode.MESSAGE_PASSCODE_CONSTRAINTS); + } + return new Passcode(passcode); + } + + /** + Parses a {@code Optional<String> Passcode} into an {@code Optional<Passcode>} if {@code passcpde} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional<Passcode> parsePasscode(Optional<String> passcode) throws IllegalValueException { + requireNonNull(passcode); + return passcode.isPresent() ? Optional.of(parsePasscode(passcode.get())) : Optional.empty(); + } + + /** + * Parses a {@code String repositroy} into a {@code Repository}. + * Leading and trailing whitespaces will be trimmed. + */ + public static Repository parseRepository(String repository) throws IllegalValueException { + requireNonNull(repository); + String trimmedRepository = repository.trim(); + if (!Repository.isValidRepository(trimmedRepository)) { + throw new IllegalValueException(Repository.MESSAGE_REPOSITORY_CONSTRAINTS); + } + return new Repository(trimmedRepository); + } + + /** + Parses a {@code Optional<String> Repository} into an {@code Optional<Repository>} if {@code repository} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional<Repository> parseRepository(Optional<String> repository) throws IllegalValueException { + requireNonNull(repository); + return repository.isPresent() ? Optional.of(parseRepository(repository.get())) : Optional.empty(); + } + + /** + * Parses a {@code String state} into a trimmed string. + * Leading and trailing whitespaces will be trimmed. + */ + public static String parseStateType(String state) throws IllegalValueException { + requireNonNull(state); + String trimmedState = state.trim(); + return trimmedState; + } +``` +###### \java\seedu\progresschecker\logic\parser\ReopenIssueCommandParser.java +``` java +/** + * Parses input arguments and creates a new CloseIssueCommand object + */ +public class ReopenIssueCommandParser implements Parser<ReopenIssueCommand> { + + /** + * Parses the given {@code String} of arguments in the context of the ReopenIssueCommand + * and returns an ReopenIssueCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public ReopenIssueCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new ReopenIssueCommand(index); + } catch (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ReopenIssueCommand.MESSAGE_USAGE)); + } + } +} + +``` +###### \java\seedu\progresschecker\model\credentials\GitDetails.java +``` java +/** + * Represents an Issue. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class GitDetails { + + private final Username username; + private final Repository repository; + private final Passcode passcode; + + /** + * Every field must be present and not null. + */ + public GitDetails(Username username, Passcode passcode, Repository repository) { + requireAllNonNull(username, repository, passcode); + this.username = username; + this.repository = repository; + this.passcode = passcode; + } + + public Username getUsername() { + return username; + } + + public Repository getRepository() { + return repository; + } + + public Passcode getPasscode() { + return passcode; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof seedu.progresschecker.model.credentials.GitDetails)) { + return false; + } + + seedu.progresschecker.model.credentials.GitDetails otherGitDetails = + (seedu.progresschecker.model.credentials.GitDetails) other; + return otherGitDetails.getUsername().equals(this.getUsername()) + && otherGitDetails.getRepository().equals(this.getRepository()) + && otherGitDetails.getPasscode().equals(this.getPasscode()); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(username, repository, passcode); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append(" Username: ") + .append(getUsername()) + .append(" Repository: ") + .append(getRepository()); + return builder.toString(); + } + +} + +``` +###### \java\seedu\progresschecker\model\credentials\Passcode.java +``` java +/** + * Represents a github passcode + */ +public class Passcode { + + public static final String MESSAGE_PASSCODE_CONSTRAINTS = + "Passcode must contain atleast one lower case character, one numeral " + + "and should be atleast 7 characters long"; + + /* + * Password must contain one lowercase character, + * one number and minimum 7 characters + */ + public static final String PASSCODE_VALIDATION_REGEX = "((?=.*\\d)(?=.*[a-z]).{7,100})"; + + public final String passcode; + + /** + * Constructs a {@code Passcode}. + * + * @param passcode A valid assignees. + */ + public Passcode(String passcode) { + requireNonNull(passcode); + checkArgument(isValidPasscode(passcode), MESSAGE_PASSCODE_CONSTRAINTS); + this.passcode = passcode; + } + + /** + * Returns true if a given string is a valid github passcode. + */ + public static boolean isValidPasscode(String test) { + return test.matches(PASSCODE_VALIDATION_REGEX); + } + + @Override + public String toString() { + return passcode; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof seedu.progresschecker.model.credentials.Passcode // instanceof handles nulls + && this.passcode.equals(((Passcode) other).passcode)); // state check + } + + @Override + public int hashCode() { + return passcode.hashCode(); + } + +} + +``` +###### \java\seedu\progresschecker\model\credentials\Username.java +``` java +/** + * Represents the username of a user on github + */ +public class Username { + public static final String MESSAGE_GITUSERNAME_CONSTRAINTS = + "Username should only contain alphanumeric characters, and it should not be blank"; + + /* + * The github username can only contain alphanumeric characters, + * with no continuous special characters. + */ + public static final String USERNAME_VALIDATION_REGEX = "^[-a-zA-Z0-9+&@#/%?=~_|!:,.;*]*[-a-zA-Z0-9+&@#/%=~_|*]"; + + public final String username; + + /** + * Constructs a {@code Username}. + * + * @param username A valid username. + */ + public Username(String username) { + requireNonNull(username); + checkArgument(isValidUsername(username), MESSAGE_GITUSERNAME_CONSTRAINTS); + this.username = username; + } + + /** + * Returns true if a given string is a valid github issue. + */ + public static boolean isValidUsername(String test) { + return test.matches(USERNAME_VALIDATION_REGEX); + } + + @Override + public String toString() { + return username; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof seedu.progresschecker.model.credentials.Username // instanceof handles nulls + && this.username.equals(((Username) other).username)); // state check + } + + @Override + public int hashCode() { + return username.hashCode(); + } + +} +``` +###### \java\seedu\progresschecker\model\issues\Assignees.java +``` java +/** + * Represents all the assignees to an issue + */ +public class Assignees { + + public static final String MESSAGE_ASSIGNEES_CONSTRAINTS = + "Assignees of the issue can be anything, but should not be blank space"; + + /* + * The first character of the Assignee must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String ASSIGNEE_VALIDATION_REGEX = ".*\\w.*|[$&+,:;=?@#|'<>.^*()%!-]"; + + public final String fullAssignees; + + /** + * Constructs a {@code Assignees}. + * + * @param assignees A valid assignees. + */ + public Assignees(String assignees) { + requireNonNull(assignees); + checkArgument(isValidAssignee(assignees), MESSAGE_ASSIGNEES_CONSTRAINTS); + this.fullAssignees = assignees; + } + + /** + * Returns true if a given string is a valid github issue. + */ + public static boolean isValidAssignee(String test) { + return test.matches(ASSIGNEE_VALIDATION_REGEX); + } + + + @Override + public String toString() { + return fullAssignees; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof seedu.progresschecker.model.issues.Assignees // instanceof handles nulls + && this.fullAssignees.equals(((Assignees) other).fullAssignees)); // state check + } + + @Override + public int hashCode() { + return fullAssignees.hashCode(); + } + +} +``` +###### \java\seedu\progresschecker\model\issues\Body.java +``` java +/** + * Represents an issue's name and description + */ +public class Body { + + public static final String MESSAGE_BODY_CONSTRAINTS = + "Issue should only contain non-null body"; + + public final String fullBody; + + /** + * Constructs a {@code Body}. + * + * @param body A valid issue description. + */ + public Body(String body) { + requireNonNull(body); + this.fullBody = body; + } + + /** + * Returns true if a given string is a valid github body. + */ + public static boolean isValidBody(String test) { + return (test != null) ? true : false; + } + + @Override + public String toString() { + return fullBody; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof seedu.progresschecker.model.issues.Body // instanceof handles nulls + && this.fullBody.equals(((Body) other).fullBody)); // state check + } + + @Override + public int hashCode() { + return fullBody.hashCode(); + } + +} +``` +###### \java\seedu\progresschecker\model\issues\GitIssueList.java +``` java +/** + * A list of persons that enforces uniqueness between its elements and does not allow nulls. + * + * Supports a minimal set of list operations. + * + * @see Person#equals(Object) + * @see CollectionUtil#elementsAreUnique(Collection) + */ +public class GitIssueList implements Iterable<Issue> { + + private final ObservableList<Issue> internalList = FXCollections.observableArrayList(); + private String repoName; + private String userLogin; + private String userAuthentication; + private GitHub github; + private GHRepository repository; + private GHIssueBuilder issueBuilder; + private GHIssue issue; + private GHIssue toEdit; + private GHIssueState issueState; + + /** + * Initialises github credentials + */ + public void initialiseCredentials(GitDetails gitdetails) throws CommandException, IOException { + repoName = gitdetails.getRepository().toString(); + userLogin = gitdetails.getUsername().toString(); + userAuthentication = gitdetails.getPasscode().toString(); + authoriseGithub(); + } + + /** + * Authorises with github + */ + private void authoriseGithub () throws CommandException, IOException { + if (github != null) { + throw new CommandException("You have already logged in as " + userLogin + ". Please logout first."); + } + try { + github = GitHub.connectUsingPassword(userLogin, userAuthentication); + if (!github.isCredentialValid()) { + github = null; + throw new IOException(); + } + } catch (IOException ie) { + throw new CommandException("Enter correct username and password"); + } + try { + repository = github.getRepository(repoName); + } catch (IOException ie) { + throw new CommandException("Enter correct repository name"); + } + updateInternalList(); + } + + /** + * Updates the internal list by fetching data from github + */ + private void updateInternalList() throws IOException { + internalList.remove(0, internalList.size()); + List<GHIssue> gitIssues = repository.getIssues(issueState); + for (GHIssue issueOnGit : gitIssues) { + Issue toBeAdded = convertToIssue(issueOnGit); + internalList.add(toBeAdded); + } + } + + /** + * Converts GHIssue to issue + */ + private Issue convertToIssue(GHIssue i) throws IOException { + + List<GHUser> gitAssigneeList = i.getAssignees(); + ArrayList<GHLabel> gitLabelsList = new ArrayList<>(i.getLabels()); + List<Assignees> assigneesList = new ArrayList<>(); + List<Labels> labelsList = new ArrayList<>(); + Milestone existingMilestone = null; + Body existingBody = new Body(i.getBody()); + Title title = new Title(i.getTitle()); + Issue issue; + + if (i.getMilestone() == null) { + existingMilestone = new Milestone(""); + } else { + existingMilestone = new Milestone(i.getMilestone().getTitle()); + } + + for (GHUser assignee : gitAssigneeList) { + assigneesList.add(new Assignees(assignee.getLogin())); + } + + for (GHLabel label : gitLabelsList) { + labelsList.add(new Labels(label.getName())); + } + + issue = new Issue(title, assigneesList, existingMilestone, + existingBody, labelsList); + + issue.setIssueIndex(i.getNumber()); + + return issue; + } + + /** + * Creates an issue on github + * + * @throws IOException if there is any problem creating an issue on github; + */ + public void createIssue(Issue toAdd) throws IOException, CommandException { + checkGitAuthentication(); + issueBuilder = repository.createIssue(toAdd.getTitle().toString()); + issueBuilder.body(toAdd.getBody().toString()); + + List<Assignees> assigneesList = toAdd.getAssignees(); + List<Labels> labelsList = toAdd.getLabelsList(); + + ArrayList<GHUser> listOfUsers = new ArrayList<>(); + ArrayList<String> listOfLabels = new ArrayList<>(); + MilestoneMap obj = new MilestoneMap(); + obj.setRepository(getRepository()); + HashMap<String, GHMilestone> milestoneMap = obj.getMilestoneMap(); + GHMilestone check = null; + + for (int ct = 0; ct < assigneesList.size(); ct++) { + listOfUsers.add(github.getUser(assigneesList.get(ct).toString())); + } + + for (int ct = 0; ct < labelsList.size(); ct++) { + listOfLabels.add(labelsList.get(ct).toString()); + } + + if (toAdd.getMilestone() != null) { + if (milestoneMap.get(toAdd.getMilestone().toString()) == null) { + throw new CommandException("Milestone doesn't exist"); + } else { + check = milestoneMap.get(toAdd.getMilestone().toString()); + } + } + GHIssue createdIssue = issueBuilder.create(); + if (check != null) { + createdIssue.setMilestone(check); + } + createdIssue.setAssignees(listOfUsers); + createdIssue.setLabels(listOfLabels.toArray(new String[0])); + updateInternalList(); + } + + /** + * Reopens an issue on github + */ + public void reopenIssue(Index index) throws IOException, CommandException { + checkGitAuthentication(); + issue = repository.getIssue(index.getOneBased()); + if (issue.getState() == GHIssueState.OPEN) { + throw new CommandException("Issue #" + index.getOneBased() + " is already open"); + } + issue.reopen(); + updateInternalList(); + } + + /** + * Closes an issue on github + */ + public void closeIssue(Index index) throws IOException, CommandException { + + checkGitAuthentication(); + issue = repository.getIssue(index.getOneBased()); + if (issue.getState() == GHIssueState.CLOSED) { + throw new CommandException("Issue #" + index.getOneBased() + " is already closed"); + } + issue.close(); + updateInternalList(); + } + /** + * Authorises with github + */ + public void clearCredentials() throws CommandException { + if (github == null) { + throw new CommandException("No one has logged into github at the moment"); + } else { + internalList.remove(0, internalList.size()); + github = null; + } + } + + /** + * Check if the github credentials are authorised + */ + private void checkGitAuthentication() throws CommandException { + if (github == null) { + throw new CommandException("Github not authenticated. " + + "Use 'gitlogin' command to first authenticate your github account"); + } + } + + /** + * Updates the GHIssueState according to mentioned state and updates the list + */ + public void listIssue(String state) throws IllegalValueException, IOException, CommandException { + if (github == null) { + throw new CommandException(""); + } else if (state.equalsIgnoreCase("OPEN")) { + issueState = GHIssueState.OPEN; + } else if (state.equalsIgnoreCase("CLOSED")) { + issueState = GHIssueState.CLOSED; + } else { + throw new IllegalValueException("Enter correct state"); + } + updateInternalList(); + + } + /** + * Replaces the person {@code target} in the list with {@code editedPerson}. + * + * @throws IOException if the replacement is equivalent to another existing person in the list. + */ + public void setIssue(Index index, Issue editedIssue) + throws IOException, CommandException { + requireNonNull(editedIssue); + toEdit = repository.getIssue(index.getOneBased()); + + List<Assignees> assigneesList = editedIssue.getAssignees(); + List<Labels> labelsList = editedIssue.getLabelsList(); + + ArrayList<GHUser> listOfUsers = new ArrayList<>(); + ArrayList<String> listOfLabels = new ArrayList<>(); + MilestoneMap obj = new MilestoneMap(); + obj.setRepository(getRepository()); + HashMap<String, GHMilestone> milestoneMap = obj.getMilestoneMap(); + + for (Assignees assignee : assigneesList) { + listOfUsers.add(github.getUser(assignee.toString())); + } + + for (Labels label : labelsList) { + listOfLabels.add(label.toString()); + } + + if (editedIssue.getMilestone() != null) { + GHMilestone check = milestoneMap.get(editedIssue.getMilestone().toString()); + toEdit.setMilestone(check); + } + toEdit.setTitle(editedIssue.getTitle().toString()); + toEdit.setBody(editedIssue.getBody().toString()); + toEdit.setAssignees(listOfUsers); + if (listOfLabels.size() != 0) { + toEdit.setLabels(listOfLabels.toArray(new String[0])); + } + updateInternalList(); + + } + + /** + * Returns github object + */ + public GitHub getGithub() { + return github; + } + + /** + * Returns github repository + */ + public GHRepository getRepository() { + return repository; + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList<Issue> asObservableList() { + return FXCollections.unmodifiableObservableList(internalList); + } + + @Override + public Iterator<Issue> iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof GitIssueList // instanceof handles nulls + && this.internalList.equals(((GitIssueList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } +} + +``` +###### \java\seedu\progresschecker\model\issues\Issue.java +``` java +/** + * Represents an Issue. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class Issue { + + private final Title title; + private final List<Assignees> assigneesList; + private final Milestone milestone; + private final Body body; + private final List<Labels> labelsList; + private int issueIndex; + + /** + * Every field must be present and not null. + */ + public Issue(Title title, List<Assignees> assigneesList, Milestone milestone, Body body, List<Labels> labelsList) { + requireAllNonNull(title); + this.title = title; + this.assigneesList = assigneesList; + this.milestone = milestone; + this.body = body; + this.labelsList = labelsList; + } + + public Title getTitle() { + return title; + } + + public List<Assignees> getAssignees() { + return assigneesList; + } + + public Milestone getMilestone() { + return milestone; + } + + public Body getBody() { + return body; + } + + public List<Labels> getLabelsList() { + return labelsList; + } + + public int getIssueIndex() { + return issueIndex; + } + + public void setIssueIndex(int issueIndex) { + this.issueIndex = issueIndex; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof seedu.progresschecker.model.issues.Issue)) { + return false; + } + + seedu.progresschecker.model.issues.Issue otherIssue = (seedu.progresschecker.model.issues.Issue) other; + return otherIssue.getTitle().equals(this.getTitle()) + && otherIssue.getAssignees().equals(this.getAssignees()) + && otherIssue.getMilestone().equals(this.getMilestone()) + && otherIssue.getBody().equals(this.getBody()) + && otherIssue.getLabelsList().equals(this.getLabelsList()); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(title, assigneesList, milestone, body, labelsList); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append(getTitle()) + .append(" Assignees: ") + .append(getAssignees()) + .append(" Milestone: ") + .append(getMilestone()) + .append(" Body: ") + .append(getBody()) + .append(" Labels: ") + .append(getLabelsList()); + return builder.toString(); + } + +} + +``` +###### \java\seedu\progresschecker\model\issues\Labels.java +``` java +/** + * Represents all the Labels of an issue + */ +public class Labels { + + public static final String MESSAGE_LABEL_CONSTRAINTS = + "Labels should only contain alphanumeric characters and spaces, and it should not be blank"; + + /* + * The first character of the label must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String LABEL_VALIDATION_REGEX = ".*\\w.*|[$&+,:;=?@#|'<>.^*()%!-]"; + + public final String fullLabels; + + /** + * Constructs a {@code Labels}. + * + * @param labels valid labels. + */ + public Labels(String labels) { + requireNonNull(labels); + checkArgument(isValidLabel(labels), MESSAGE_LABEL_CONSTRAINTS); + this.fullLabels = labels; + } + + /** + * Returns true if a given string is a valid github label. + */ + public static boolean isValidLabel(String test) { + return test.matches(LABEL_VALIDATION_REGEX); + } + + @Override + public String toString() { + return fullLabels; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof seedu.progresschecker.model.issues.Labels // instanceof handles nulls + && this.fullLabels.equals(((Labels) other).fullLabels)); // state check + } + + @Override + public int hashCode() { + return fullLabels.hashCode(); + } + +} +``` +###### \java\seedu\progresschecker\model\issues\Milestone.java +``` java +/** + * Represents a milestone for an issue + */ +public class Milestone { + + public final String fullMilestone; + + /** + * Constructs a {@code Milestone}. + * + * @param milestone A valid milestone. + */ + public Milestone(String milestone) { + //requireNonNull(milestone); + this.fullMilestone = milestone; + } + + @Override + public String toString() { + return fullMilestone; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof seedu.progresschecker.model.issues.Milestone // instanceof handles nulls + && this.fullMilestone.equals(((Milestone) other).fullMilestone)); // state check + } + + @Override + public int hashCode() { + return fullMilestone.hashCode(); + } + +} +``` +###### \java\seedu\progresschecker\model\issues\MilestoneMap.java +``` java +/** + * Initialises and returns a Hashmap of milestones + */ +public final class MilestoneMap { + + private static HashMap<String, GHMilestone> milestoneMap; + + private GHRepository repository; + + /** + * Returns a hashmap of milestones + */ + public HashMap<String, GHMilestone> getMilestoneMap() throws CommandException { + milestoneMap = new HashMap<>(); + createMilestoneHashMap(); + return milestoneMap; + } + + /** + * creates a map with the milestone values + */ + private void createMilestoneHashMap() { + List<GHMilestone> milestones = repository.listMilestones(GHIssueState.ALL).asList(); + for (int i = 0; i < milestones.size(); i++) { + milestoneMap.put(milestones.get(i).getTitle(), milestones.get(i)); + } + } + + public void setRepository(GHRepository repo) { + repository = repo; + } +} +``` +###### \java\seedu\progresschecker\model\issues\Title.java +``` java + +import static java.util.Objects.requireNonNull; +import static seedu.progresschecker.commons.util.AppUtil.checkArgument; + +/** + * Represents an issue's name and description + */ +public class Title { + + public static final String MESSAGE_TITLE_CONSTRAINTS = + "Issue should only contain alphanumeric characters and spaces, and it should not be blank"; + + /* + * The first character of the title must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String TITLE_VALIDATION_REGEX = ".*\\w.*|[$&+,:;=?@#|'<>.^*()%!-]"; + + public final String fullMessage; + + /** + * Constructs a {@code Title}. + * + * @param title A valid description. + */ + public Title(String title) { + requireNonNull(title); + checkArgument(isValidTitle(title), MESSAGE_TITLE_CONSTRAINTS); + this.fullMessage = title; + } + + /** + * Returns true if a given string is a valid github issue. + */ + public static boolean isValidTitle(String test) { + return test.matches(TITLE_VALIDATION_REGEX); + } + + @Override + public String toString() { + return fullMessage; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof seedu.progresschecker.model.issues.Title // instanceof handles nulls + && this.fullMessage.equals(((Title) other).fullMessage)); // state check + } + + @Override + public int hashCode() { + return fullMessage.hashCode(); + } + +} +``` +###### \java\seedu\progresschecker\model\Model.java +``` java + /** authenticates git using password */ + void loginGithub(GitDetails gitdetails) throws IOException, CommandException; + + /** authenticates git using password */ + void logoutGithub() throws CommandException; + + /** creates an issue on github */ + void createIssueOnGitHub(Issue issue) throws IOException, CommandException; + + /** reopen issue on github */ + void reopenIssueOnGithub(Index index) throws IOException, CommandException; + + /** closes an issue issue on github */ + void closeIssueOnGithub(Index index) throws IOException, CommandException; + + /**viwes issues of the specified state */ + void listIssues(String state) throws IllegalValueException, IOException, CommandException; + + /** + * Replaces the fields in Issue {@code index} with {@code editedIssue}. + * + * @throws IOException if while updating the issue there is some problem in authentication + */ + void updateIssue(Index index, Issue editedIssue) throws IOException, CommandException; + + /** Returns unmodifiable view of the filtered issue list */ + ObservableList<Issue> getFilteredIssueList(); + + /** + * Updates the filter of the filtered person list to filter by the given {@code predicate}. + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredIssueList(Predicate<Issue> predicate); +``` +###### \java\seedu\progresschecker\model\ModelManager.java +``` java + @Override + public synchronized void reopenIssueOnGithub(Index index) throws IOException, CommandException { + progressChecker.reopenIssueOnGithub(index); + indicateProgressCheckerChanged(); + } +``` +###### \java\seedu\progresschecker\model\ModelManager.java +``` java + @Override + public void updateIssue(Index index, Issue editedIssue) throws IOException, CommandException { + requireAllNonNull(index, editedIssue); + + progressChecker.updateIssue(index, editedIssue); + indicateProgressCheckerChanged(); + } +``` +###### \java\seedu\progresschecker\model\ModelManager.java +``` java + //=========== Filtered Issue List Accessors ============================================================= + + /** + * Returns an unmodifiable view of the list of {@code Issue} backed by the internal list of + * {@code progressChecker} + */ + @Override + public ObservableList<Issue> getFilteredIssueList() { + return FXCollections.unmodifiableObservableList(filteredIssues); + } + + @Override + public void updateFilteredIssueList(Predicate<Issue> predicate) { + requireNonNull(predicate); + filteredIssues.setPredicate(predicate); + } +``` +###### \java\seedu\progresschecker\model\ProgressChecker.java +``` java + + //issue-level operations + + /** + * Login to github + * + * @throws IOException is there is any problem in authentication + * + */ + public void loginGithub(GitDetails gitdetails) throws IOException, CommandException { + issues.initialiseCredentials(gitdetails); + } + + /** + * Logout of github + */ + public void logoutGithub() throws CommandException { + issues.clearCredentials(); + } + + /** + * Creates issue on github + * + * @throws IOException if theres any fault in the input values or the authentication fails due to wrong input + */ + public void createIssueOnGitHub(Issue i) throws IOException, CommandException { + issues.createIssue(i); + } + + /** + * Replaces the given issue at {@code index} from github with {@code editedPerson}. + * reopens an issue on github + * + * @throws IOException if the index mentioned is not valid or he's closed + */ + public void reopenIssueOnGithub(Index index) throws IOException, CommandException { + issues.reopenIssue(index); + } + + /** + * closes an issue on github + * + * @throws IOException if the index mentioned is not valid or he's closed + */ + public void closeIssueOnGithub(Index index) throws IOException, CommandException { + issues.closeIssue(index); + } + + /** + * Replaces the given person {@code target} in the list with {@code editedPerson}. + * {@code ProgressChecker}'s tag list will be updated with the tags of {@code editedPerson}. + * + * @throws IOException if there is any problem in git authentication or parameter + * + */ + public void updateIssue(Index index, Issue editedIssue) throws IOException, CommandException { + requireNonNull(editedIssue); + issues.setIssue(index, editedIssue); + } + + /** + * Lists all the issues of the specified state + */ + public void listIssues(String state) throws IllegalValueException, IOException, CommandException { + requireNonNull(state); + issues.listIssue(state); + } + +``` +###### \java\seedu\progresschecker\model\util\SampleDataUtil.java +``` java + /** + * Returns an label list containing the list of strings given. + */ + public static List<Labels> getLabelsList(String... strings) { + ArrayList<Labels> labels = new ArrayList<>(); + for (String s : strings) { + labels.add(new Labels(s)); + } + + return labels; + } + + /** + * Returns an Assignee list containing the list of strings given. + */ + public static List<Assignees> getAssigneeList(String... strings) { + ArrayList<Assignees> assignees = new ArrayList<>(); + for (String s : strings) { + assignees.add(new Assignees(s)); + } + + return assignees; + } +``` +###### \java\seedu\progresschecker\storage\XmlAdaptedIssue.java +``` java +/** + * JAXB-friendly version of the Issue. + */ +public class XmlAdaptedIssue { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Issue's %s field is missing!"; + + @XmlElement(required = true) + private String title; + @XmlElement(required = false) + private String body; + @XmlElement(required = false) + private String milestone; + + @XmlElement + private List<XmlAdaptedAssignee> assignees = new ArrayList<>(); + + @XmlElement + private List<XmlAdaptedLabel> labelled = new ArrayList<>(); + + /** + * Constructs an XmlAdaptedIssue. + * This is the no-arg constructor that is required by JAXB. + */ + public XmlAdaptedIssue() {} + + /** + * Constructs an {@code XmlAdaptedPerson} with the given person details. + */ + public XmlAdaptedIssue( + String title, String body, String milestone, + List<XmlAdaptedAssignee> assignees, List<XmlAdaptedLabel> labelled) { + this.title = title; + this.body = body; + this.milestone = milestone; + + if (assignees != null) { + this.assignees = new ArrayList<>(assignees); + } + if (labelled != null) { + this.labelled = new ArrayList<>(labelled); + } + } + + /** + * Converts a given Issue into this class for JAXB use. + * + * @param source future changes to this will not affect the created XmlAdaptedIssue + */ + public XmlAdaptedIssue(Issue source) { + title = source.getTitle().fullMessage; + body = source.getBody().fullBody; + if (source.getMilestone() == null) { + milestone = ""; + } else { + milestone = source.getMilestone().fullMilestone; + } + assignees = new ArrayList<>(); + for (Assignees assignee : source.getAssignees()) { + assignees.add(new XmlAdaptedAssignee(assignee)); + } + for (Labels label : source.getLabelsList()) { + labelled.add(new XmlAdaptedLabel(label)); + } + } + + /** + * Converts this jaxb-friendly adapted issue object into the model's Issue object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted issue + */ + public Issue toModelType() throws IllegalValueException { + final List<Assignees> issueAssignees = new ArrayList<>(); + final List<Labels> issueLabels = new ArrayList<>(); + for (XmlAdaptedAssignee assigneeIssue : assignees) { + issueAssignees.add(assigneeIssue.toModelType()); + } + + for (XmlAdaptedLabel labelIssue : labelled) { + issueLabels.add(labelIssue.toModelType()); + } + + if (this.title == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Title.class.getSimpleName())); + } + final Title title = new Title(this.title); + + final Body body = new Body(this.body); + + final Milestone milestone = new Milestone(this.milestone); + + return new Issue(title, issueAssignees, milestone, body, issueLabels); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlAdaptedIssue)) { + return false; + } + + XmlAdaptedIssue otherIssue = (XmlAdaptedIssue) other; + return Objects.equals(title, otherIssue.title) + && Objects.equals(body, otherIssue.body) + && Objects.equals(milestone, otherIssue.milestone) + && Objects.equals(assignees, otherIssue.assignees) + && Objects.equals(labelled, otherIssue.labelled); + } +} +``` +###### \java\seedu\progresschecker\ui\CommandBox.java +``` java + //TAB case is used to auto-complete commands + case TAB: + keyEvent.consume(); + autocompleteCommad(commandTextField.getText()); + break; + default: + //dynamic search implementation + try { + if ((commandTextField.getText().trim().equalsIgnoreCase(CORRECT_COMMAND_WORD) + || isCorrectCommandWord)) { + isCorrectCommandWord = !commandTextField.getText().trim().isEmpty(); + CommandResult commandResult; + if (keyEvent.getCode() != KeyCode.BACK_SPACE && keyEvent.getCode() != KeyCode.DELETE) { + commandResult = logic.execute(commandTextField.getText() + keyEvent.getText()); + } else { + commandResult = logic.execute(commandTextField.getText().substring(0, + commandTextField.getText().length() - 1)); + } + // process result of the command + logger.info("Result: " + commandResult.feedbackToUser); + raise(new NewResultAvailableEvent(commandResult.feedbackToUser)); + } + + } catch (CommandException | ParseException e) { + // handle command failure + setStyleToIndicateCommandFailure(); + logger.info("Invalid command: " + commandTextField.getText()); + raise(new NewResultAvailableEvent(e.getMessage())); + } +``` +###### \java\seedu\progresschecker\ui\IssueCard.java +``` java +/** + * An UI component that displays information of a {@code Issue}. + */ +public class IssueCard extends UiPart<Region> { + + private static final String FXML = "IssueListCard.fxml"; + private static final String[] LABEL_COLORS = { "red", "orange", "yellow", "green", "blue", "purple" }; + /** + * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. + * As a consequence, UI elements' variable names cannot be set to such keywords + * or an exception will be thrown by JavaFX during runtime. + * + * @see <a href="https://github.com/se-edu/addressbook-level4/issues/336">The issue on AddressBook level 4</a> + */ + + public final Issue issue; + + @javafx.fxml.FXML + private HBox cardPane; + @FXML + private Label title; + @FXML + private Label id; + @FXML + private Label body; + @FXML + private Label milestone; + @FXML + private FlowPane labelled; + @FXML + private FlowPane assignees; + + public IssueCard(Issue issue, int displayedIndex) { + super(FXML); + this.issue = issue; + id.setText("#" + displayedIndex + " "); + title.setText(issue.getTitle().toString()); + body.setText(issue.getBody().fullBody); + milestone.setText(issue.getMilestone().fullMilestone); + issue.getLabelsList().forEach(labels -> { + Label label = new Label(labels.fullLabels); + label.getStyleClass().add(getLabelColor(labels.fullLabels)); + labelled.getChildren().add(label); + }); + issue.getAssignees().forEach(assignee -> { + Label label = new Label(assignee.fullAssignees); + label.getStyleClass().add(getLabelColor(assignee.fullAssignees)); + assignees.getChildren().add(label); + }); + } + + /** + * Get a deterministic label color based off label's name value. + */ + private String getLabelColor(String labelName) { + int index = getValueOfString(labelName) % LABEL_COLORS.length; + return LABEL_COLORS[index]; + } + + /** + * Adds each letter of given string into an integer. + */ + private int getValueOfString(String labelName) { + int sum = 0; + for (char c : labelName.toCharArray()) { + sum += c; + } + return sum; + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof IssueCard)) { + return false; + } + + // state check + IssueCard card = (IssueCard) other; + return id.getText().equals(card.id.getText()) + && issue.equals(card.issue); + } +} +``` +###### \java\seedu\progresschecker\ui\IssueListPanel.java +``` java +/** + * Panel containing the issues on github. + */ +public class IssueListPanel extends UiPart<Region> { + private static final String FXML = "IssueListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(IssueListPanel.class); + + @javafx.fxml.FXML + private ListView<IssueCard> issueListView; + + public IssueListPanel(ObservableList<Issue> issueList) { + super(FXML); + setConnections(issueList); + registerAsAnEventHandler(this); + } + + private void setConnections(ObservableList<Issue> issueList) { + ObservableList<IssueCard> mappedList = EasyBind.map( + issueList, (issue) -> new IssueCard(issue, issue.getIssueIndex())); + issueListView.setItems(mappedList); + issueListView.setCellFactory(listView -> new IssueListViewCell()); + } + + /** + * Scrolls to the {@code IssueCard} at the {@code index} and selects it. + */ + private void scrollTo(int index) { + Platform.runLater(() -> { + issueListView.scrollTo(index); + issueListView.getSelectionModel().clearAndSelect(index); + }); + } + + @Subscribe + private void handleJumpToListRequestEvent(JumpToListRequestEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + scrollTo(event.targetIndex); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code IssueCard}. + */ + class IssueListViewCell extends ListCell<IssueCard> { + + @Override + protected void updateItem(IssueCard issue, boolean empty) { + super.updateItem(issue, empty); + + if (empty || issue == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(issue.getRoot()); + } + } + } + +} +``` +###### \resources\view\IssueListCard.fxml +``` fxml + +<HBox id="cardPane" fx:id="cardPane" prefHeight="140.0" prefWidth="380.0" xmlns="http://javafx.com/javafx/8.0.121" xmlns:fx="http://javafx.com/fxml/1" styleClass="main-box"> + <GridPane prefHeight="140.0" prefWidth="380.0" HBox.hgrow="ALWAYS"> + <columnConstraints> + <ColumnConstraints hgrow="SOMETIMES" minWidth="10" prefWidth="150" /> + </columnConstraints> + <HBox prefHeight="140.0" prefWidth="380.0"> + <children> + <VBox alignment="CENTER_LEFT" minHeight="-Infinity" prefHeight="140.0" prefWidth="510.0"> + <padding> + <Insets bottom="5" left="15" right="5" top="5" /> + </padding> + <HBox alignment="CENTER_LEFT" spacing="5" prefWidth="380.0"> + <Label fx:id="id" styleClass="cell_big_label"> + <minWidth> + <!-- Ensures that the label text is never truncated --> + <Region fx:constant="USE_PREF_SIZE" /> + </minWidth> + </Label> + <Label fx:id="title" prefWidth="380.0" styleClass="title-issue" text="\$first" wrapText="true"> + <minWidth> + <!-- Ensures that the label text is never truncated --> + <Region fx:constant="USE_PREF_SIZE" /> + </minWidth> + </Label> + </HBox> + <FlowPane fx:id="labelled" /> + <GridPane prefHeight="80.0" prefWidth="301.0"> + <columnConstraints> + <ColumnConstraints hgrow="SOMETIMES" maxWidth="92.0" minWidth="0.0" prefWidth="78.0" /> + <ColumnConstraints hgrow="ALWAYS" maxWidth="360.0" minWidth="10.0" prefWidth="282.0" /> + </columnConstraints> + <rowConstraints> + <RowConstraints maxHeight="25.0" minHeight="10.0" prefHeight="20.0" vgrow="ALWAYS" /> + <RowConstraints maxHeight="32.0" minHeight="0.0" prefHeight="32.0" vgrow="ALWAYS" /> + <RowConstraints maxHeight="23.0" minHeight="7.0" prefHeight="17.0" vgrow="ALWAYS" /> + </rowConstraints> + <children> + <Text strokeType="OUTSIDE" strokeWidth="0.0" text="Body: " /> + <Label fx:id="body" styleClass="cell_small_label" text="\$body" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" GridPane.valignment="CENTER" GridPane.vgrow="ALWAYS" wrapText="true"/> + <Text strokeType="OUTSIDE" strokeWidth="0.0" text="Assignees: " GridPane.rowIndex="2" /> + <FlowPane fx:id="assignees" prefHeight="25.0" prefWidth="282.0" GridPane.columnIndex="1" GridPane.rowIndex="2"> + <GridPane.margin> + <Insets bottom="-1.0" top="1.0" /> + </GridPane.margin> + <padding> + <Insets top="3.0" /> + </padding></FlowPane> + <Text strokeType="OUTSIDE" strokeWidth="0.0" text="Milestone: " GridPane.rowIndex="1" /> + <Label fx:id="milestone" styleClass="cell_small_label" text="\$milestone" GridPane.columnIndex="1" GridPane.rowIndex="1"> + <rotationAxis> + <Point3D /> + </rotationAxis> + <GridPane.margin> + <Insets top="1.0" /> + </GridPane.margin></Label> + </children> + </GridPane> + </VBox> + </children> + </HBox> + <rowConstraints> + <RowConstraints /> + </rowConstraints> + </GridPane> +</HBox> +``` +###### \resources\view\IssueListPanel.fxml +``` fxml +<VBox xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"> + <ListView fx:id="issueListView" VBox.vgrow="ALWAYS" /> +</VBox> +``` diff --git a/collated/functional/iNekox3.md b/collated/functional/iNekox3.md new file mode 100644 index 000000000000..b8c5d6299a7a --- /dev/null +++ b/collated/functional/iNekox3.md @@ -0,0 +1,1882 @@ +# iNekox3 +###### \java\seedu\progresschecker\commons\events\ui\TabLoadChangedEvent.java +``` java +/** + * Represents a tab change in Main Window. + */ +public class TabLoadChangedEvent extends BaseEvent { + + public final String type; + + public TabLoadChangedEvent(String type) { + this.type = type; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + + public String getTabName() { + return type; + } +} +``` +###### \java\seedu\progresschecker\commons\util\StringUtil.java +``` java + /** + * Returns true if {@code s} is within the range 2 to 11. + * e.g. 2, 3, 4, ..., 11 <br> + * Will return false for any other non-null string input + * e.g. empty string, "-1", "0", "+1", and " 2 " (untrimmed), "3 0" (contains whitespace), "1 a" (contains letters) + * @throws NullPointerException if {@code s} is null. + */ + public static boolean isWithinRange(String s) { + requireNonNull(s); + + try { + int value = Integer.parseInt(s); + return value >= ViewCommand.MIN_WEEK_NUMBER && value <= ViewCommand.MAX_WEEK_NUMBER; + } catch (NumberFormatException nfe) { + return false; + } + } +} +``` +###### \java\seedu\progresschecker\logic\commands\AnswerCommand.java +``` java +/** + * Edits details of student answer of an exercise in the ProgressChecker. + */ +public class AnswerCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "answer"; + public static final String COMMAND_ALIAS = "ans"; + public static final String COMMAND_FORMAT = COMMAND_WORD + " QUESTION-INDEX" + " ANSWER"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Answer an exercise " + + "identified by the index number shown. " + + "Existing answer will be overwritten by the input value.\n" + + "Parameters: INDEX (must be in the format of WEEK.SECTION.QUESTION number " + + "where WEEK range from " + MIN_WEEK_NUMBER + " to " + MAX_WEEK_NUMBER + ") " + + "ANSWER\n" + + "Example: " + COMMAND_WORD + " 2.1.1 " + + "Procedural languages work at simple data structures and functions level."; + + public static final String MESSAGE_EDIT_EXERCISE_SUCCESS = "Answered Exercise: %1$s"; + + private final QuestionIndex questionIndex; + private final StudentAnswer studentAnswer; + + private Exercise exerciseToEdit; + private Exercise editedExercise; + + /** + * @param questionIndex of the question in the filtered exercise list to edit + * @param studentAnswer answer to edit the exercise with + */ + public AnswerCommand(QuestionIndex questionIndex, StudentAnswer studentAnswer) { + requireNonNull(questionIndex); + + this.questionIndex = questionIndex; + this.studentAnswer = studentAnswer; + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + try { + model.updateExercise(exerciseToEdit, editedExercise); + } catch (ExerciseNotFoundException enfe) { + throw new AssertionError("The target exercise cannot be missing"); + } + model.updateFilteredExerciseList(exercise -> exercise.getQuestionIndex().getWeekNumber() + == editedExercise.getQuestionIndex().getWeekNumber()); + return new CommandResult(String.format(MESSAGE_EDIT_EXERCISE_SUCCESS, questionIndex)); + } + + //TODO: store mapping of questionIndex to exercise's index in exerciseList in a separate data structure + @Override + protected void preprocessUndoableCommand() throws CommandException { + List<Exercise> exerciseList = model.getFilteredExerciseList(); + boolean isFound = false; + + if (!questionIndex.isValidIndex(questionIndex.toString())) { + throw new CommandException(String.format( + Messages.MESSAGE_INVALID_EXERCISE_INDEX, questionIndex.toString())); + } + + for (Exercise e : exerciseList) { + if (e.getQuestionIndex().toString().equals(questionIndex.toString())) { + exerciseToEdit = exerciseList.get(exerciseList.indexOf(e)); + editedExercise = createEditedExercise(exerciseToEdit, studentAnswer); + isFound = true; + break; + } + } + + if (!isFound) { + throw new CommandException(String.format( + Messages.MESSAGE_INVALID_EXERCISE_INDEX, questionIndex.toString())); + } + } + + /** + * Creates and returns a {@code Exercise} with the details of {@code exerciseToEdit} + * edited with {@code editExerciseDescriptor}. + */ + private static Exercise createEditedExercise(Exercise exerciseToEdit, StudentAnswer studentAnswer) { + assert exerciseToEdit != null; + + QuestionIndex questionIndex = exerciseToEdit.getQuestionIndex(); + QuestionType questionType = exerciseToEdit.getQuestionType(); + Question question = exerciseToEdit.getQuestion(); + StudentAnswer updatedStudentAnswer = studentAnswer; + ModelAnswer modelAnswer = exerciseToEdit.getModelAnswer(); + + return new Exercise(questionIndex, questionType, question, + updatedStudentAnswer, modelAnswer); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof AnswerCommand)) { + return false; + } + + // state check + AnswerCommand e = (AnswerCommand) other; + return questionIndex.equals(e.questionIndex) + && studentAnswer.equals(e.studentAnswer) + && Objects.equals(exerciseToEdit, e.exerciseToEdit); + } + +} +``` +###### \java\seedu\progresschecker\logic\commands\ViewCommand.java +``` java +/** + * Change view of the tab pane in main window based on categories. + */ +public class ViewCommand extends Command { + + public static final int MIN_WEEK_NUMBER = 2; + public static final int MAX_WEEK_NUMBER = 11; + + public static final String COMMAND_WORD = "view"; + public static final String COMMAND_ALIAS = "v"; + public static final String COMMAND_FORMAT = COMMAND_WORD + " TYPE"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Change the tab view to profiles, tasks, exercises, or issues.\n" + + "Parameters: TYPE (must be either 'profile', 'task', 'exercise', or 'issues')\n" + + "INDEX (if TYPE is exercise and must be within " + MIN_WEEK_NUMBER + + " and " + MAX_WEEK_NUMBER + " (boundary numbers inclusive)\n" + + "Example: " + COMMAND_WORD + " exercise\n" + + COMMAND_WORD + "exercise 5"; + + public static final String MESSAGE_SUCCESS_TAB = "Viewing tab %1$s"; + public static final String MESSAGE_SUCCESS_WEEK = "Viewing week %1$s's exercises"; + + private final String type; + private final int weekNumber; + private final boolean isToggleExerciseByWeek; + + public ViewCommand(String type, int weekNumber, boolean isToggleExerciseByWeek) { + this.type = type; + this.weekNumber = weekNumber; + this.isToggleExerciseByWeek = isToggleExerciseByWeek; + } + + @Override + public CommandResult execute() { + if (!isToggleExerciseByWeek) { + EventsCenter.getInstance().post(new TabLoadChangedEvent(type)); + return new CommandResult(String.format(MESSAGE_SUCCESS_TAB, type)); + } else { + model.updateFilteredExerciseList(exercise -> exercise.getQuestionIndex().getWeekNumber() == weekNumber); + EventsCenter.getInstance().post(new TabLoadChangedEvent(type)); + return new CommandResult(String.format(MESSAGE_SUCCESS_WEEK, weekNumber)); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ViewCommand // instanceof handles nulls + && this.type.equals(((ViewCommand) other).type)) // state check + && this.weekNumber == ((ViewCommand) other).weekNumber + && this.isToggleExerciseByWeek == ((ViewCommand) other).isToggleExerciseByWeek; + } +} +``` +###### \java\seedu\progresschecker\logic\LogicManager.java +``` java + @Override + public ObservableList<Exercise> getFilteredExerciseList() { + return model.getFilteredExerciseList(); + } + +``` +###### \java\seedu\progresschecker\logic\parser\AnswerCommandParser.java +``` java +/** + * Parses input arguments and creates a new AnswerCommand object + */ +public class AnswerCommandParser implements Parser<AnswerCommand> { + + private static final int QUESTION_INDEX_INDEX = 0; + private static final int ANSWER_INDEX = 1; + + /** + * Parses the given {@code String} of arguments in the context of the AnswerCommand + * and returns an AnswerCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public AnswerCommand parse(String args) throws ParseException { + requireNonNull(args); + try { + String[] content = args.trim().split(" ", 2); + + QuestionIndex questionIndex; + questionIndex = ParserUtil.parseQuestionIndex(content[QUESTION_INDEX_INDEX]); + StudentAnswer studentAnswer = ParserUtil.parseStudentAnswer(content[ANSWER_INDEX]); + + return new AnswerCommand(questionIndex, studentAnswer); + } catch (ArrayIndexOutOfBoundsException e) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AnswerCommand.MESSAGE_USAGE)); + } catch (IllegalValueException ive) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AnswerCommand.MESSAGE_USAGE)); + } + } + +} +``` +###### \java\seedu\progresschecker\logic\parser\ParserUtil.java +``` java + /** + * Parses {@code type} into a {@code String[]} and returns it. Leading and trailing whitespaces will be + * trimmed. + * @throws IllegalValueException if the specified type is invalid, that is: + * i. type is not of string "profile", "task", or "exercise". + * ii. type is of "exercise" and comes with an index specified + * in which the index does not fall between the accepted range. + */ + public static String[] parseTabType(String type) throws IllegalValueException { + String trimmedType = type.trim(); + String[] trimmedTypeArray = trimmedType.split(" "); + if (!trimmedTypeArray[0].equals("profile") + && !trimmedTypeArray[0].equals("task") + && !trimmedTypeArray[0].equals("exercise") + && !trimmedTypeArray[0].equals("issues")) { + throw new IllegalValueException(MESSAGE_INVALID_TAB_TYPE); + } + + if (trimmedTypeArray.length > 1 + && trimmedTypeArray[0].equals("exercise") + && !StringUtil.isWithinRange(trimmedTypeArray[1])) { + throw new IllegalValueException(MESSAGE_INVALID_WEEK_NUMBER); + } + + return trimmedTypeArray; + } + +``` +###### \java\seedu\progresschecker\logic\parser\ParserUtil.java +``` java + /** + * Parses a {@code String questionIndex} into a {@code QuestionIndex}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code questionIndex} is invalid. + */ + public static QuestionIndex parseQuestionIndex(String questionIndex) throws IllegalValueException { + requireNonNull(questionIndex); + String trimmedQuestionIndex = questionIndex.trim(); + if (!QuestionIndex.isValidIndex(trimmedQuestionIndex)) { + throw new IllegalValueException(QuestionIndex.MESSAGE_INDEX_CONSTRAINTS); + } + return new QuestionIndex(trimmedQuestionIndex); + } + + /** + * Parses a {@code String studentAnswer} into a {@code StudentAnswer}. + * Leading and trailing whitespaces will be trimmed. + */ + public static StudentAnswer parseStudentAnswer(String studentAnswer) { + requireNonNull(studentAnswer); + String trimmedStudentAnswer = studentAnswer.trim(); + + return new StudentAnswer(trimmedStudentAnswer); + } +} +``` +###### \java\seedu\progresschecker\logic\parser\ViewCommandParser.java +``` java +/** + * Parses input arguments and creates a new ViewCommand object + */ +public class ViewCommandParser implements Parser<ViewCommand> { + + /** + * Parses the given {@code String} of arguments in the context of the ViewCommand + * and returns an ViewCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public ViewCommand parse(String args) throws ParseException { + try { + String[] typeArray = ParserUtil.parseTabType(args); + String type = typeArray[0]; + int weekNumber = -1; + boolean isToggleExerciseByWeek = false; + if (typeArray.length > 1) { + weekNumber = Integer.parseInt(typeArray[1]); + isToggleExerciseByWeek = true; + } + return new ViewCommand(type, weekNumber, isToggleExerciseByWeek); + } catch (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ViewCommand.MESSAGE_USAGE)); + } + } +} +``` +###### \java\seedu\progresschecker\model\exercise\exceptions\DuplicateExerciseException.java +``` java +/** + * Signals that the operation will result in duplicate Exercise objects. + */ +public class DuplicateExerciseException extends DuplicateDataException { + public DuplicateExerciseException() { + super("Operation would result in duplicate exercises"); + } +} +``` +###### \java\seedu\progresschecker\model\exercise\exceptions\ExerciseNotFoundException.java +``` java +/** + * Signals that the operation is unable to find the specified exercise. + */ +public class ExerciseNotFoundException extends Exception {} +``` +###### \java\seedu\progresschecker\model\exercise\Exercise.java +``` java +/** + * Represents an Exercise in the ProgressChecker. + * Guarantees: details are present and not null, field values are validated. + */ +public class Exercise { + + private final QuestionIndex questionIndex; + private final QuestionType questionType; + private final Question question; + private final StudentAnswer studentAnswer; + private final ModelAnswer modelAnswer; + + /** + * Every field must be present and not null. + */ + public Exercise(QuestionIndex questionIndex, QuestionType questionType, Question question, + StudentAnswer studentAnswer, ModelAnswer modelAnswer) { + requireAllNonNull(questionIndex, questionType, question); + this.questionIndex = questionIndex; + this.questionType = questionType; + this.question = question; + this.studentAnswer = studentAnswer; + this.modelAnswer = modelAnswer; + } + + public QuestionIndex getQuestionIndex() { + return questionIndex; + } + + public QuestionType getQuestionType() { + return questionType; + } + + public Question getQuestion() { + return question; + } + + public StudentAnswer getStudentAnswer() { + return studentAnswer; + } + + public ModelAnswer getModelAnswer() { + return modelAnswer; + } + + @Override + public String toString() { + return "Q" + questionIndex + " " + question + "\n\n" + + "Your Answer: " + studentAnswer + "\n\n" + + "Suggested Answer: " + modelAnswer; + } +} +``` +###### \java\seedu\progresschecker\model\exercise\ModelAnswer.java +``` java +/** + * Represents an Exercise's model answer in the ProgressChecker. + */ +public class ModelAnswer { + + public final String value; + + /** + * Constructs a {@code ModelAnswer}. + * + * @param answer An answer of any word and character. + */ + public ModelAnswer(String answer) { + requireNonNull(answer); + this.value = answer; + } + + @Override + public String toString() { + return value; + } +} +``` +###### \java\seedu\progresschecker\model\exercise\Question.java +``` java +/** + * Represents an Exercise's question in the ProgressChecker. + */ +public class Question { + + public final String value; + + /** + * Constructs a {@code Question}. + * + * @param question A question of any word and character. + */ + public Question(String question) { + requireNonNull(question); + this.value = question; + } + + @Override + public String toString() { + return value; + } +} +``` +###### \java\seedu\progresschecker\model\exercise\QuestionIndex.java +``` java +/** + * Represents an Exercise's question index in the ProgressChecker. + */ +public class QuestionIndex { + + public static final int WEEK_NUMBER_INDEX = 0; + + public static final String MESSAGE_INDEX_CONSTRAINTS = + "Indices can only contain numbers, and should be in the format of " + + "SECTION NUMBER.PART NUMBER.QUESTION NUMBER"; + public static final String INDEX_VALIDATION_REGEX = "([2-9]|1[0-3])\\.([0-9]|[0-9]{2})\\.([0-9]|[0-9]{2})"; + public final String value; + + /** + * Constructs a {@code QuestionIndex}. + * + * @param index A valid index number. + */ + public QuestionIndex(String index) { + requireNonNull(index); + checkArgument(isValidIndex(index), MESSAGE_INDEX_CONSTRAINTS); + this.value = index; + } + + /** + * Returns true if a given string is a valid index number. + */ + public static boolean isValidIndex(String test) { + return test.matches(INDEX_VALIDATION_REGEX); + } + + /** + * Returns the question number in the whole question index. + */ + public int getWeekNumber() { + return Integer.parseInt(value.split(Pattern.quote("."))[WEEK_NUMBER_INDEX]); + } + + @Override + public String toString() { + return value; + } +} +``` +###### \java\seedu\progresschecker\model\exercise\QuestionType.java +``` java +/** + * Represents an Exercise's question type in the ProgressChecker. + */ +public class QuestionType { + + public static final String MESSAGE_TYPE_CONSTRAINTS = + "Type can only be 'text' or 'choice'"; + public static final String TYPE_VALIDATION_REGEX = "text|choice"; + public final String value; + + /** + * Constructs a {@code QuestionType}. + * + * @param type A valid type. + */ + public QuestionType(String type) { + requireNonNull(type); + checkArgument(isValidType(type), MESSAGE_TYPE_CONSTRAINTS); + this.value = type; + } + + /** + * Returns true if a given string is a valid type. + */ + public static boolean isValidType(String test) { + return test.matches(TYPE_VALIDATION_REGEX); + } + + @Override + public String toString() { + return value; + } +} +``` +###### \java\seedu\progresschecker\model\exercise\StudentAnswer.java +``` java +/** + * Represents an Exercise's student answer in the ProgressChecker. + */ +public class StudentAnswer { + + public final String value; + + /** + * Constructs a {@code StudentAnswer}. + * + * @param answer An answer of any word and character. + */ + public StudentAnswer(String answer) { + requireNonNull(answer); + this.value = answer; + } + + @Override + public String toString() { + return value; + } +} +``` +###### \java\seedu\progresschecker\model\exercise\UniqueExerciseList.java +``` java +/** + * A list of exercises that enforces uniqueness between its elements and does not allow nulls. + * + * Supports a minimal set of list operations. + * + * @see Exercise#equals(Object) + * @see CollectionUtil#elementsAreUnique(Collection) + */ +public class UniqueExerciseList implements Iterable<Exercise> { + + private final ObservableList<Exercise> internalList = FXCollections.observableArrayList(); + + /** + * Returns true if the list contains an equivalent exercise as the given argument. + */ + public boolean contains(Exercise toCheck) { + requireNonNull(toCheck); + return internalList.contains(toCheck); + } + + /** + * Adds an exercise to the list. + * + * @throws DuplicateExerciseException if the exercise to add is a duplicate of an existing exercise in the list. + */ + public void add(Exercise toAdd) throws DuplicateExerciseException { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateExerciseException(); + } + internalList.add(toAdd); + } + + /** + * Replaces the exercise {@code target} in the list with {@code editedExercise}. + * + * @throws ExerciseNotFoundException if {@code target} could not be found in the list. + */ + public void setExercise(Exercise target, Exercise editedExercise) + throws ExerciseNotFoundException { + requireNonNull(editedExercise); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new ExerciseNotFoundException(); + } + + internalList.set(index, editedExercise); + } + + public void setExercises(UniqueExerciseList replacement) { + this.internalList.setAll(replacement.internalList); + } + + public void setExercises(List<Exercise> exercises) throws DuplicateExerciseException { + requireAllNonNull(exercises); + final UniqueExerciseList replacement = new UniqueExerciseList(); + for (final Exercise exercise : exercises) { + replacement.add(exercise); + } + setExercises(replacement); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList<Exercise> asObservableList() { + return FXCollections.unmodifiableObservableList(internalList); + } + + @Override + public Iterator<Exercise> iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniqueExerciseList // instanceof handles nulls + && this.internalList.equals(((UniqueExerciseList) other).internalList)); + } +} +``` +###### \java\seedu\progresschecker\model\Model.java +``` java + /** + * Replaces the given exercise {@code target} with {@code editedExercise}. + * + * @throws ExerciseNotFoundException if {@code target} could not be found in the list. + */ + void updateExercise(Exercise target, Exercise editedExercise) + throws ExerciseNotFoundException; + + /** Returns an unmodifiable view of the filtered exercise list */ + ObservableList<Exercise> getFilteredExerciseList(); + + /** + * Updates the filter of the filtered exercise list to filter by the given {@code predicate}. + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredExerciseList(Predicate<Exercise> predicate); + +``` +###### \java\seedu\progresschecker\model\ModelManager.java +``` java + @Override + public void updateExercise(Exercise target, Exercise editedExercise) + throws ExerciseNotFoundException { + requireAllNonNull(target, editedExercise); + + progressChecker.updateExercise(target, editedExercise); + indicateProgressCheckerChanged(); + } + +``` +###### \java\seedu\progresschecker\model\ModelManager.java +``` java + //=========== Filtered Exercise List Accessors ============================================================= + + /** + * Returns an unmodifiable view of the list of {@code Exercise} backed by the internal list of + * {@code progressChecker} + */ + @Override + public ObservableList<Exercise> getFilteredExerciseList() { + return FXCollections.unmodifiableObservableList(filteredExercises); + } + + @Override + public void updateFilteredExerciseList(Predicate<Exercise> predicate) { + requireNonNull(predicate); + filteredExercises.setPredicate(predicate); + } +} +``` +###### \java\seedu\progresschecker\model\ProgressChecker.java +``` java + public void setExercises(List<Exercise> exercises) throws DuplicateExerciseException { + this.exercises.setExercises(exercises); + } + +``` +###### \java\seedu\progresschecker\model\ProgressChecker.java +``` java + //// exercise-level operations + + /** + * Adds an exercise to the ProgressChecker. + * + * @throws DuplicateExerciseException if an equivalent exercise already exists. + */ + public void addExercise(Exercise e) throws DuplicateExerciseException { + Exercise exercise = new Exercise( + e.getQuestionIndex(), e.getQuestionType(), e.getQuestion(), + e.getStudentAnswer(), e.getModelAnswer()); + exercises.add(exercise); + } + + /** + * Replaces the given exercise {@code target} in the list with {@code editedExercise}. + * + * @throws ExerciseNotFoundException if {@code target} could not be found in the list. + */ + public void updateExercise(Exercise target, Exercise editedExercise) + throws ExerciseNotFoundException { + requireNonNull(editedExercise); + + exercises.setExercise(target, editedExercise); + } + +``` +###### \java\seedu\progresschecker\model\ProgressChecker.java +``` java + @Override + public ObservableList<Exercise> getExerciseList() { + return exercises.asObservableList(); + } +``` +###### \java\seedu\progresschecker\model\util\SampleDataUtil.java +``` java + public static Exercise[] getSampleExercises() { + return new Exercise[] { + // week 2 + new Exercise(new QuestionIndex("2.2.1"), new QuestionType("choice"), + new Question("Which one of these is not a feature available in IDEs?\n" + + "\n" + + "a. Compiling.\n" + + "b. Syntax error highlighting.\n" + + "c. Debugging.\n" + + "d. Code navigation e.g., to navigate from a method call to the method implementation.\n" + + "e. Simulation e.g., run a mobile app in a simulator.\n" + + "f. Code analysis e.g. to find unreachable code.\n" + + "g. Reverse engineering design/documentation e.g. generate diagrams from code\n" + + "h. Visual programming e.g. Write programs using ‘drag and drop’ actions " + + "instead of typing code.\n" + + "i. Syntax assistance e.g., show hints as you type.\n" + + "j. Code generation e.g., to generate the code required " + + "by simply specifying which component/structure you want to implement.\n" + + "k. Extension. i.e. ability add more functionality to the IDE using plugins."), + new StudentAnswer(""), + new ModelAnswer("All. While all of these features may not be present in some IDEs, " + + "most do have these features in some form or other.")), + new Exercise(new QuestionIndex("2.5.1"), new QuestionType("text"), + new Question("Explain how the concepts of testing, test case, test failure, " + + "and defect are related to each other."), + new StudentAnswer(""), + new ModelAnswer("No suggested answer.")), + new Exercise(new QuestionIndex("2.5.2"), new QuestionType("choice"), + new Question("Regression testing is the automated re-testing of a software " + + "after it has been modified.\n" + + "\n" + + "a. True\n" + + "b. False\n" + + "c. Partially true"), + new StudentAnswer(""), + new ModelAnswer("c. Regression testing need not be automated but automation is highly recommended.")), + new Exercise(new QuestionIndex("2.5.3"), new QuestionType("text"), + new Question("Explain why and when you would do regression testing in a software project."), + new StudentAnswer(""), + new ModelAnswer("No suggested answer.")), + new Exercise(new QuestionIndex("2.6.1"), new QuestionType("text"), + new Question("What does RCS stand for?"), + new StudentAnswer(""), + new ModelAnswer("Revision Control Software.")), + new Exercise(new QuestionIndex("2.6.2"), new QuestionType("text"), + new Question("In the context of RCS, what is a Revision? Give an example."), + new StudentAnswer(""), + new ModelAnswer("Versions of a piece of information. For example, " + + "take a file containing program code. " + + "If you modify the code and save the file, " + + "you have a new version of that file.")), + new Exercise(new QuestionIndex("2.6.3"), new QuestionType("choice"), + new Question("Which of these is not considered a benefit of a typical RCS?\n" + + "a. Help a single user manage revisions of a single file\n" + + "b. Help a developer recover from a incorrect modification to a code file\n" + + "c. Makes it easier for a group of developers to collaborate on a project\n" + + "d. Manage the drift between multiple versions of your project\n" + + "e. Detect when multiple developers make incompatible changes to the same file\n" + + "f. All of them are benefits of RCS"), + new StudentAnswer(""), + new ModelAnswer("f.")), + new Exercise(new QuestionIndex("2.6.4"), new QuestionType("text"), + new Question("Suppose You are doing a team project with Tom, Dick, and Harry " + + "but those three have not even heard the term RCS. " + + "How do you explain RCS to them as briefly as possible, " + + "using the project as an example?"), + new StudentAnswer(""), + new ModelAnswer("No suggested answer.")), + new Exercise(new QuestionIndex("2.6.5"), new QuestionType("text"), + new Question("In the context of RCS, what is a repo?"), + new StudentAnswer(""), + new ModelAnswer("No suggested answer.")), + + // week 3 + new Exercise(new QuestionIndex("3.1.1"), new QuestionType("choice"), + new Question("Choose the correct statements\n" + + "\n" + + " a. Refactoring can improve understandability\n" + + " b. Refactoring can uncover bugs\n" + + " c. Refactoring can result in better performance\n" + + " d. Refactoring can change the number of methods/classes"), + new StudentAnswer(""), + new ModelAnswer("a b c d. (a, b, c) Although the primary aim of refactoring " + + "is to improve internal code structure, there are other secondary benefits. " + + "(d) Some refactorings result in adding/removing methods/classes.")), + new Exercise(new QuestionIndex("3.1.2"), new QuestionType("text"), + new Question("Do you agree with the following statement? Justify your answer.\n" + + "\n" + + "Statement: Whenever we refactor code to fix bugs, " + + "we need not do regression testing if the bug fix was minor."), + new StudentAnswer(""), + new ModelAnswer("DISAGREE. Even a minor change can have major repercussions on the system. " + + "We MUST do regression testing after each change, no matter how minor it is. " + + "Fixing bugs is technically not refactoring.")), + new Exercise(new QuestionIndex("3.1.3"), new QuestionType("text"), + new Question("Explain what is refactoring and why it is not the same as rewriting, " + + "bug fixing, or adding features."), + new StudentAnswer(""), + new ModelAnswer("No suggested answer.")), + new Exercise(new QuestionIndex("3.1.4"), new QuestionType("choice"), + new Question("‘Extract method’ and ‘Inline method’ refactorings\n" + + "\n" + + "a. are opposites of each other.\n" + + "b. sounds like opposites but they are not."), + new StudentAnswer(""), + new ModelAnswer("a.")), + new Exercise(new QuestionIndex("3.2.1"), new QuestionType("choice"), + new Question("What is the recommended approach regarding coding standards?\n" + + "\n" + + "a. Each developer should find a suitable coding standard and follow it in their coding.\n" + + "b. A developer should understand the importance of following a coding standard. " + + "However, there is no need to follow one.\n" + + "c. A developer should find out the coding standards currently used by the project " + + "and follow that closely.\n" + + "d. Coding standards are lame. Real programmers develop their own individual styles."), + new StudentAnswer(""), + new ModelAnswer("c.")), + new Exercise(new QuestionIndex("3.2.2"), new QuestionType("text"), + new Question("What is the aim of using a coding standard? How does it help?"), + new StudentAnswer(""), + new ModelAnswer("No suggested answer.")), + new Exercise(new QuestionIndex("3.2.3"), new QuestionType("choice"), + new Question("According to the given Java coding standard, which one of these is not a good name?\n" + + "\n" + + "a. integer variable name: totalPeople\n" + + "b. boolean variable name: checkWeight\n" + + "c. method name (returns integer): getPeopleCount\n" + + "d. method name (returns boolean): isValidAddress\n" + + "e. String variable name: description"), + new StudentAnswer(""), + new ModelAnswer("b. checkWeight is an action. " + + "Naming variables as actions makes the code harder to follow. " + + "isWeightValid may be a better name.")), + new Exercise(new QuestionIndex("3.3.1"), new QuestionType("choice"), + new Question("Putting all details in one place can create lengthy methods, " + + "but it is preferred over creating many small methods " + + "because it makes the code easier to understand.\n" + + "\n" + + "a. True\n" + + "b. False"), + new StudentAnswer(""), + new ModelAnswer("b. False. If you are using abstraction properly, " + + "you DON’T need to see all details to understand something. " + + "The whole point of using abstraction is to be able to understand things " + + "without knowing as little details as possible. " + + "This is why we recommend single level of abstraction per method and top-down coding.")), + new Exercise(new QuestionIndex("3.3.2"), new QuestionType("choice"), + new Question("What are the drawbacks of trying to optimize code too soon?\n" + + "\n" + + "a. We may not know which parts are the real performance bottleneck\n" + + "b. When we optimize code manually, it becomes harder for the compiler to optimize\n" + + "c. Optimizing can complicate code\n" + + "d. Optimizing can lead to more error-prone code"), + new StudentAnswer(""), + new ModelAnswer("All.")), + new Exercise(new QuestionIndex("3.3.3"), new QuestionType("choice"), + new Question("This is a common saying among programmers\n" + + "\n" + + "a. Make it fast, make it right, make it work\n" + + "b. Make it work, make it right, make it fast\n" + + "c. Make it fast, make it right, now make it faster"), + new StudentAnswer(""), + new ModelAnswer("b.")), + new Exercise(new QuestionIndex("3.6.1"), new QuestionType("choice"), + new Question("In general, comments should describe,\n" + + "\n" + + "a. WHAT the code does\n" + + "b. WHY the code does something\n" + + "c. HOW the code does something"), + new StudentAnswer(""), + new ModelAnswer("a b. How the code does something should be apparent from the code itself. " + + "However, comments can help the reader in describing WHAT and WHY aspects of the code.")), + + // week 4 + new Exercise(new QuestionIndex("4.1.1"), new QuestionType("choice"), + new Question("Choose the correct statements about models.\n" + + "\n" + + "a. Models are abstractions.\n" + + "b. Models can be used for communication.\n" + + "c. Models can be used for analysis of a problem.\n" + + "d. Generating models from code is useless.\n" + + "e. Models can be used as blueprints for generating code."), + new StudentAnswer(""), + new ModelAnswer("a b c e. Models generated from code can be used for understanding, analysing, " + + "and communicating about the code.")), + new Exercise(new QuestionIndex("4.1.2"), new QuestionType("text"), + new Question("Explain how models (e.g. UML diagrams) can be used in a class project."), + new StudentAnswer(""), + new ModelAnswer("No suggested answer.")), + new Exercise(new QuestionIndex("4.2.1"), new QuestionType("choice"), + new Question("A) Choose the correct statements\n" + + "\n" + + "a. OO is a programming paradigm\n" + + "b. OO guides us in how to structure the solution\n" + + "c. OO is mainly an abstraction mechanism\n" + + "d. OO is a programming language\n" + + "e. OO is modeled after how the objects in real world work"), + new StudentAnswer(""), + new ModelAnswer("a b c e. While many languages support the OO paradigm, OO is not a language itself.")), + new Exercise(new QuestionIndex("4.2.2"), new QuestionType("choice"), + new Question("Choose the correct statements\n" + + "\n" + + "a. Java and C++ are OO languages\n" + + "b. C language follows the Functional Programming paradigm\n" + + "c. Java can be used to write procedural code\n" + + "d. Prolog follows the Logic Programming paradigm"), + new StudentAnswer(""), + new ModelAnswer("a c d. C follows the procedural paradigm. " + + "Yes, we can write procedural code using OO languages e.g., AddressBook-level1.")), + new Exercise(new QuestionIndex("4.2.3"), new QuestionType("choice"), + new Question("OO is a higher level mechanism than the procedural paradigm.\n" + + "\n" + + "a. True\n" + + "b. False"), + new StudentAnswer(""), + new ModelAnswer("a. True. Procedural languages work at simple data structures (e.g., integers, arrays) " + + "and functions level. Because an object is an abstraction over data+related functions, " + + "OO works at a higher level.")), + new Exercise(new QuestionIndex("4.2.4"), new QuestionType("choice"), + new Question("Choose the correct statement\n" + + "\n" + + "a. An object is an encapsulation because it packages data and behavior into one bundle.\n" + + "b. An object is an encapsulation because it lets us think in terms of higher level concepts " + + "such as Students rather than student-related functions and data separately."), + new StudentAnswer(""), + new ModelAnswer("a. The second statement should be: An object is an abstraction encapsulation " + + "because it lets ...")), + new Exercise(new QuestionIndex("4.5.1"), new QuestionType("choice"), + new Question("Which are benefits of exceptions?\n" + + "+\n" + + " a. Exceptions allow us to separate normal code from error handling code.\n" + + " b. Exceptions can prevent problems that happen in the environment.\n" + + " c. Exceptions allow us to handle in one location an error raised in another location."), + new StudentAnswer(""), + new ModelAnswer("a c. Exceptions cannot prevent problems in the environment. " + + "They can only be used to handle and recover from such problems.")), + new Exercise(new QuestionIndex("4.6.1"), new QuestionType("text"), + new Question("Show (in UML notation) an enumeration called WeekDay " + + "to use when the value can only be Monday ... Friday."), + new StudentAnswer(""), + new ModelAnswer("No suggested answer.")), + new Exercise(new QuestionIndex("4.7.1"), new QuestionType("text"), + new Question("In the context of RCS, what is the branching? What is the need for branching?"), + new StudentAnswer(""), + new ModelAnswer("No suggested answer.")), + new Exercise(new QuestionIndex("4.7.2"), new QuestionType("text"), + new Question("In the context of RCS, what is the merging branches? " + + "How can it lead to merge conflicts?"), + new StudentAnswer(""), + new ModelAnswer("No suggested answer.")), + + // week 5 + new Exercise(new QuestionIndex("5.4.1"), new QuestionType("choice"), + new Question("Which of these are suitable as class-level variables?\n" + + "\n" + + "a. system: multi-player Pac Man game, Class: Player, variable: totalScore\n" + + "b. system: eLearning system, class: Course, variable: totalStudents\n" + + "c. system: ToDo manager, class: Task, variable: totalPendingTasks\n" + + "d. system: any, class: ArrayList, variable: total " + + "(i.e., total items in a given ArrayList object)"), + new StudentAnswer(""), + new ModelAnswer("c. totalPendingTasks should not be managed by individual Task objects " + + "and therefore suitable to be maintained as a class-level variable. " + + "The other variables should be managed at instance level " + + "as their value varies from instance to instance. " + + "e.g., totalStudents for one Course object will differ from totalStudents of another.")), + new Exercise(new QuestionIndex("5.6.1"), new QuestionType("choice"), + new Question("Which one of these is recommended not to use in UML diagrams " + + "because it adds more confusion than clarity?\n" + + "\n" + + "a. Composition symbol\n" + + "b. Aggregation symbol"), + new StudentAnswer(""), + new ModelAnswer("b.")), + new Exercise(new QuestionIndex("5.8.1"), new QuestionType("choice"), + new Question("Given below are some requirements of TEAMMATES " + + "(an online peer evaluation system for education). " + + "Which one of these are non-functional requirements?\n" + + "\n" + + "a. The response to any use action should become visible within 5 seconds.\n" + + "b. The application admin should be able to view a log of user activities.\n" + + "c. The source code should be open source.\n" + + "d. A course should be able to have up to 2000 students.\n" + + "e. As a student user, I can view details of my team members " + + "so that I can know who they are.\n" + + "f. The user interface should be intuitive enough for users who are not IT-savvy.\n" + + "g. The product is offered as a free online service."), + new StudentAnswer(""), + new ModelAnswer("a c d f g. (b) are (e) are functions available for a specific user types. " + + "Therefore, they are functional requirements. " + + "(a), (c), (d), (f) and (g) are either constraints on functionality " + + "or constraints on how the project is done, " + + "both of which are considered non-functional requirements.")), + new Exercise(new QuestionIndex("5.9.1"), new QuestionType("choice"), + new Question("What is the key characteristic about brainstorming?\n" + + "\n" + + " a. There should be at least 5 participants.\n" + + " b. All ideas are welcome. There are no bad ideas.\n" + + " c. Only the best people in the team should take part.\n" + + " d. They are a good way to eliminate bad ideas."), + new StudentAnswer(""), + new ModelAnswer("b.")), + + // week 6 + new Exercise(new QuestionIndex("6.1.1"), new QuestionType("text"), + new Question("Discuss pros and cons of developers testing their own code."), + new StudentAnswer(""), + new ModelAnswer("Pros:\n" + + "\n" + + "Can be done early (the earlier we find a bug, the cheaper it is to fix).\n" + + "Can be done at lower levels, for examples, at operation and class level " + + "(testers usually test the system at UI level).\n" + + "It is possible to do more thorough testing because " + + "developers know the expected external behavior " + + "as well as the internal structure of the component.\n" + + "It forces developers to take responsibility for their own work " + + "(they cannot claim that \"testing is the job of the testers\").\n" + + "\n" + + "Cons:\n" + + "\n" + + "A developer may unconsciously test only situations that he knows to work " + + "(i.e. test it too 'gently').\n" + + "A developer may be blind to his own mistakes" + + "(if he did not consider a certain combination of input while writing code, " + + "it is possible for him to miss it again during testing).\n" + + "A developer may have misunderstood what the SUT is supposed to do in the first place.\n" + + "A developer may lack the testing expertise.")), + new Exercise(new QuestionIndex("6.1.2"), new QuestionType("choice"), + new Question("The cost of fixing a bug goes down as we reach the product release.\n" + + "\n" + + "a. True\n" + + "b. False"), + new StudentAnswer(""), + new ModelAnswer("b. False. The cost goes up over time.")), + new Exercise(new QuestionIndex("6.1.3"), new QuestionType("text"), + new Question("Explain why early testing by developers is important."), + new StudentAnswer(""), + new ModelAnswer("No suggested answer.")), + new Exercise(new QuestionIndex("6.4.1"), new QuestionType("choice"), + new Question("Choose the correct statements about abstract classes and concrete classes.\n" + + "\n" + + "a. A concrete class can contain an abstract method.\n" + + "b. An abstract class can contain concrete methods.\n" + + "c. An abstract class need not contain any concrete methods.\n" + + "d. An abstract class cannot be instantiated."), + new StudentAnswer(""), + new ModelAnswer("b c d. A concrete class cannot contain even a single abstract method.")), + + // week 7 + new Exercise(new QuestionIndex("7.2.1"), new QuestionType("choice"), + new Question("Choose the correct statement\n" + + "\n" + + "a. The architecture of a system should be simple enough " + + "for all team members to understand it.\n" + + "b. The architecture is primarily a high-level design of the system.\n" + + "c. The architecture is usually decided by the project manager.\n" + + "d. The architecture can contain details private to a component."), + new StudentAnswer(""), + new ModelAnswer("a b. Not (c) because architecture is usually designed by the Architect. " + + "Not (d) because ... private details of elements—details having to do solely " + + "with internal implementation—are not architectural.")), + new Exercise(new QuestionIndex("7.3.1"), new QuestionType("choice"), + new Question("Choose the correct statements\n" + + "\n" + + "a. A software component can have an API.\n" + + "b. Any method of a class is part of its API.\n" + + "c. Private methods of a class are not part of its API.\n" + + "d. The API forms the contract between the component developer and the component user.\n" + + "e. Sequence diagrams can be used to show how components interact " + + "with each other via APIs."), + new StudentAnswer(""), + new ModelAnswer("a c d e. (b) is incorrect because private methods cannot be a part of the API.")), + new Exercise(new QuestionIndex("7.3.2"), new QuestionType("choice"), + new Question("Defining component APIs early is useful for developing components in parallel.\n" + + "\n" + + "a. True\n" + + "b. False"), + new StudentAnswer(""), + new ModelAnswer("a. True. Yes, once we know the precise behavior expected of each component, " + + "we can start developing them in parallel.\n")), + new Exercise(new QuestionIndex("7.6.1"), new QuestionType("choice"), + new Question("A Calculator program crashes with an ‘assertion failure’ message " + + "when you try to find the square root of a negative number.\n" + + "\n" + + "a. This is a correct use of assertions.\n" + + "b. The application should have terminated with an exception instead.\n" + + "c. The program has a bug.\n" + + "d. All statements above are incorrect."), + new StudentAnswer(""), + new ModelAnswer("c. An assertion failure indicates a bug in the code. " + + "(b) is not acceptable because of the word \"terminated\". " + + "The application should not fail at all for this input. " + + "But it could have used an exception to handle the situation internally.")), + new Exercise(new QuestionIndex("7.6.2"), new QuestionType("choice"), + new Question("Which statements are correct?\n" + + "\n" + + " a. Use assertions to indicate the programmer messed up; " + + "Use exceptions to indicate the user or the environment messed up.\n" + + " b. Use exceptions to indicate the programmer messed up; " + + "Use assertions to indicate the user or the environment messed up."), + new StudentAnswer(""), + new ModelAnswer("a.")), + new Exercise(new QuestionIndex("7.8.1"), new QuestionType("choice"), + new Question("Gradle_is used used for,\n" + + "\n" + + "a. better revision control\n" + + "b. build automation\n" + + "c. UML diagramming\n" + + "d. project collaboration"), + new StudentAnswer(""), + new ModelAnswer("b.")), + + // week 8 + new Exercise(new QuestionIndex("8.4.1"), new QuestionType("text"), + new Question("Explain the link (if any) between regressions and coupling."), + new StudentAnswer(""), + new ModelAnswer("When the system is highly-coupled, the risk of regressions is higher too " + + "e.g. when component A is modified, all components ‘coupled’ to component A " + + "risk ‘unintended behavioral changes’.\n")), + new Exercise(new QuestionIndex("8.4.2"), new QuestionType("text"), + new Question("Discuss the relationship between coupling and testability.\n"), + new StudentAnswer(""), + new ModelAnswer("Coupling decreases testability because if the SUT is coupled to many other components " + + "it becomes difficult to test the SUI in isolation of its dependencies.")), + new Exercise(new QuestionIndex("8.4.3"), new QuestionType("choice"), + new Question("Choose the correct statements.\n" + + "\n" + + "a. As coupling increases, testability decreases.\n" + + "b. As coupling increases, the risk of regression increases.\n" + + "c. As coupling increases, the value of automated regression testing increases.\n" + + "d. As coupling increases, integration becomes easier as everything is connected together.\n" + + "e. As coupling increases, maintainability decreases."), + new StudentAnswer(""), + new ModelAnswer("a b c e. High coupling means either more components require to be integrated at once " + + "in a big-bang fashion (increasing the risk of things going wrong) or more drivers " + + "and stubs are required when integrating incrementally.")), + new Exercise(new QuestionIndex("8.4.4"), new QuestionType("choice"), + new Question("Which of these indicate a coupling between components A and B?\n" + + "\n" + + "a. component A has access to internal structure of component B.\n" + + "b. component A and B are written by the same developer.\n" + + "c. component A calls component B.\n" + + "d. component A receives an object of component B as a parameter.\n" + + "e. component A inherits from component B.\n" + + "f. components A and B have to follow the same data format or communication protocol."), + new StudentAnswer(""), + new ModelAnswer("a c d e f. Being written by the same developer does not imply a coupling.")), + new Exercise(new QuestionIndex("8.4.5"), new QuestionType("choice"), + new Question("“Only the GUI class should interact with the user. " + + "The GUI class should only concern itself with user interactionsâ€?. " + + "This statement follows from,\n" + + "\n" + + "a. A software design should promote separation of concerns in a design.\n" + + "b. A software design should increase cohesion of its components.\n" + + "c. A software design should follow single responsibility principle."), + new StudentAnswer(""), + new ModelAnswer("a b c. By making ‘user interaction’ GUI class’ sole responsibility, " + + "we increase its cohesion. This is also in line with separation of concerns " + + "(i.e., we separated the concern of user interaction) " + + "and single responsibility principle (GUI class has only one responsibility).")), + new Exercise(new QuestionIndex("8.4.6"), new QuestionType("choice"), + new Question("Which of these is closest to the meaning of the open-closed principle?\n" + + "\n" + + "a. We should be able to change a software module’s behavior without modifying its code.\n" + + "b. A software module should remain open to modification as long as possible.\n" + + "c. A software module should be open to modification and closed to extension.\n" + + "d. Open source software rocks. Closed source software sucks."), + new StudentAnswer(""), + new ModelAnswer("a. Please refer the handout for the definition of OCP.")), + new Exercise(new QuestionIndex("8.7.1"), new QuestionType("choice"), + new Question("Stubs help us to test a component in isolation from its dependencies.\n" + + "\n" + + "a. True\n" + + "b. False"), + new StudentAnswer(""), + new ModelAnswer("a. True.")), + new Exercise(new QuestionIndex("8.7.2"), new QuestionType("choice"), + new Question("Choose correct statement about dependency injection\n" + + "\n" + + "a. It is a technique for increasing dependencies\n" + + "b. It is useful for unit testing\n" + + "c. It can be done using polymorphism\n" + + "d. It can be used to substitute a component with a stub"), + new StudentAnswer(""), + new ModelAnswer("b c d. " + + "It is a technique we can use to substitute an existing dependency with another, " + + "not increase dependencies. It is useful when you want to test a component in isolation " + + "but the SUT depends on other components. Using dependency injection, " + + "we can substitute those other components with test-friendly stubs. " + + "This is often done using polymorphism.")), + + // week 9 + new Exercise(new QuestionIndex("9.2.1"), new QuestionType("choice"), + new Question("Which one of these is least related to how OO programs achieve polymorphism?\n" + + "\n" + + "a. substitutability\n" + + "b. dynamic binding\n" + + "c. operation overloading\n" + + "d. interfaces\n" + + "e. abstract classes"), + new StudentAnswer(""), + new ModelAnswer("c. Operation overriding is the one that is related, not operation overloading. " + + "Interfaces and abstract classes, although not required, " + + "can be used in achieving polymorphism.")), + new Exercise(new QuestionIndex("9.2.2"), new QuestionType("choice"), + new Question("Top-down design is better than bottom-up design.\n" + + "\n" + + "a. True\n" + + "b. False"), + new StudentAnswer(""), + new ModelAnswer("b. False. Not necessarily. It depends on the situation. " + + "Bottom-up design may be preferable when there are lot of existing components " + + "we want to reuse.")), + new Exercise(new QuestionIndex("9.2.3"), new QuestionType("choice"), + new Question("Agile design camp expects the design to change over the product’s lifetime.\n" + + "\n" + + "a. True\n" + + "b. False"), + new StudentAnswer(""), + new ModelAnswer("a. True. Yes, that is why they do not believe in spending too much time " + + "creating a detailed and full design at the very beginning. " + + "However, the architecture is expected to remain relatively stable " + + "even in the agile design approach.")), + new Exercise(new QuestionIndex("9.2.4"), new QuestionType("choice"), + new Question("If a subclass imposes more restrictive conditions than its parent class, " + + "it violates Liskov Substitution Principle.\n" + + "\n" + + "a. True\n" + + "b. False"), + new StudentAnswer(""), + new ModelAnswer("a. True. If the subclass is more restrictive than the parent class, " + + "code that worked with the parent class may not work with the child class. " + + "Hence, the substitutability does not exist and LSP has been violated.")), + new Exercise(new QuestionIndex("9.2.5"), new QuestionType("choice"), + new Question("Which of these statements is true about the Dependency Inversion Principle.\n" + + "\n" + + "a. It can complicate the design/implementation by introducing extra abstractions, " + + "but it has some benefits.\n" + + "b. It is often used during testing, to replace dependencies with mocks.\n" + + "c. It reduces dependencies in a design.\n" + + "d. It advocates making higher level classes to depend on lower level classes."), + new StudentAnswer(""), + new ModelAnswer("a. Replacing dependencies with mocks is Dependency Injection, not DIP. " + + "DIP does not reduce dependencies, rather, it changes the direction of dependencies. " + + "Yes, it can introduce extra abstractions " + + "but often the benefit can outweigh the extra complications.")), + new Exercise(new QuestionIndex("9.3.1"), new QuestionType("choice"), + new Question("Bidirectional associations, if not implemented properly, " + + "can result in referential integrity violations.\n" + + "\n" + + "a. True\n" + + "b. False"), + new StudentAnswer(""), + new ModelAnswer("a. True. Bidirectional associations require two objects to link to each other. " + + "When one of these links is not consistent with the other, " + + "we have a referential integrity violation.")), + new Exercise(new QuestionIndex("9.3.2"), new QuestionType("choice"), + new Question("Defensive programming,\n" + + "\n" + + "a. can make the program slower.\n" + + "b. can make the code longer.\n" + + "c. can make the code more complex.\n" + + "d. can make the code less susceptible to misuse.\n" + + "e. can require extra effort."), + new StudentAnswer(""), + new ModelAnswer("All. Defensive programming requires a more checks, " + + "possibly making the code longer, more complex, and possibly slower. " + + "Use it only when benefits outweigh costs, which is often.")), + new Exercise(new QuestionIndex("9.3.3"), new QuestionType("choice"), + new Question("Which statements are correct?\n" + + "\n" + + "a. It is not natively supported by Java and C++.\n" + + "b. It is an alternative to OOP.\n" + + "c. It assumes the caller of a method is responsible for " + + "ensuring all preconditions are met."), + new StudentAnswer(""), + new ModelAnswer("a c. DbC is not an alternative to OOP. We can use DbC in an OOP solution.")), + new Exercise(new QuestionIndex("9.4.1"), new QuestionType("choice"), + new Question("Choose correct statements about API documentation.\n" + + "\n" + + "a. They are useful for both developers who use the API " + + "and developers who maintain the API implementation.\n" + + "b. There are tools that can generate API documents from code comments.\n" + + "d. API documentation may contain code examples."), + new StudentAnswer(""), + new ModelAnswer("All.")), + new Exercise(new QuestionIndex("9.4.2"), new QuestionType("choice"), + new Question("It is recommended for developer documents,\n" + + "\n" + + "a. to have separate sections for each type of diagrams " + + "such as class diagrams, sequence diagrams, use case diagrams etc.\n" + + "b. to give a high priority to comprehension too, not stop at comprehensiveness only."), + new StudentAnswer(""), + new ModelAnswer("b. (a) Use diagrams when they help to understand the text descriptions. " + + "Text and diagrams should be used in tandem. " + + "Having separate sections for each diagram type is a sign of generating diagrams " + + "for the sake of having them.\n" + + "\n" + + "(b) Both are important, but lengthy, complete, accurate yet hard to understand documents " + + "are not that useful.")), + new Exercise(new QuestionIndex("9.5.1"), new QuestionType("choice"), + new Question("Which of these gives us the highest intensity of testing?\n" + + "\n" + + " a. 100% statement coverage\n" + + " b. 100% path coverage\n" + + " c. 100% branch coverage\n" + + " d. 100% condition coverage"), + new StudentAnswer(""), + new ModelAnswer("b. 100% path coverage implies all possible execution paths " + + "through the SUT have been tested. This is essentially ‘exhaustive testing’. " + + "While this is very hard to achieve for a non-trivial SUT, " + + "it technically gives us the highest intensity of testing. " + + "If all tests pass at 100% path coverage, the SUT code can be considered ‘bug free’. " + + "However, note that path coverage does not include paths that are missing from the code " + + "altogether because the programmer left them out by mistake.")), + new Exercise(new QuestionIndex("9.5.2"), new QuestionType("choice"), + new Question("In TDD, we write all the test cases before we start writing functional code.\n" + + "\n" + + "a. True\n" + + "b. False"), + new StudentAnswer(""), + new ModelAnswer("b. False. No, not all. We proceed in small steps, " + + "writing tests and functional code in tandem, " + + "but writing the test before we write the corresponding functional code.")), + new Exercise(new QuestionIndex("9.5.3"), new QuestionType("choice"), + new Question("Testing tools such as Junit require us to follow TDD.\n" + + "\n" + + "a. True\n" + + "b. False"), + new StudentAnswer(""), + new ModelAnswer("b. False. They can be used for TDD, but they can be used without TDD too.")), + new Exercise(new QuestionIndex("9.6.1"), new QuestionType("choice"), + new Question("GUI testing is usually easier than API testing because " + + "it doesn’t require any extra coding.\n" + + "\n" + + "a. True\n" + + "b. False"), + new StudentAnswer(""), + new ModelAnswer("b. False.")), + new Exercise(new QuestionIndex("9.6.2"), new QuestionType("choice"), + new Question("Choose the correct statements about system testing and acceptance testing.\n" + + "\n" + + "a. Both system testing and acceptance testing typically involve the whole system.\n" + + "b. System testing is typically more extensive than acceptance testing.\n" + + "c. System testing can include testing for non-functional qualities.\n" + + "d. Acceptance testing typically has more user involvement than system testing.\n" + + "e. In smaller projects, the developers may do system testing as well, " + + "in addition to developer testing.\n" + + "f. If system testing is adequately done, we need not do acceptance testing."), + new StudentAnswer(""), + new ModelAnswer("a b c d e. (b) is correct because system testing can aim to cover all " + + "specified behaviors and can even go beyond the system specification. " + + "Therefore, system testing is typically more extensive than acceptance testing.\n" + + "\n" + + "(f) is incorrect because it is possible for a system to pass system tests " + + "but fail acceptance tests.")), + + // week 10 + new Exercise(new QuestionIndex("10.1.1"), new QuestionType("choice"), + new Question("Pick the odd one out.\n" + + "\n" + + "a. Law of Demeter.\n" + + "b. Don’t add people to a late project.\n" + + "c. Don’t talk to strangers.\n" + + "d. Principle of least knowledge.\n" + + "e. Coupling."), + new StudentAnswer(""), + new ModelAnswer("b. Law of Demeter, which aims to reduce coupling, " + + "is also known as ‘Don’t talk to strangers’ and ‘Principle of least knowledge’.")), + new Exercise(new QuestionIndex("10.1.2"), new QuestionType("text"), + new Question("Do the Brook’s Law apply to a school project? Justify."), + new StudentAnswer(""), + new ModelAnswer("Yes. Adding a new student to a project team " + + "can result in a slow-down of the project for a short period. " + + "This is because the new member needs time to learn the project " + + "and existing members will have to spend time helping the new guy get up to speed. " + + "If the project is already behind schedule and near a deadline, " + + "this could delay the delivery even further.")), + new Exercise(new QuestionIndex("10.1.3"), new QuestionType("choice"), + new Question("Which one of these (all attributed to Fred Brooks, " + + "the author of the famous SE book The Mythical Man-Month), is called the Brook’s law?\n" + + "\n" + + " a. All programmers are optimists.\n" + + " b. Good judgement comes from experience, and experience comes from bad judgement.\n" + + " c. The bearing of a child takes nine months, no matter how many women are assigned.\n" + + " d. Adding more manpower to an already late project makes it even later."), + new StudentAnswer(""), + new ModelAnswer("d.")), + new Exercise(new QuestionIndex("10.3.1"), new QuestionType("choice"), + new Question("Which one of these describes the ‘software design patterns’ concept best?\n" + + "\n" + + " a. Designs that appear repetitively in software.\n" + + " b. Elegant solutions to recurring problems in software design.\n" + + " c. Architectural styles used in applications.\n" + + " d. Some good design techniques proposed by the Gang of Four"), + new StudentAnswer(""), + new ModelAnswer("b.")), + new Exercise(new QuestionIndex("10.3.2"), new QuestionType("choice"), + new Question("When we describe a pattern, we must also specify anti-patterns.\n" + + "\n" + + "a. True\n" + + "b. False"), + new StudentAnswer(""), + new ModelAnswer("b. False. Anti-patterns are related to patterns, " + + "but they are not a ‘must have’ component of a pattern description.")), + new Exercise(new QuestionIndex("10.3.3"), new QuestionType("choice"), + new Question("We use the Singleton pattern when\n" + + "\n" + + "a. we want an a class with a private constructor.\n" + + "b. we want a single class to hold all functionality of the system.\n" + + "c. we want a class with no more than one instance.\n" + + "d. we want to hide internal structure of a component from its clients."), + new StudentAnswer(""), + new ModelAnswer("c.")), + new Exercise(new QuestionIndex("10.4.1"), new QuestionType("choice"), + new Question("Choose correct statements about software frameworks.\n" + + "\n" + + "a. They follow the hollywood principle, otherwise known as ‘inversion of control’\n" + + "b. They come with full or partial implementation.\n" + + "c. They are more concrete than patterns or principles.\n" + + "d. They are often configurable.\n" + + "e. They are reuse mechanisms.\n" + + "f. They are similar to reusable libraries but bigger."), + new StudentAnswer(""), + new ModelAnswer("a b c d e. While both libraries and frameworks are reuse mechanisms, " + + "and both more concrete than principles and patterns, " + + "libraries differ from frameworks in some key ways. " + + "One of them is the ‘inversion of control’ used by frameworks but not libraries. " + + "Furthermore, frameworks do not have to be bigger than libraries all the time.")), + new Exercise(new QuestionIndex("10.4.2"), new QuestionType("choice"), + new Question("Which one of these are frameworks ?\n" + + "\n" + + "a. JUnit\n" + + "b. Eclipse\n" + + "c. Drupal\n" + + "d. Ruby on Rails"), + new StudentAnswer(""), + new ModelAnswer("All. These are frameworks.")), + + // week 11 + new Exercise(new QuestionIndex("11.1.1"), new QuestionType("choice"), + new Question("What is the main difference between a class diagram and and an OO domain model?\n" + + "\n" + + "a. One is about the problem domain while the other is about the solution domain.\n" + + "b. One has more classes than the other.\n" + + "c. One shows more details than the other.\n" + + "d. One is a UML diagram, while the other is not a UML diagram."), + new StudentAnswer(""), + new ModelAnswer("a. Both are UML diagrams, and use the class diagram notation. " + + "While it is true that often a class diagram may have more classes and more details, " + + "the main difference is that the OO domain model describes the problem domain " + + "while the class diagram describes the solution.")), + new Exercise(new QuestionIndex("11.3.1"), new QuestionType("text"), + new Question("Here are some common elements of a design pattern: " + + "Name, Context, Problem, Solution, Anti-patterns (optional), Consequences (optional), " + + "other useful information (optional).\n" + + "\n" + + "Using similar elements, describe a pattern that is not a design pattern. " + + "It must be a pattern you have noticed, not a pattern already documented by others. " + + "You may also give a pattern not related to software.\n" + + "\n" + + "Some examples:\n" + + "- A pattern for testing textual UIs.\n" + + "- A pattern for striking a good bargain at a mall such as Sim-Lim Square."), + new StudentAnswer(""), + new ModelAnswer("No suggested answer.")), + new Exercise(new QuestionIndex("11.4.1"), new QuestionType("choice"), + new Question("Applying the heuristics covered so far, we can determine the precise number of " + + "test cases required to test any given SUT effectively.\n" + + "\n" + + "a. True\n" + + "b. False"), + new StudentAnswer(""), + new ModelAnswer("b. False. These heuristics are, well, heuristics only. " + + "They will help you to make better decisions about test case design. " + + "However, they are speculative in nature (especially, when testing in black-box fashion) " + + "and cannot give you precise number of test cases.")), + new Exercise(new QuestionIndex("11.4.2"), new QuestionType("choice"), + new Question("Which of these contradict the heuristics recommended " + + "when creating test cases with multiple inputs?\n" + + "\n" + + "a. All invalid test inputs must be tested together.\n" + + "b. It is ok to combine valid values for different inputs.\n" + + "c. No more than one invalid test input should be in a given test case.\n" + + "d. Each valid test input should appear at least once " + + "in a test case that doesn’t have any invalid inputs."), + new StudentAnswer(""), + new ModelAnswer("a. If you test all invalid test inputs together, " + + "you will not know if each one of the invalid inputs are handled correctly by the SUT. " + + "This is because most SUTs return an error message " + + "upon encountering the first invalid input.")), + new Exercise(new QuestionIndex("11.6.1"), new QuestionType("choice"), + new Question("Choose the correct statements about agile processes.\n" + + "\n" + + "a. They value working software over comprehensive documentation.\n" + + "b. They value responding to change over following a plan.\n" + + "c. They may not be suitable for some type of projects.\n" + + "d. XP and Scrum are agile processes."), + new StudentAnswer(""), + new ModelAnswer("a b c d.")), + new Exercise(new QuestionIndex("11.7.1"), new QuestionType("choice"), + new Question("Choose the correct statements about the unified process.\n" + + "\n" + + "a. It was conceived by the three amigos who also created UML.\n" + + "b. The Unified process requires the use of UML.\n" + + "c. The Unified process is actually a process framework rather than a fixed process.\n" + + "d. The Unified process can be iterative and incremental"), + new StudentAnswer(""), + new ModelAnswer("a c d. Although UP was created by the same three amigos who created UML, ")) + }; + } + +} +``` +###### \java\seedu\progresschecker\storage\XmlAdaptedExercise.java +``` java +/** + * JAXB-friendly version of the Exercise. + */ +public class XmlAdaptedExercise { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Exercise's %s field is missing!"; + + @XmlElement(required = true) + private String questionIndex; + @XmlElement(required = true) + private String questionType; + @XmlElement(required = true) + private String question; + @XmlElement(required = true) + private String studentAnswer; + @XmlElement(required = true) + private String modelAnswer; + + /** + * Constructs an XmlAdaptedExercise. + * This is the no-arg constructor that is required by JAXB. + */ + public XmlAdaptedExercise() {} + + /** + * Constructs an {@code XmlAdaptedExercise} with the given exercise details. + */ + public XmlAdaptedExercise( + String questionIndex, String questionType, String question, + String studentAnswer, String modelAnswer) { + this.questionIndex = questionIndex; + this.questionType = questionType; + this.question = question; + this.studentAnswer = studentAnswer; + this.modelAnswer = modelAnswer; + } + + /** + * Converts a given Exercise into this class for JAXB use. + * + * @param source future changes to this will not affect the created XmlAdaptedExercise + */ + public XmlAdaptedExercise(Exercise source) { + questionIndex = source.getQuestionIndex().value; + questionType = source.getQuestionType().value; + question = source.getQuestion().value; + studentAnswer = source.getStudentAnswer().value; + modelAnswer = source.getModelAnswer().value; + } + + /** + * Converts this jaxb-friendly adapted exercise object into the model's Exercise object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted exercise + */ + public Exercise toModelType() throws IllegalValueException { + if (this.questionIndex == null) { + throw new IllegalValueException( + String.format(MISSING_FIELD_MESSAGE_FORMAT, + QuestionIndex.class.getSimpleName())); + } + if (!QuestionIndex.isValidIndex(this.questionIndex)) { + throw new IllegalValueException(QuestionIndex.MESSAGE_INDEX_CONSTRAINTS); + } + final QuestionIndex questionIndex = new QuestionIndex(this.questionIndex); + + if (this.questionType == null) { + throw new IllegalValueException( + String.format(MISSING_FIELD_MESSAGE_FORMAT, + QuestionType.class.getSimpleName())); + } + if (!QuestionType.isValidType(this.questionType)) { + throw new IllegalValueException(QuestionType.MESSAGE_TYPE_CONSTRAINTS); + } + final QuestionType questionType = new QuestionType(this.questionType); + + if (this.question == null) { + throw new IllegalValueException( + String.format(MISSING_FIELD_MESSAGE_FORMAT, + Question.class.getSimpleName())); + } + final Question question = new Question(this.question); + + if (this.studentAnswer == null) { + throw new IllegalValueException( + String.format(MISSING_FIELD_MESSAGE_FORMAT, + StudentAnswer.class.getSimpleName())); + } + final StudentAnswer studentAnswer = new StudentAnswer(this.studentAnswer); + + if (this.modelAnswer == null) { + throw new IllegalValueException( + String.format(MISSING_FIELD_MESSAGE_FORMAT, + ModelAnswer.class.getSimpleName())); + } + final ModelAnswer modelAnswer = new ModelAnswer(this.modelAnswer); + + return new Exercise(questionIndex, questionType, question, studentAnswer, modelAnswer); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlAdaptedExercise)) { + return false; + } + + XmlAdaptedExercise otherExercise = (XmlAdaptedExercise) other; + return Objects.equals(questionIndex, otherExercise.questionIndex) + && Objects.equals(questionType, otherExercise.questionType) + && Objects.equals(question, otherExercise.question); + } +} +``` +###### \java\seedu\progresschecker\ui\ExerciseCard.java +``` java +/** + * An UI component that displays information of an {@code Exercise}. + */ +public class ExerciseCard extends UiPart<Region> { + + private static final String FXML = "ExerciseListCard.fxml"; + + public final Exercise exercise; + + @FXML + private Label questionIndex; + @FXML + private Label questionType; + @FXML + private Label question; + @FXML + private Label studentAnswer; + @FXML + private Label modelAnswerHeader; + @FXML + private Label modelAnswer; + + public ExerciseCard(Exercise exercise) { + super(FXML); + this.exercise = exercise; + questionIndex.setText(exercise.getQuestionIndex().value); + question.setText(exercise.getQuestion().value); + studentAnswer.setText(exercise.getStudentAnswer().value); + modelAnswer.setText(""); + + if (!exercise.getStudentAnswer().value.equals("")) { + questionIndex.getStyleClass().add("answered"); + modelAnswerHeader.setText("Suggested Answer: "); + modelAnswer.setText(exercise.getModelAnswer().value); + } + } +} +``` +###### \java\seedu\progresschecker\ui\ExerciseListPanel.java +``` java +/** + * Panel containing the list of exercises. + */ +public class ExerciseListPanel extends UiPart<Region> { + private static final String FXML = "ExerciseListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(ExerciseListPanel.class); + + @FXML + private ListView<ExerciseCard> exerciseListView; + + public ExerciseListPanel(ObservableList<Exercise> exerciseList) { + super(FXML); + setConnections(exerciseList); + registerAsAnEventHandler(this); + } + + private void setConnections(ObservableList<Exercise> exerciseList) { + ObservableList<ExerciseCard> mappedList = EasyBind.map( + exerciseList, (exercise) -> new ExerciseCard(exercise)); + exerciseListView.setItems(mappedList); + exerciseListView.setCellFactory(listView -> new ExerciseListViewCell()); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code ExerciseCard}. + */ + class ExerciseListViewCell extends ListCell<ExerciseCard> { + + @Override + protected void updateItem(ExerciseCard exercise, boolean empty) { + super.updateItem(exercise, empty); + + if (empty || exercise == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(exercise.getRoot()); + } + } + } + +} +``` +###### \java\seedu\progresschecker\ui\MainWindow.java +``` java + @Subscribe + private void handleTabLoadChangedEvent(TabLoadChangedEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + SingleSelectionModel<Tab> selectionModel = tabPlaceholder.getSelectionModel(); + switch (event.getTabName()) { + case "profile": + selectionModel.select(profilePlaceholder); + break; + case "task": + selectionModel.select(taskPlaceholder); + break; + case "exercise": + selectionModel.select(exercisePlaceholder); + break; + case "issues": + selectionModel.select(issuePlaceholder); + break; + default: + selectionModel.select(selectionModel.getSelectedItem()); + } + } +``` +###### \java\seedu\progresschecker\ui\PersonCard.java +``` java + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof PersonCard)) { + return false; + } + + // state check + PersonCard card = (PersonCard) other; + return id.getText().equals(card.id.getText()) + && person.equals(card.person); + } +} +``` +###### \java\seedu\progresschecker\ui\ProfilePanel.java +``` java + /** + * Get a deterministic tag color based off tag's name value. + */ + private String getTagColor(String tagName) { + int index = getValueOfString(tagName) % TAG_COLORS.length; + return TAG_COLORS[index]; + } + + /** + * Adds each letter of given string into an integer. + */ + private int getValueOfString(String tagName) { + int sum = 0; + for (char c : tagName.toCharArray()) { + sum += c; + } + return sum; + } +``` +###### \java\seedu\progresschecker\ui\ProfilePanel.java +``` java + person.getTags().forEach(tag -> { + Label label = new Label(tag.tagName); + label.getStyleClass().add(getTagColor(tag.tagName)); + tags.getChildren().add(label); + }); +``` +###### \resources\view\ExerciseListCard.fxml +``` fxml +<VBox id="cardPane" fx:id="cardPane" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"> + <padding> + <Insets bottom="5" left="15" right="5" top="5" /> + </padding> + <VBox prefWidth="325.0" styleClass="exercise"> + <children> + <Label fx:id="questionIndex" text="\$questionIndex" wrapText="true" styleClass="question-number"/> + <Label fx:id="question" text="\$question" wrapText="true"/> + <Text/> + <Separator/> + <Text/> + + <Label text="Your Answer: "/> + <Label fx:id="studentAnswer" text="\$studentAnswer" wrapText="true"/> + <Text/> + + <Label fx:id="modelAnswerHeader" styleClass="exercise-header, suggested-answer"/> + <Label fx:id="modelAnswer" text="\$modelAnswer" wrapText="true" styleClass="suggested-answer"/> + </children> + </VBox> +</VBox> +``` +###### \resources\view\ExerciseListPanel.fxml +``` fxml +<VBox xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"> + <ListView fx:id="exerciseListView" VBox.vgrow="ALWAYS" /> +</VBox> +``` diff --git a/collated/test/EdwardKSG.md b/collated/test/EdwardKSG.md new file mode 100644 index 000000000000..2927eafc6b51 --- /dev/null +++ b/collated/test/EdwardKSG.md @@ -0,0 +1,1033 @@ +# EdwardKSG +###### \java\guitests\guihandles\BrowserPanelHandle.java +``` java + /** + * Returns the {@code String title} of the currently loaded page. + */ + public String getLoadedTitle() { + return WebViewUtil.getLoadedTitle(getChildNode(BROWSER_ID)); + } +``` +###### \java\guitests\guihandles\WebViewUtil.java +``` java + /** + * Returns the {@code String} of the currently loaded page in the {@code webView}. + */ + public static String getLoadedTitle(WebView webView) { + return webView.getEngine().getTitle(); + } +``` +###### \java\seedu\progresschecker\logic\commands\AddDefaultTasksCommandTest.java +``` java +/** + * Contains assertion tests for {@code AddDefaultTasksCommand}. This command is not undoable. + * This test may take a long time to execute, roughly 20s. + */ +public class AddDefaultTasksCommandTest { + public static final String TEST_TITLE = "testTitle"; + @Test + public void execute_commandEquals() throws Exception { + AddDefaultTasksCommand completeTaskCommand = new AddDefaultTasksCommand(DEFAULT_LIST_TITLE); + AddDefaultTasksCommand completeTaskCommand2 = new AddDefaultTasksCommand("random thing"); + + // same object -> execution successful + assertTrue(completeTaskCommand.equals(completeTaskCommand)); + + // different object -> execution failed + assertFalse(completeTaskCommand.equals(completeTaskCommand2)); + } + + /* Outdated: Decided to remove this test because: 1. this test will add a new task list and the content of the + list is still being updated while before the final release. Once the list data is updated by us developers, the + edge condition and expected output for tests of complete/reset task command and view URL command will all must be + updated which is very tedious. 2. the result of this command is easy to observe and no repetitive tests involved + 3. this test takes a long time, which slows down the process when other developers build the project. + Current solution: have a special fixed test data file which is small. */ + @Test + public void execute_success() throws Exception { + AddDefaultTasksCommand addDefaultTasksCommand = new AddDefaultTasksCommand(TEST_TITLE); + + String expected = String.format(MESSAGE_SUCCESS, DEFAULT_LIST_TITLE); + String actual = addDefaultTasksCommand.execute().feedbackToUser; + assertEquals(expected, actual); + } + +} +``` +###### \java\seedu\progresschecker\logic\commands\CompleteTaskCommandTest.java +``` java +/** + * Contains assertion tests for {@code CompleteTaskCommand}. This command is not undoable. + */ +public class CompleteTaskCommandTest { + + @Test + public void execute_commandEquals() throws Exception { + CompleteTaskCommand completeTaskCommand = new CompleteTaskCommand(INDEX_FIRST_TASK_INT); + CompleteTaskCommand completeTaskCommand2 = new CompleteTaskCommand(INDEX_LAST_TASK_INT); + + // same object -> execution successful + assertTrue(completeTaskCommand.equals(completeTaskCommand)); + + // different object -> execution failed + assertFalse(completeTaskCommand.equals(completeTaskCommand2)); + } + + @Test + public void execute_validIndexFirst_success() throws Exception { + CompleteTaskCommand completeFirst = new CompleteTaskCommand(INDEX_FIRST_TASK_INT); + + String expected = String.format(MESSAGE_SUCCESS, + INDEX_FIRST_TASK_INT + ". LO[W6.5][Submission]"); + String actual = completeFirst.execute().feedbackToUser; + assertEquals(expected, actual); + } + + @Test + public void execute_validIndexLast_success() throws Exception { + CompleteTaskCommand completeLast = new CompleteTaskCommand(INDEX_LAST_TASK_INT); + + String expected = String.format(MESSAGE_SUCCESS, + INDEX_LAST_TASK_INT + ". LO[W3.10][Compulsory][Submission]"); + String actual = completeLast.execute().feedbackToUser; + assertEquals(expected, actual); + } + + @Test + public void execute_validIndexLastTwice_success() throws Exception { + CompleteTaskCommand completeTwice = new CompleteTaskCommand(INDEX_LAST_TASK_INT); + + String expected = String.format(MESSAGE_NO_ACTION, + INDEX_LAST_TASK_INT + ". LO[W3.10][Compulsory][Submission]"); + String actual = completeTwice.execute().feedbackToUser; + assertEquals(expected, actual); + } + + // the case of negative/zero/non-integer are tested in the command parser test. + + @Test + public void execute_invalidIndexZero_success() throws Exception { + CompleteTaskCommand completeOutOfBound = new CompleteTaskCommand(OUT_OF_BOUND_TASK_INDEX_INT); + + String expected = String.format(INDEX_OUT_OF_BOUND); + String actual = completeOutOfBound.execute().feedbackToUser; + assertEquals(expected, actual); + } +} + +``` +###### \java\seedu\progresschecker\logic\commands\EditPersonDescriptorTest.java +``` java + // different major -> returns false + editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withMajor(VALID_MAJOR_BOB).build(); + assertFalse(DESC_AMY.equals(editedAmy)); + + // different year -> returns false + editedAmy = new EditPersonDescriptorBuilder(DESC_AMY).withYear(VALID_YEAR_BOB).build(); + assertFalse(DESC_AMY.equals(editedAmy)); +``` +###### \java\seedu\progresschecker\logic\commands\GoToTaskUrlCommandTest.java +``` java +/** + * Contains assertion tests for {@code GoToTaskUrlCommand}. This command is not undoable. + */ +public class GoToTaskUrlCommandTest { + + @Test + public void execute_commandEquals() throws Exception { + GoToTaskUrlCommand goToTaskUrlCommand = new GoToTaskUrlCommand(INDEX_FIRST_TASK_INT); + GoToTaskUrlCommand goToTaskUrlCommand2 = new GoToTaskUrlCommand(INDEX_LAST_TASK_INT); + + // same object -> execution successful + assertTrue(goToTaskUrlCommand.equals(goToTaskUrlCommand)); + + // different object -> execution failed + assertFalse(goToTaskUrlCommand.equals(goToTaskUrlCommand2)); + } + + @Test + public void execute_validIndexFirst_success() throws Exception { + GoToTaskUrlCommand gotoFirst = new GoToTaskUrlCommand(INDEX_FIRST_TASK_INT); + + String expected = String.format(MESSAGE_SUCCESS, + INDEX_FIRST_TASK_INT + ". LO[W6.5][Submission]"); + String actual = gotoFirst.execute().feedbackToUser; + assertEquals(expected, actual); + } + + @Test + public void execute_validIndexLast_success() throws Exception { + GoToTaskUrlCommand gotoLast = new GoToTaskUrlCommand(INDEX_LAST_TASK_INT); + + String expected = String.format(MESSAGE_SUCCESS, + INDEX_LAST_TASK_INT + ". LO[W3.10][Compulsory][Submission]"); + String actual = gotoLast.execute().feedbackToUser; + assertEquals(expected, actual); + } + + @Test + public void execute_validIndexLastTwice_success() throws Exception { + GoToTaskUrlCommand gotoTwice = new GoToTaskUrlCommand(INDEX_LAST_TASK_INT); + + String expected = String.format(MESSAGE_SUCCESS, + INDEX_LAST_TASK_INT + ". LO[W3.10][Compulsory][Submission]"); + String actual = gotoTwice.execute().feedbackToUser; + assertEquals(expected, actual); + } + + // the case of negative/zero/non-integer are tested in the command parser test. + + @Test + public void execute_invalidIndexZero_success() throws Exception { + GoToTaskUrlCommand gotoOutOfBound = new GoToTaskUrlCommand(OUT_OF_BOUND_TASK_INDEX_INT); + + String expected = String.format(INDEX_OUT_OF_BOUND); + String actual = gotoOutOfBound.execute().feedbackToUser; + assertEquals(expected, actual); + } +} +``` +###### \java\seedu\progresschecker\logic\commands\ResetTaskCommandTest.java +``` java +/** + * Contains assertion tests for {@code ResetTaskCommand}. This command is not undoable. + */ +public class ResetTaskCommandTest { + + @Test + public void execute_commandEquals() throws Exception { + ResetTaskCommand resetTaskCommand = new ResetTaskCommand(INDEX_FIRST_TASK_INT); + ResetTaskCommand resetTaskCommand2 = new ResetTaskCommand(INDEX_LAST_TASK_INT); + + // same object -> execution successful + assertTrue(resetTaskCommand.equals(resetTaskCommand)); + + // different object -> execution failed + assertFalse(resetTaskCommand.equals(resetTaskCommand2)); + } + + @Test + public void execute_validIndexFirst_success() throws Exception { + ResetTaskCommand resetFirst = new ResetTaskCommand(INDEX_FIRST_TASK_INT); + + String expected = String.format(MESSAGE_SUCCESS, + INDEX_FIRST_TASK_INT + ". LO[W6.5][Submission]"); + String actual = resetFirst.execute().feedbackToUser; + assertEquals(expected, actual); + } + + @Test + public void execute_validIndexLast_success() throws Exception { + ResetTaskCommand resetLast = new ResetTaskCommand(INDEX_LAST_TASK_INT); + + String expected = String.format(MESSAGE_SUCCESS, + INDEX_LAST_TASK_INT + ". LO[W3.10][Compulsory][Submission]"); + String actual = resetLast.execute().feedbackToUser; + assertEquals(expected, actual); + } + + @Test + public void execute_validIndexLastTwice_success() throws Exception { + ResetTaskCommand resetTwice = new ResetTaskCommand(INDEX_LAST_TASK_INT); + + String expected = String.format(MESSAGE_NO_ACTION, + INDEX_LAST_TASK_INT + ". LO[W3.10][Compulsory][Submission]"); + String actual = resetTwice.execute().feedbackToUser; + assertEquals(expected, actual); + } + + // the case of negative/zero/non-integer are tested in the command parser test. + + @Test + public void execute_invalidIndexZero_success() throws Exception { + ResetTaskCommand resetOutOfBound = new ResetTaskCommand(OUT_OF_BOUND_TASK_INDEX_INT); + + String expected = String.format(INDEX_OUT_OF_BOUND); + String actual = resetOutOfBound.execute().feedbackToUser; + assertEquals(expected, actual); + } +} +``` +###### \java\seedu\progresschecker\logic\commands\ViewTaskListCommandTest.java +``` java +/** + * Contains assertion tests for {@code ViewTaskListCommand}. This command is not undoable. + */ +public class ViewTaskListCommandTest { + + @Test + public void execute_commandEquals() throws Exception { + ViewTaskListCommand viewTaskListCommand = new ViewTaskListCommand(ASTERISK_INT); + ViewTaskListCommand viewTaskListCommand2 = new ViewTaskListCommand(FIRST_WEEK_INT); + + // same object -> execution successful + assertTrue(viewTaskListCommand.equals(viewTaskListCommand)); + + // different object -> execution failed + assertFalse(viewTaskListCommand.equals(viewTaskListCommand2)); + } + + @Test + public void execute_validArgUnfilteredList_success() throws Exception { + ViewTaskListCommand viewAll = new ViewTaskListCommand(ASTERISK_INT); + + String expected = String.format(MESSAGE_SUCCESS, DEFAULT_LIST_TITLE); + String actual = viewAll.execute().feedbackToUser; + assertEquals(expected, actual); + } + + @Test + public void execute_validArgFirstWeekFilteredList_success() throws Exception { + ViewTaskListCommand viewFirst = new ViewTaskListCommand(FIRST_WEEK_INT); + + String expected = String.format(MESSAGE_SUCCESS, + DEFAULT_LIST_TITLE + " Week: " + FIRST_WEEK_INT); + String actual = viewFirst.execute().feedbackToUser; + assertEquals(expected, actual); + } + + @Test + public void execute_validArgLastWeekFilteredList_success() throws Exception { + ViewTaskListCommand viewFirst = new ViewTaskListCommand(LAST_WEEK_INT); + + String expected = String.format(MESSAGE_SUCCESS, + DEFAULT_LIST_TITLE + " Week: " + LAST_WEEK_INT); + String actual = viewFirst.execute().feedbackToUser; + assertEquals(expected, actual); + } + + @Test + public void execute_validArgCompulsoryFilteredList_success() throws Exception { + ViewTaskListCommand viewFirst = new ViewTaskListCommand(COM_INT); + + String expected = String.format(MESSAGE_SUCCESS, + DEFAULT_LIST_TITLE + COMPULSORY_STR); + String actual = viewFirst.execute().feedbackToUser; + assertEquals(expected, actual); + } + + @Test + public void execute_validArgSubmissionFilteredList_success() throws Exception { + ViewTaskListCommand viewFirst = new ViewTaskListCommand(SUB_INT); + + String expected = String.format(MESSAGE_SUCCESS, + DEFAULT_LIST_TITLE + SUBMISSION_STR); + String actual = viewFirst.execute().feedbackToUser; + assertEquals(expected, actual); + } + +} +``` +###### \java\seedu\progresschecker\logic\parser\AddCommandParserTest.java +``` java + // multiple usernames - last name accepted + assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + USERNAME_DESC_AMY + + USERNAME_DESC_BOB + MAJOR_DESC_BOB + YEAR_DESC_BOB + TAG_DESC_FRIEND, + new AddCommand(expectedPerson)); + + // multiple majors - last major accepted + assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + USERNAME_DESC_BOB + + MAJOR_DESC_AMY + MAJOR_DESC_BOB + YEAR_DESC_BOB + TAG_DESC_FRIEND, + new AddCommand(expectedPerson)); + + // multiple years - last year accepted + assertParseSuccess(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + USERNAME_DESC_BOB + + MAJOR_DESC_BOB + YEAR_DESC_AMY + YEAR_DESC_BOB + TAG_DESC_FRIEND, + new AddCommand(expectedPerson)); +``` +###### \java\seedu\progresschecker\logic\parser\AddCommandParserTest.java +``` java + // missing username prefix + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + VALID_USERNAME_BOB + + MAJOR_DESC_BOB + YEAR_DESC_BOB, expectedMessage); + + // missing major prefix + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + USERNAME_DESC_BOB + + VALID_MAJOR_BOB + YEAR_DESC_BOB, expectedMessage); + + // missing year prefix + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + USERNAME_DESC_BOB + + MAJOR_DESC_BOB + VALID_YEAR_BOB, expectedMessage); +``` +###### \java\seedu\progresschecker\logic\parser\AddCommandParserTest.java +``` java + // invalid username + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + INVALID_USERNAME_DESC + + MAJOR_DESC_BOB + YEAR_DESC_BOB + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, + GithubUsername.MESSAGE_USERNAME_CONSTRAINTS); + + // invalid major + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + USERNAME_DESC_BOB + + INVALID_MAJOR_DESC + YEAR_DESC_BOB + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, + Major.MESSAGE_MAJOR_CONSTRAINTS); + + // invalid year + assertParseFailure(parser, NAME_DESC_BOB + PHONE_DESC_BOB + EMAIL_DESC_BOB + USERNAME_DESC_BOB + + MAJOR_DESC_BOB + INVALID_YEAR_DESC + TAG_DESC_HUSBAND + TAG_DESC_FRIEND, + Year.MESSAGE_YEAR_CONSTRAINTS); +``` +###### \java\seedu\progresschecker\logic\parser\CompleteTaskCommandParserTest.java +``` java +public class CompleteTaskCommandParserTest { + + private CompleteTaskCommandParser parser = new CompleteTaskCommandParser(); + + @Test + public void parse_validArgsFirstTask_returnsCompleteTaskCommand() { + assertParseSuccess(parser, INDEX_FIRST_TASK, new CompleteTaskCommand(INDEX_FIRST_TASK_INT)); + } + + @Test + public void parse_invalidArgsNegative_throwsParseException() { + assertParseFailure(parser, INVALID_NEGATIVE, String.format(MESSAGE_INVALID_INDEX_OR_FORMAT, + CompleteTaskCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_invalidArgsZero_throwsParseException() { + assertParseFailure(parser, INVALID_ZERO, String.format(MESSAGE_INVALID_INDEX_OR_FORMAT, + CompleteTaskCommand.MESSAGE_USAGE)); + } + @Test + public void parse_invalidArgsCharset_throwsParseException() { + assertParseFailure(parser, INVALID_CHARSET, String.format(MESSAGE_INVALID_INDEX_OR_FORMAT, + CompleteTaskCommand.MESSAGE_USAGE)); + } +} +``` +###### \java\seedu\progresschecker\logic\parser\EditCommandParserTest.java +``` java + assertParseFailure(parser, "1" + INVALID_USERNAME_DESC, + GithubUsername.MESSAGE_USERNAME_CONSTRAINTS); // invalid username + assertParseFailure(parser, "1" + INVALID_MAJOR_DESC, Major.MESSAGE_MAJOR_CONSTRAINTS); // invalid major + assertParseFailure(parser, "1" + INVALID_YEAR_DESC, Year.MESSAGE_YEAR_CONSTRAINTS); // invalid year +``` +###### \java\seedu\progresschecker\logic\parser\EditCommandParserTest.java +``` java + // username + userInput = targetIndex.getOneBased() + USERNAME_DESC_AMY; + descriptor = new EditPersonDescriptorBuilder().withUsername(VALID_USERNAME_AMY).build(); + expectedCommand = new EditCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); + + // major + userInput = targetIndex.getOneBased() + MAJOR_DESC_AMY; + descriptor = new EditPersonDescriptorBuilder().withMajor(VALID_MAJOR_AMY).build(); + expectedCommand = new EditCommand(targetIndex, descriptor); + assertParseSuccess(parser, userInput, expectedCommand); +``` +###### \java\seedu\progresschecker\logic\parser\GoToTaskUrlCommandParserTest.java +``` java +public class GoToTaskUrlCommandParserTest { + + private GoToTaskUrlCommandParser parser = new GoToTaskUrlCommandParser(); + + @Test + public void parse_validArgsFirstTask_returnsGoToTaskUrlCommand() { + assertParseSuccess(parser, INDEX_FIRST_TASK, new GoToTaskUrlCommand(INDEX_FIRST_TASK_INT)); + } + + @Test + public void parse_invalidArgsNegative_throwsParseException() { + assertParseFailure(parser, INVALID_NEGATIVE, String.format(MESSAGE_INVALID_INDEX_OR_FORMAT, + GoToTaskUrlCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_invalidArgsZero_throwsParseException() { + assertParseFailure(parser, INVALID_ZERO, String.format(MESSAGE_INVALID_INDEX_OR_FORMAT, + GoToTaskUrlCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_invalidArgsCharset_throwsParseException() { + assertParseFailure(parser, INVALID_CHARSET, String.format(MESSAGE_INVALID_INDEX_OR_FORMAT, + GoToTaskUrlCommand.MESSAGE_USAGE)); + } +} +``` +###### \java\seedu\progresschecker\logic\parser\ParserUtilTest.java +``` java + @Test + public void parseTaskIndex_invalidInputZero_throwsIllegalValueException() throws Exception { + thrown.expect(IllegalValueException.class); + ParserUtil.parseTaskIndex(INVALID_ZERO); + } + + @Test + public void parseTaskIndex_invalidInputNegative_throwsIllegalValueException() throws Exception { + thrown.expect(IllegalValueException.class); + ParserUtil.parseTaskIndex(INVALID_NEGATIVE); + } + + @Test + public void parseTaskIndex_invalidInputNotInteger_throwsIllegalValueException() throws Exception { + thrown.expect(IllegalValueException.class); + ParserUtil.parseTaskIndex(INVALID_DOUBLE); + } + + @Test + public void parseTaskIndex_invalidInputNotNumber_throwsIllegalValueException() throws Exception { + thrown.expect(IllegalValueException.class); + ParserUtil.parseTaskIndex(INVALID_CHARSET); + } + + @Test + public void parseTaskIndex_invalidInputMultipleArgs_throwsIllegalValueException() throws Exception { + thrown.expect(IllegalValueException.class); + ParserUtil.parseTaskIndex(INVALID_MULTIPLE_ARGS); + } + + @Test + public void parseTaskIndex_validInput_success() throws Exception { + // No whitespaces + assertEquals(INDEX_FIRST_TASK_INT, ParserUtil.parseTaskIndex(INDEX_FIRST_TASK)); + + // Leading and trailing whitespaces + assertEquals(INDEX_FIRST_TASK_INT, ParserUtil.parseTaskIndex(" " + INDEX_FIRST_TASK + " ")); + } + + @Test + public void parseTaskWeek_invalidInputOutOfBound_throwsIllegalValueException() throws Exception { + thrown.expect(IllegalValueException.class); + ParserUtil.parseTaskWeek(OUT_OF_BOUND_WEEK); + } + + @Test + public void parseTaskWeek_invalidInputZero_throwsIllegalValueException() throws Exception { + thrown.expect(IllegalValueException.class); + ParserUtil.parseTaskWeek(INVALID_ZERO); + } + + @Test + public void parseTaskWeek_invalidInputNegative_throwsIllegalValueException() throws Exception { + thrown.expect(IllegalValueException.class); + ParserUtil.parseTaskWeek(INVALID_NEGATIVE); + } + + @Test + public void parseTaskWeek_invalidInputNotInteger_throwsIllegalValueException() throws Exception { + thrown.expect(IllegalValueException.class); + ParserUtil.parseTaskWeek(INVALID_DOUBLE); + } + + @Test + public void parseTaskWeek_invalidInputNotNumber_throwsIllegalValueException() throws Exception { + thrown.expect(IllegalValueException.class); + ParserUtil.parseTaskWeek(INVALID_CHARSET); + } + + @Test + public void parseTaskWeek_invalidInputMultipleArgs_throwsIllegalValueException() throws Exception { + thrown.expect(IllegalValueException.class); + ParserUtil.parseTaskWeek(INVALID_MULTIPLE_ARGS); + } + + @Test + public void parseTaskWeek_validInputFirstWeek_success() throws Exception { + // No whitespaces + assertEquals(FIRST_WEEK_INT, ParserUtil.parseTaskWeek(FIRST_WEEK)); + + // Leading and trailing whitespaces + assertEquals(FIRST_WEEK_INT, ParserUtil.parseTaskWeek(" " + FIRST_WEEK + " ")); + } + + @Test + public void parseTaskWeek_validInputLastWeek_success() throws Exception { + // No whitespaces + assertEquals(LAST_WEEK_INT, ParserUtil.parseTaskWeek(LAST_WEEK)); + + // Leading and trailing whitespaces + assertEquals(LAST_WEEK_INT, ParserUtil.parseTaskWeek(" " + LAST_WEEK + " ")); + } + + @Test + public void parseTaskWeek_validInputAsterisk_success() throws Exception { + // No whitespaces + assertEquals(ASTERISK_INT, ParserUtil.parseTaskWeek(ASTERISK)); + + // Leading and trailing whitespaces + assertEquals(ASTERISK_INT, ParserUtil.parseTaskWeek(" " + ASTERISK + " ")); + } + + @Test + public void parseTaskWeek_validInputCom_success() throws Exception { + // No whitespaces + assertEquals(COM_INT, ParserUtil.parseTaskWeek(COMPULSORY)); + + // Leading and trailing whitespaces + assertEquals(COM_INT, ParserUtil.parseTaskWeek(" " + COMPULSORY + " ")); + + // No whitespaces-alias + assertEquals(COM_INT, ParserUtil.parseTaskWeek(COM)); + + // Leading and trailing whitespaces-alias + assertEquals(COM_INT, ParserUtil.parseTaskWeek(" " + COM + " ")); + } + + @Test + public void parseTaskWeek_validInputSub_success() throws Exception { + // No whitespaces + assertEquals(SUB_INT, ParserUtil.parseTaskWeek(SUBMISSION)); + + // Leading and trailing whitespaces + assertEquals(SUB_INT, ParserUtil.parseTaskWeek(" " + SUBMISSION + " ")); + + // No whitespaces-alias + assertEquals(SUB_INT, ParserUtil.parseTaskWeek(SUB)); + + // Leading and trailing whitespaces-alias + assertEquals(SUB_INT, ParserUtil.parseTaskWeek(" " + SUB + " ")); + } + + @Test + public void parseTaskTitle_invalidInput_throwsIllegalValueException() throws Exception { + thrown.expect(IllegalValueException.class); + ParserUtil.parseTaskTitle(INVALID_TITLE); + } + + @Test + public void parseTaskTitle_validInput_success() throws Exception { + // No whitespaces + assertEquals(DEFAULT_LIST_TITLE, ParserUtil.parseTaskTitle(DEFAULT_LIST_TITLE)); + + // Leading and trailing whitespaces + assertEquals(DEFAULT_LIST_TITLE, ParserUtil.parseTaskTitle(" " + DEFAULT_LIST_TITLE + " ")); + + // Valid length without leading and trailing whitespaces, but exceeds limit after having these spaces + assertEquals(VALID_TITLE_EDGE, ParserUtil.parseTaskTitle(" " + VALID_TITLE_EDGE + " ")); + } +``` +###### \java\seedu\progresschecker\logic\parser\ParserUtilTest.java +``` java + @Test + public void parseUsername_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> ParserUtil.parseUsername((String) null)); + Assert.assertThrows(NullPointerException.class, () -> ParserUtil.parseUsername((Optional<String>) null)); + } + + @Test + public void parseUsername_invalidValue_throwsIllegalValueException() { + Assert.assertThrows(IllegalValueException.class, () -> ParserUtil.parseUsername(INVALID_USERNAME)); + Assert.assertThrows(IllegalValueException.class, () -> ParserUtil.parseUsername( + Optional.of(INVALID_USERNAME))); + } + + @Test + public void parseUsername_optionalEmpty_returnsOptionalEmpty() throws Exception { + assertFalse(ParserUtil.parseUsername(Optional.empty()).isPresent()); + } + + @Test + public void parseUsername_validValueWithoutWhitespace_returnsUsername() throws Exception { + GithubUsername expectedUsername = new GithubUsername(VALID_USERNAME); + assertEquals(expectedUsername, ParserUtil.parseUsername(VALID_USERNAME)); + assertEquals(Optional.of(expectedUsername), ParserUtil.parseUsername(Optional.of(VALID_USERNAME))); + } + + @Test + public void parseUsername_validValueWithWhitespace_returnsTrimmedUsername() throws Exception { + String usernameWithWhitespace = WHITESPACE + VALID_USERNAME + WHITESPACE; + GithubUsername expectedUsername = new GithubUsername(VALID_USERNAME); + assertEquals(expectedUsername, ParserUtil.parseUsername(usernameWithWhitespace)); + assertEquals(Optional.of(expectedUsername), ParserUtil.parseUsername(Optional.of(usernameWithWhitespace))); + } + + + @Test + public void parseMajor_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> ParserUtil.parseMajor((String) null)); + Assert.assertThrows(NullPointerException.class, () -> ParserUtil.parseMajor((Optional<String>) null)); + } + + @Test + public void parseMajor_invalidValue_throwsIllegalValueException() { + Assert.assertThrows(IllegalValueException.class, () -> ParserUtil.parseMajor(INVALID_MAJOR)); + Assert.assertThrows(IllegalValueException.class, () -> ParserUtil.parseMajor(Optional.of(INVALID_MAJOR))); + } + + @Test + public void parseMajor_optionalEmpty_returnsOptionalEmpty() throws Exception { + assertFalse(ParserUtil.parseMajor(Optional.empty()).isPresent()); + } + + @Test + public void parseMajor_validValueWithoutWhitespace_returnsMajor() throws Exception { + Major expectedMajor = new Major(VALID_MAJOR); + assertEquals(expectedMajor, ParserUtil.parseMajor(VALID_MAJOR)); + assertEquals(Optional.of(expectedMajor), ParserUtil.parseMajor(Optional.of(VALID_MAJOR))); + } + + @Test + public void parseMajor_validValueWithWhitespace_returnsTrimmedMajor() throws Exception { + String majorWithWhitespace = WHITESPACE + VALID_MAJOR + WHITESPACE; + Major expectedMajor = new Major(VALID_MAJOR); + assertEquals(expectedMajor, ParserUtil.parseMajor(majorWithWhitespace)); + assertEquals(Optional.of(expectedMajor), ParserUtil.parseMajor(Optional.of(majorWithWhitespace))); + } +``` +###### \java\seedu\progresschecker\logic\parser\ProgressCheckerParserTest.java +``` java + @Test + public void parseCommand_addDefaultTasks() throws Exception { + assertTrue(parser.parseCommand(AddDefaultTasksCommand.COMMAND_WORD) instanceof AddDefaultTasksCommand); + assertTrue(parser.parseCommand(AddDefaultTasksCommand.COMMAND_WORD + + " 3") instanceof AddDefaultTasksCommand); + assertTrue(parser.parseCommand(AddDefaultTasksCommand.COMMAND_ALIAS) instanceof AddDefaultTasksCommand); + assertTrue(parser.parseCommand(AddDefaultTasksCommand.COMMAND_ALIAS + + " 3") instanceof AddDefaultTasksCommand); + } + + @Test + public void parseCommand_completeTask() throws Exception { + CompleteTaskCommand command = (CompleteTaskCommand) parser.parseCommand( + CompleteTaskCommand.COMMAND_WORD + " " + INDEX_FIRST_TASK); + assertEquals(new CompleteTaskCommand(INDEX_FIRST_TASK_INT), command); + CompleteTaskCommand command2 = (CompleteTaskCommand) parser.parseCommand( + CompleteTaskCommand.COMMAND_ALIAS + " " + INDEX_FIRST_TASK); + assertEquals(new CompleteTaskCommand(INDEX_FIRST_TASK_INT), command2); + } + + @Test + public void parseCommand_resetTask() throws Exception { + ResetTaskCommand command = (ResetTaskCommand) parser.parseCommand( + ResetTaskCommand.COMMAND_WORD + " " + INDEX_FIRST_TASK); + assertEquals(new ResetTaskCommand(INDEX_FIRST_TASK_INT), command); + ResetTaskCommand command2 = (ResetTaskCommand) parser.parseCommand( + ResetTaskCommand.COMMAND_ALIAS + " " + INDEX_FIRST_TASK); + assertEquals(new ResetTaskCommand(INDEX_FIRST_TASK_INT), command2); + } + + @Test + public void parseCommand_goToTaskUrl() throws Exception { + GoToTaskUrlCommand command = (GoToTaskUrlCommand) parser.parseCommand( + GoToTaskUrlCommand.COMMAND_WORD + " " + INDEX_FIRST_TASK); + assertEquals(new GoToTaskUrlCommand(INDEX_FIRST_TASK_INT), command); + GoToTaskUrlCommand command2 = (GoToTaskUrlCommand) parser.parseCommand( + GoToTaskUrlCommand.COMMAND_ALIAS + " " + INDEX_FIRST_TASK); + assertEquals(new GoToTaskUrlCommand(INDEX_FIRST_TASK_INT), command2); + } + + @Test + public void parseCommand_viewTaskList() throws Exception { + ViewTaskListCommand command = (ViewTaskListCommand) parser.parseCommand( + ViewTaskListCommand.COMMAND_WORD + " " + FIRST_WEEK); + assertEquals(new ViewTaskListCommand(FIRST_WEEK_INT), command); + ViewTaskListCommand command2 = (ViewTaskListCommand) parser.parseCommand( + ViewTaskListCommand.COMMAND_ALIAS + " " + COMPULSORY); + assertEquals(new ViewTaskListCommand(COM_INT), command2); + } +``` +###### \java\seedu\progresschecker\logic\parser\ResetTaskCommandParserTest.java +``` java +public class ResetTaskCommandParserTest { + + private ResetTaskCommandParser parser = new ResetTaskCommandParser(); + + @Test + public void parse_validArgsFirstTask_returnsResetTaskCommand() { + assertParseSuccess(parser, INDEX_FIRST_TASK, new ResetTaskCommand(INDEX_FIRST_TASK_INT)); + } + + @Test + public void parse_invalidArgsNegative_throwsParseException() { + assertParseFailure(parser, INVALID_NEGATIVE, String.format(MESSAGE_INVALID_INDEX_OR_FORMAT, + ResetTaskCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_invalidArgsZero_throwsParseException() { + assertParseFailure(parser, INVALID_ZERO, String.format(MESSAGE_INVALID_INDEX_OR_FORMAT, + ResetTaskCommand.MESSAGE_USAGE)); + } + @Test + public void parse_invalidArgsCharset_throwsParseException() { + assertParseFailure(parser, INVALID_CHARSET, String.format(MESSAGE_INVALID_INDEX_OR_FORMAT, + ResetTaskCommand.MESSAGE_USAGE)); + } +} +``` +###### \java\seedu\progresschecker\logic\parser\ViewTaskListCommandParserTest.java +``` java +public class ViewTaskListCommandParserTest { + + private ViewTaskListCommandParser parser = new ViewTaskListCommandParser(); + + @Test + public void parse_validArgsFirstWeek_returnsViewTaskListCommand() { + assertParseSuccess(parser, FIRST_WEEK, new ViewTaskListCommand(FIRST_WEEK_INT)); + } + + @Test + public void parse_validArgsCompulsory_returnsViewTaskListCommand() { + assertParseSuccess(parser, COMPULSORY, new ViewTaskListCommand(COM_INT)); + } + + @Test + public void parse_validArgsSubmission_returnsViewTaskListCommand() { + assertParseSuccess(parser, SUBMISSION, new ViewTaskListCommand(SUB_INT)); + } + + @Test + public void parse_invalidArgsNegative_throwsParseException() { + assertParseFailure(parser, INVALID_NEGATIVE, String.format(MESSAGE_INVALID_TASK_FILTER, + ViewTaskListCommand.MESSAGE_USAGE)); + } + + @Test + public void parse_invalidArgsZero_throwsParseException() { + assertParseFailure(parser, INVALID_ZERO, String.format(MESSAGE_INVALID_TASK_FILTER, + ViewTaskListCommand.MESSAGE_USAGE)); + } + @Test + public void parse_invalidArgsCharset_throwsParseException() { + assertParseFailure(parser, INVALID_CHARSET, String.format(MESSAGE_INVALID_TASK_FILTER, + ViewTaskListCommand.MESSAGE_USAGE)); + } +} +``` +###### \java\seedu\progresschecker\model\person\GithubUsernameTest.java +``` java +public class GithubUsernameTest { + + @Test + public void constructor_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> new GithubUsername(null)); + } + + @Test + public void constructor_invalidUsername_throwsIllegalArgumentException() { + String invalidUsername = ""; + Assert.assertThrows(IllegalArgumentException.class, () -> new GithubUsername(invalidUsername)); + } + + @Test + public void isValidUsername() { + // null username + Assert.assertThrows(NullPointerException.class, () -> GithubUsername.isValidUsername(null)); + + // invalid username + assertFalse(GithubUsername.isValidUsername("")); // empty string + assertFalse(GithubUsername.isValidUsername(" ")); // spaces only + assertFalse(GithubUsername.isValidUsername("^")); // only non-alphanumeric characters + assertFalse(GithubUsername.isValidUsername("peter*")); // contains non-alphanumeric characters + + // valid username + assertTrue(GithubUsername.isValidUsername("peter jack")); // alphabets only + assertTrue(GithubUsername.isValidUsername("12345")); // numbers only + assertTrue(GithubUsername.isValidUsername("peter the 2nd")); // alphanumeric characters + assertTrue(GithubUsername.isValidUsername("Capital Tan")); // with capital letters + assertTrue(GithubUsername.isValidUsername("David Roger Jackson Ray Jr 2nd")); // long usernames + } +} +``` +###### \java\seedu\progresschecker\model\person\MajorTest.java +``` java +public class MajorTest { + + @Test + public void constructor_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> new Major(null)); + } + + @Test + public void constructor_invalidMajor_throwsIllegalArgumentException() { + String invalidMajor = ""; + Assert.assertThrows(IllegalArgumentException.class, () -> new Major(invalidMajor)); + } + + @Test + public void isValidMajor() { + // null major + Assert.assertThrows(NullPointerException.class, () -> Major.isValidMajor(null)); + + // invalid majors + assertFalse(Major.isValidMajor("")); // empty string + assertFalse(Major.isValidMajor(" ")); // spaces only + + // valid majors + assertTrue(Major.isValidMajor("Blk 456, Den Road, #01-355")); + assertTrue(Major.isValidMajor("-")); // one character + assertTrue(Major.isValidMajor("Leng Inc; 1234 Market St; San Francisco CA 2349879; USA")); // long major + } +} +``` +###### \java\seedu\progresschecker\testutil\TypicalTaskArgs.java +``` java +/** + * A utility class containing a list of arguments to be used in tests for tasks commands. + */ +public class TypicalTaskArgs { + // User input + + // week number arguments + public static final String FIRST_WEEK = "1"; + public static final int FIRST_WEEK_INT = 1; + public static final String RANDOM_WEEK = "5"; + public static final String LAST_WEEK = "13"; + public static final int LAST_WEEK_INT = 13; + + public static final String OUT_OF_BOUND_WEEK = "14"; + + // task index number arguments + public static final String INDEX_FIRST_TASK = "1"; + public static final int INDEX_FIRST_TASK_INT = 1; + public static final int INDEX_LAST_TASK_INT = 4; //specifically for the model being tested + public static final int OUT_OF_BOUND_TASK_INDEX_INT = 500; + + // valid char arguments + public static final String COMPULSORY = "compulsory"; + public static final String COM = "com"; + public static final int COM_INT = -13; + public static final String SUBMISSION = "submission"; + public static final String SUB = "sub"; + public static final int SUB_INT = -20; + public static final String ASTERISK = "*"; + public static final int ASTERISK_INT = 0; + public static final String DEFAULT_LIST_TITLE = "CS2103 LOs"; + public static final String VALID_TITLE_EDGE = "1234567891234567891234567891234567891234567891234"; + + // general invalid input arguments + public static final String INVALID_ZERO = "0"; + public static final String INVALID_NEGATIVE = "-3"; + public static final String INVALID_DOUBLE = "3.4"; + public static final String INVALID_CHARSET = "comppp"; + public static final String INVALID_TITLE = "ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss" + + "ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss"; // exceeds length limit + public static final String INVALID_MULTIPLE_ARGS = "compulsory 4 2"; + + //----------------------------------------------------------------------------------------------------- + + // Parser output (command input) + +} +``` +###### \java\seedu\progresschecker\ui\Browser2PanelTest.java +``` java +public class Browser2PanelTest extends GuiUnitTest { + private static final String webpage = "<!DOCTYPE html>\n" + + "<html>\n" + + "<head>\n" + + " <!-- <link rel=\"stylesheet\" href=\"DarkTheme.css\"> -->\n" + + "</head>\n" + + "\n" + + "<body class=\"background\">\n" + + "</body>\n" + + "</html>"; + + private Browser2Panel browser2Panel; + private Browser2PanelHandle browser2PanelHandle; + + private LoadBarEvent loadBarEventStub; + + @Before + public void setUp() { + loadBarEventStub = new LoadBarEvent(webpage); + + guiRobot.interact(() -> browser2Panel = new Browser2Panel()); + uiPartRule.setUiPart(browser2Panel); + + browser2PanelHandle = new Browser2PanelHandle(browser2Panel.getRoot()); + } + + @Test + public void display() throws Exception { + // default web page + URL expectedDefaultPageUrl = MainApp.class.getResource(FXML_FILE_FOLDER + DEFAULT_PAGE); + assertEquals(expectedDefaultPageUrl, browser2PanelHandle.getLoadedUrl()); + + postNow(loadBarEventStub); + String expectedTitle = null; + + waitUntilBrowser2Loaded(browser2PanelHandle); + assertEquals(expectedTitle, browser2PanelHandle.getLoadedTitle()); + } +} +``` +###### \java\seedu\progresschecker\ui\BrowserPanelTest.java +``` java + private static final String webpage = "<!DOCTYPE html>\n" + + "<html>\n" + + "<head>\n" + + " <!-- <link rel=\"stylesheet\" href=\"DarkTheme.css\"> -->\n" + + "</head>\n" + + "\n" + + "<body class=\"background\">\n" + + "</body>\n" + + "</html>"; + + private PersonPanelSelectionChangedEvent selectionChangedEventStub; + private LoadUrlEvent loadUrlEventStub; + private LoadTaskEvent loadTaskEventStub; + + private BrowserPanel browserPanel; + private BrowserPanelHandle browserPanelHandle; + + @Before + public void setUp() { + selectionChangedEventStub = new PersonPanelSelectionChangedEvent(new PersonCard(ALICE, 0)); + + loadTaskEventStub = new LoadTaskEvent(webpage); + String expectedUrl = MainApp.class.getResource(FXML_FILE_FOLDER + DEFAULT_PAGE).toString(); + + loadUrlEventStub = new LoadUrlEvent(expectedUrl); + + guiRobot.interact(() -> browserPanel = new BrowserPanel()); + uiPartRule.setUiPart(browserPanel); + + browserPanelHandle = new BrowserPanelHandle(browserPanel.getRoot()); + } + + @Test + public void display() throws Exception { + // default web page + URL expectedDefaultPageUrl = MainApp.class.getResource(FXML_FILE_FOLDER + DEFAULT_PAGE); + assertEquals(expectedDefaultPageUrl, browserPanelHandle.getLoadedUrl()); + + // associated web page of a person + postNow(loadTaskEventStub); + String expectedTitle = null; + + waitUntilBrowserLoaded(browserPanelHandle); + assertEquals(expectedTitle, browserPanelHandle.getLoadedTitle()); + + postNow(loadUrlEventStub); + URL expectedUrl = MainApp.class.getResource(FXML_FILE_FOLDER + DEFAULT_PAGE);; + } +} +``` +###### \java\systemtests\AddCommandSystemTest.java +``` java + /* Case: add a person with all fields same as another person in the ProgressChecker except major -> added */ + toAdd = new PersonBuilder().withName(VALID_NAME_AMY).withPhone(VALID_PHONE_AMY).withEmail(VALID_EMAIL_AMY) + .withUsername(VALID_USERNAME_AMY).withMajor(VALID_MAJOR_BOB).withYear(VALID_YEAR_AMY) + .withTags(VALID_TAG_FRIEND).build(); + command = AddCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + USERNAME_DESC_AMY + + MAJOR_DESC_BOB + YEAR_DESC_AMY + TAG_DESC_FRIEND; + assertCommandSuccess(command, toAdd); +``` +###### \java\systemtests\AddCommandSystemTest.java +``` java + /* Case: missing username -> rejected */ + command = AddCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + + MAJOR_DESC_AMY + YEAR_DESC_AMY; + assertCommandFailure(command, String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); + + /* Case: missing major -> rejected */ + command = AddCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + USERNAME_DESC_AMY + + YEAR_DESC_AMY; + assertCommandFailure(command, String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); +``` +###### \java\systemtests\AddCommandSystemTest.java +``` java + /* Case: invalid username -> rejected */ + command = AddCommand.COMMAND_WORD + NAME_DESC_AMY + PHONE_DESC_AMY + EMAIL_DESC_AMY + INVALID_USERNAME_DESC + + MAJOR_DESC_AMY + YEAR_DESC_AMY; + assertCommandFailure(command, GithubUsername.MESSAGE_USERNAME_CONSTRAINTS); +``` +###### \java\systemtests\EditCommandSystemTest.java +``` java + /* Case: invalid username -> rejected */ + assertCommandFailure(EditCommand.COMMAND_WORD + " " + INDEX_FIRST_PERSON.getOneBased() + + INVALID_USERNAME_DESC, GithubUsername.MESSAGE_USERNAME_CONSTRAINTS); +``` diff --git a/collated/test/Livian1107.md b/collated/test/Livian1107.md new file mode 100644 index 000000000000..ecfdf89dc24b --- /dev/null +++ b/collated/test/Livian1107.md @@ -0,0 +1,236 @@ +# Livian1107 +###### \java\guitests\guihandles\ProfilePanelHandle.java +``` java +/** + * Provides a handle to profile panel. + */ +public class ProfilePanelHandle extends NodeHandle<Node> { + + private static final String NAME_FIELD_ID = "#name"; + private static final String MAJOR_FIELD_ID = "#major"; + private static final String YEAR_FIELD_ID = "#year"; + private static final String PHONE_FIELD_ID = "#phone"; + private static final String USERNAME_FIELD_ID = "#username"; + private static final String EMAIL_FIELD_ID = "#email"; + private static final String TAGS_FIELD_ID = "#tags"; + + private final Label nameLabel; + private final Label majorLabel; + private final Label yearLabel; + private final Label phoneLabel; + private final Label usernameLabel; + private final Label emailLabel; + private final List<Label> tagLabels; + + private String lastRememberedName; + + public ProfilePanelHandle(Node cardNode) { + super(cardNode); + + this.nameLabel = getChildNode(NAME_FIELD_ID); + this.majorLabel = getChildNode(MAJOR_FIELD_ID); + this.yearLabel = getChildNode(YEAR_FIELD_ID); + this.phoneLabel = getChildNode(PHONE_FIELD_ID); + this.usernameLabel = getChildNode(USERNAME_FIELD_ID); + this.emailLabel = getChildNode(EMAIL_FIELD_ID); + + Region tagsContainer = getChildNode(TAGS_FIELD_ID); + this.tagLabels = tagsContainer + .getChildrenUnmodifiable() + .stream() + .map(Label.class::cast) + .collect(Collectors.toList()); + } + + public String getUsername() { + return usernameLabel.getText(); + } + + public String getName() { + return nameLabel.getText(); + } + + public String getAddress() { + return majorLabel.getText(); + } + + public String getYear() { + return yearLabel.getText(); + } + + public String getPhone() { + return phoneLabel.getText(); + } + + public String getEmail() { + return emailLabel.getText(); + } + + public List<String> getTags() { + return tagLabels + .stream() + .map(Label::getText) + .collect(Collectors.toList()); + } + + public List<Label> getTagLabels() { + return tagLabels; + } +} +``` +###### \java\seedu\progresschecker\logic\commands\ThemeCommandTest.java +``` java +/** + * Contains assertion tests for {@code ThemeCommand}. + */ +public class ThemeCommandTest { + @Test + public void equals() { + ThemeCommand dayTheme = new ThemeCommand(DAY_THEME); + ThemeCommand nightTheme = new ThemeCommand(NIGHT_THEME); + + // same object -> returns true + assertTrue(dayTheme.equals(dayTheme)); + + // same values -> returns true + ThemeCommand dayThemeCopy = new ThemeCommand(DAY_THEME); + assertTrue(dayTheme.equals(dayThemeCopy)); + + // different types -> returns false + assertFalse(dayTheme.equals(1)); + + // null -> returns false + assertFalse(dayTheme.equals(null)); + + // different type -> returns false + assertFalse(dayTheme.equals(nightTheme)); + } +} +``` +###### \java\seedu\progresschecker\logic\commands\UploadCommandTest.java +``` java +public class UploadCommandTest { + @Test + public void isValidLocalPath() { + + // valid photo path + assertTrue(UploadCommand.isValidLocalPath("C:\\Users\\Livian\\desktop\\1.png")); + + // empty path + assertFalse(UploadCommand.isValidLocalPath("")); // empty string + assertFalse(UploadCommand.isValidLocalPath(" ")); // spaces only + + // invalid extension + assertFalse(UploadCommand.isValidLocalPath("C:\\photo.gif")); + assertFalse(UploadCommand.isValidLocalPath("D:\\photo.bmp")); + + // invalid path format + assertFalse(UploadCommand.isValidLocalPath("C:\\\\1.jpg")); // too many backslashes + assertFalse(UploadCommand.isValidLocalPath("C:\\")); // no file name + } +} +``` +###### \java\seedu\progresschecker\model\photo\PhotoPathTest.java +``` java +public class PhotoPathTest { + + @Test + public void constructor_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> new PhotoPath(null)); + } + + @Test + public void isValidPhotoPath() { + // null photo path + Assert.assertThrows(NullPointerException.class, () -> PhotoPath.isValidPhotoPath(null)); + + // blank photo path + assertFalse(PhotoPath.isValidPhotoPath(" ")); // spaces only + + // invalid starting + assertFalse(PhotoPath.isValidPhotoPath("src/image.jpg")); // missing parent path + assertFalse(PhotoPath.isValidPhotoPath("src/main/image.jpg")); // missing parent path + assertFalse(PhotoPath.isValidPhotoPath("src/main/resources/image.jpg")); // missing parent path + assertFalse(PhotoPath.isValidPhotoPath("src/main/resources/images/image.jpg")); // missing parent path + + // invalid file extension + assertFalse(PhotoPath.isValidPhotoPath("src/main/resources/images/contact/image.psd")); + assertFalse(PhotoPath.isValidPhotoPath("src/main/resources/images/contact/image.gif")); + + // valid photo path + assertTrue(PhotoPath.isValidPhotoPath("src/main/resources/images/contact/image.jpg")); + assertTrue(PhotoPath.isValidPhotoPath("src/main/resources/images/contact/image.jpeg")); + assertTrue(PhotoPath.isValidPhotoPath("src/main/resources/images/contact/image.png")); + assertTrue(PhotoPath.isValidPhotoPath("")); // empty path + } +} +``` +###### \java\seedu\progresschecker\testutil\TypicalThemes.java +``` java +/** + * A utility class containing a list of {@code String} objects to be used in tests. + */ +public class TypicalThemes { + public static final String DAY_THEME = "day"; + public static final String NIGHT_THEME = "night"; +} +``` +###### \java\seedu\progresschecker\ui\ProfilePanelTest.java +``` java +public class ProfilePanelTest extends GuiUnitTest { + + @Test + public void display() { + // no tags + Person personWithNoTags = new PersonBuilder().withTags(new String[0]).build(); + ProfilePanel profilePanel = new ProfilePanel(); + profilePanel.loadPerson(personWithNoTags); + uiPartRule.setUiPart(profilePanel); + assertProfileDisplay(profilePanel, personWithNoTags); + + // with tags + Person personWithTags = new PersonBuilder().build(); + profilePanel = new ProfilePanel(); + profilePanel.loadPerson(personWithTags); + uiPartRule.setUiPart(profilePanel); + assertProfileDisplay(profilePanel, personWithTags); + } + + @Test + public void equals() { + Person person = new PersonBuilder().build(); + ProfilePanel profilePanel = new ProfilePanel(); + profilePanel.loadPerson(person); + + // same object -> returns true + assertTrue(profilePanel.equals(profilePanel)); + + // null -> returns false + assertFalse(profilePanel.equals(null)); + + // different types -> returns false + assertFalse(profilePanel.equals(0)); + } + + /** + * Asserts that {@code personProfile} displays the details of {@code expectedPerson} correctly. + */ + private void assertProfileDisplay(ProfilePanel personProfile, Person expectedPerson) { + guiRobot.pauseForHuman(); + + ProfilePanelHandle profilePanelHandle = new ProfilePanelHandle(personProfile.getRoot()); + + // verify person details are displayed correctly + assertProfileDisplaysPerson(expectedPerson, profilePanelHandle); + } +} +``` +###### \java\seedu\progresschecker\ui\testutil\GuiTestAssert.java +``` java + /** + * Asserts that {@code actualProfile} displays the details of {@code expectedPerson}. + */ + public static void assertProfileDisplaysPerson(Person expectedPerson, ProfilePanelHandle actualProfile) { + assertEquals(expectedPerson.getName().fullName, actualProfile.getName()); + } +``` diff --git a/collated/test/adityaa1998.md b/collated/test/adityaa1998.md new file mode 100644 index 000000000000..c92873112edf --- /dev/null +++ b/collated/test/adityaa1998.md @@ -0,0 +1,940 @@ +# adityaa1998 +###### \java\seedu\progresschecker\logic\commands\CreateIssueCommandIntegrationTest.java +``` java +public class CreateIssueCommandIntegrationTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private Model model; + + @Before + public void setUp() throws Exception { + model = new ModelManager(getTypicalProgressChecker(), new UserPrefs()); + GitDetails validDetails = new GitDetailsBuilder().build(); + model.loginGithub(validDetails); + } + + @Test + public void execute_newIssue_success() throws Exception { + + Issue validIssue = new IssueBuilder().build(); + CommandResult commandResult = prepareCommand(validIssue, model).execute(); + + /** + * The model cannot be tested because if the model is tested, + * There is just one model instead of two : an expected model and a model + * The reason for the same is because if createIssue command is executed twice, there will be 2 issues online + * Thus, the success message is comapred with the feedback to the user + * success message is only posted after an issue is created on git + */ + assertEquals (CreateIssueCommand.MESSAGE_SUCCESS, commandResult.feedbackToUser); + } + + @Test + public void execute_authenticationError_throwsCommandException() throws Exception { + model.logoutGithub(); + + Issue validIssue = new IssueBuilder().build(); + + thrown.expect(CommandException.class); + thrown.expectMessage(CreateIssueCommand.MESSAGE_FAILURE); + + prepareCommand(validIssue, model).execute(); + + } + + /** + * Generates a new {@code CreateIssueCommadn} which upon execution, adds {@code issue} into the {@code model}. + */ + private CreateIssueCommand prepareCommand(Issue issue, Model model) { + CreateIssueCommand command = new CreateIssueCommand(issue); + command.setData(model, new CommandHistory(), new UndoRedoStack()); + return command; + } + +} +``` +###### \java\seedu\progresschecker\logic\commands\CreateIssueCommandTest.java +``` java +public class CreateIssueCommandTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void constructor_nullIssue_throwsNullPointerException() { + thrown.expect(NullPointerException.class); + new CreateIssueCommand(null); + } + + @Test + public void equals() { + Issue one = new IssueBuilder().withTitle("one").build(); + Issue two = new IssueBuilder().withTitle("two").build(); + CreateIssueCommand createOneIssue = new CreateIssueCommand(one); + CreateIssueCommand createTwoIssue = new CreateIssueCommand(two); + + // same object -> returns true + assertTrue(createOneIssue.equals(createOneIssue)); + + // same values -> returns true + CreateIssueCommand createOneIssueCopy = new CreateIssueCommand(one); + assertTrue(createOneIssue.equals(createOneIssue)); + + // different types -> returns false + assertFalse(createOneIssue.equals(1)); + + // null -> returns false + assertFalse(createOneIssue.equals(null)); + + // different person -> returns false + assertFalse(createOneIssue.equals(createTwoIssue)); + } + + @Test + public void execute_authenticationError_throwsCommandException() throws Exception { + ModelStub modelStub = new CreateIssueCommandTest.ModelStubCommandExceptionException(); + Issue validIssue = new IssueBuilder().build(); + + thrown.expect(CommandException.class); + thrown.expectMessage(CreateIssueCommand.MESSAGE_FAILURE); + + getCreateIssueCommandForIssue(validIssue, modelStub).execute(); + } + + /** + * Generates a new CreateIssueCommand with the details of the given issue. + */ + private CreateIssueCommand getCreateIssueCommandForIssue(Issue issue, ModelStub model) { + CreateIssueCommand command = new CreateIssueCommand(issue); + command.setData(model, new CommandHistory(), new UndoRedoStack()); + return command; + } + + @Test + public void execute_issueAcceptedByModel_addSuccessful() throws Exception { + ModelStubAcceptingIssueAdded modelStub = new ModelStubAcceptingIssueAdded(); + Issue validIssue = new IssueBuilder().build(); + + CommandResult commandResult = getCreateIssueCommandForIssue(validIssue, modelStub).execute(); + + assertEquals(CreateIssueCommand.MESSAGE_SUCCESS, commandResult.feedbackToUser); + assertEquals(Arrays.asList(validIssue), modelStub.issueAdded); + } + + /** + * A default model stub that have all of the methods failing. + */ + private class ModelStub implements Model { + + @Override + public void loginGithub(GitDetails gitdetails) throws IOException, CommandException { + fail("This method should not be called."); + } + + @Override + public void logoutGithub() throws CommandException { + fail("This method should not be called."); + } + + @Override + public void createIssueOnGitHub(Issue issue) throws IOException, CommandException { + fail("This method should not be called. "); + } + + @Override + public void closeIssueOnGithub(Index index) throws IOException { + fail("This method should not be called"); + } + + @Override + public void listIssues(String state) throws IOException, CommandException, IllegalValueException { + fail("This method should not be called"); + } + + @Override + public void addPerson(Person person) throws DuplicatePersonException { + fail("This method should not be called."); + } + + @Override + public void reopenIssueOnGithub(Index index) throws IOException, CommandException { + fail("This method should not be called"); + } + + @Override + public void sort() { + fail("This method should not be called."); + } + + @Override + public void resetData(ReadOnlyProgressChecker newData) { + fail("This method should not be called."); + } + + @Override + public ReadOnlyProgressChecker getProgressChecker() { + fail("This method should not be called."); + return null; + } + + @Override + public void deletePerson(Person target) throws PersonNotFoundException { + fail("This method should not be called."); + } + + @Override + public void updatePerson(Person target, Person editedPerson) + throws DuplicatePersonException { + fail("This method should not be called."); + } + + @Override + public ObservableList<Person> getFilteredPersonList() { + fail("This method should not be called."); + return null; + } + + @Override + public void updateFilteredPersonList(Predicate<Person> predicate) { + fail("This method should not be called."); + } + + @Override + public void uploadPhoto(Person target, String path) + throws DuplicatePersonException, PersonNotFoundException { + fail("This method should not be called."); + } + + @Override + public void updateIssue(Index index, Issue editedIssue) throws IOException { + fail("This method should not be called"); + } + + @Override + public void addPhoto(PhotoPath photoPath) throws DuplicatePhotoException { + fail("This method should not be called."); + } + + @Override + public void updateExercise(Exercise target, Exercise editedExercise) { + fail("This method should not be called."); + } + + @Override + public ObservableList<Exercise> getFilteredExerciseList() { + fail("This method should not be called."); + return null; + } + + @Override + public void updateFilteredExerciseList(Predicate<Exercise> predicate) { + fail("This method should not be called."); + } + + @Override + public ObservableList<Issue> getFilteredIssueList() { + fail("This method should not be called."); + return null; + } + + @Override + public void updateFilteredIssueList(Predicate<Issue> predicate) { + fail("This method should not be called."); + } + } + + /** + * A Model stub that always throw a CommandException when trying to create a new issue. + */ + private class ModelStubCommandExceptionException extends ModelStub { + @Override + public void createIssueOnGitHub(Issue issue) throws IOException, CommandException { + throw new CommandException(""); + } + + } + + /** + * A Model stub that always accept the issue being added. + */ + private class ModelStubAcceptingIssueAdded extends ModelStub { + final ArrayList<Issue> issueAdded = new ArrayList<>(); + + @Override + public void createIssueOnGitHub(Issue issue) throws IOException, CommandException { + requireNonNull(issue); + issueAdded.add(issue); + } + + @Override + public ReadOnlyProgressChecker getProgressChecker() { + return new ProgressChecker(); + } + } + +} +``` +###### \java\seedu\progresschecker\logic\parser\ReopenIssueCommandParserTest.java +``` java +public class ReopenIssueCommandParserTest { + + private ReopenIssueCommandParser parser = new ReopenIssueCommandParser(); + + @Test + public void parse_validArgs_returnsReopenIssueCommand() { + assertParseSuccess(parser, "1", new ReopenIssueCommand(INDEX_ISSUE)); + } + + @Test + public void parse_invalidArgs_throwsParseException() { + assertParseFailure(parser, "a", String.format(MESSAGE_INVALID_COMMAND_FORMAT, + ReopenIssueCommand.MESSAGE_USAGE)); + } +} +``` +###### \java\seedu\progresschecker\model\credentials\PasscodeTest.java +``` java +public class PasscodeTest { + + @Test + public void constructor_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> new Passcode(null)); + } + + @Test + public void constructor_invalidPasscode_throwsIllegalArgumentException() { + String invalidPasscode = ""; + Assert.assertThrows(IllegalArgumentException.class, () -> new Passcode(invalidPasscode)); + } + + @Test + public void isValidPasscode() { + // null passcode + Assert.assertThrows(NullPointerException.class, () -> Passcode.isValidPasscode(null)); + + // invalid passcode + assertFalse(Passcode.isValidPasscode("")); // empty string + assertFalse(Passcode.isValidPasscode(" ")); // spaces only + assertFalse(Passcode.isValidPasscode("^")); // only non-alphanumeric characters + assertFalse(Passcode.isValidPasscode("ads12")); // only lowercase and numbers with less than 7 characters + assertFalse(Passcode.isValidPasscode("cajacxvccxk")); // alphabets only + assertFalse(Passcode.isValidPasscode("12345")); // numbers only + assertFalse(Passcode.isValidPasscode("ADDD1232")); // capital letter and numbers only + assertFalse(Passcode.isValidPasscode("git*")); // contains characters with less than 7 characters + + + // valid passcode + assertTrue(Passcode.isValidPasscode("adityaathe2nd")); // alphanumeric characters with numerals + assertTrue(Passcode.isValidPasscode("giTHub/repo-4")); // with capital letters + assertTrue(Passcode.isValidPasscode("github passcode1")); // with letters and numerals + + } +} +``` +###### \java\seedu\progresschecker\model\credentials\RepositoryTest.java +``` java +public class RepositoryTest { + + @Test + public void constructor_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> new Repository(null)); + } + + @Test + public void constructor_invalidRepository_throwsIllegalArgumentException() { + String invalidRepo = ""; + Assert.assertThrows(IllegalArgumentException.class, () -> new Repository(invalidRepo)); + } + + @Test + public void isValidRepository() { + // null repo + Assert.assertThrows(NullPointerException.class, () -> Repository.isValidRepository(null)); + + // invalid repo + assertFalse(Repository.isValidRepository("")); // empty string + assertFalse(Repository.isValidRepository(" ")); // spaces only + assertFalse(Repository.isValidRepository("^")); // only non-alphanumeric characters + assertFalse(Repository.isValidRepository("ca jacxvccxk")); // alphabets only with spaces + assertFalse(Repository.isValidRepository("adityaa the 2nd")); // alphanumeric characters with spaces + + + // valid repo + assertTrue(Repository.isValidRepository("12345")); // numbers only + assertTrue(Repository.isValidRepository("github/repo-4")); // with capital letters + assertTrue(Repository.isValidRepository("git*")); // contains non-alphanumeric characters + + } +} +``` +###### \java\seedu\progresschecker\model\credentials\UsernameTest.java +``` java +public class UsernameTest { + @Test + public void constructor_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> new Username(null)); + } + + @Test + public void constructor_invalidUsername_throwsIllegalArgumentException() { + String invalidUsername = ""; + Assert.assertThrows(IllegalArgumentException.class, () -> new Username(invalidUsername)); + } + + @Test + public void isValidUsername() { + // null repo + Assert.assertThrows(NullPointerException.class, () -> Username.isValidUsername(null)); + + // invalid repo + assertFalse(Username.isValidUsername("")); // empty string + assertFalse(Username.isValidUsername(" ")); // spaces only + assertFalse(Username.isValidUsername("^")); // only non-alphanumeric characters + assertFalse(Username.isValidUsername("ca jacxvccxk")); // alphabets only with spaces + assertFalse(Username.isValidUsername("git hub1212#")); // alphanumeric characters with spaces + + + // valid repo + assertTrue(Username.isValidUsername("12345")); // numbers only + assertTrue(Username.isValidUsername("github-repo-4")); // with capital letters + assertTrue(Username.isValidUsername("git_hub")); // contains non-alphanumeric characters + + } +} +``` +###### \java\seedu\progresschecker\model\issue\AssigneesTest.java +``` java +public class AssigneesTest { + + @Test + public void constructor_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> new Assignees(null)); + } + + @Test + public void constructor_invalidAssigneeName_throwsIllegalArgumentException() { + String invalidAssigneeName = ""; + Assert.assertThrows(IllegalArgumentException.class, () -> new Assignees(invalidAssigneeName)); + } + + @Test + public void isValidAssigneeName() { + // null tag name + Assert.assertThrows(NullPointerException.class, () -> Assignees.isValidAssignee(null)); + } +} +``` +###### \java\seedu\progresschecker\model\issue\BodyTest.java +``` java +public class BodyTest { + + @Test + public void constructor_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> new Body(null)); + } + + @Test + public void isValidBody() { + // null name + assertFalse(Body.isValidBody(null)); + + // valid name + assertTrue(Body.isValidBody("peter jack")); // alphabets only + assertTrue(Body.isValidBody("12345")); // numbers only + assertTrue(Body.isValidBody("peter the 2nd")); // alphanumeric characters + assertTrue(Body.isValidBody("Capital Tan")); // with capital letters + assertTrue(Body.isValidBody("David Roger Jackson Ray Jr 2nd")); // long names + assertTrue(Body.isValidBody("^")); // only non-alphanumeric characters + assertTrue(Body.isValidBody("peter*")); // contains non-alphanumeric characters + } +} +``` +###### \java\seedu\progresschecker\model\issue\LabelsTest.java +``` java +public class LabelsTest { + @Test + public void constructor_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> new Labels(null)); + } + + @Test + public void constructor_invalidLabelName_throwsIllegalArgumentException() { + String invalidLabelName = ""; + Assert.assertThrows(IllegalArgumentException.class, () -> new Labels(invalidLabelName)); + } + + @Test + public void isValidLabelName() { + // null tag name + Assert.assertThrows(NullPointerException.class, () -> Labels.isValidLabel(null)); + + // valid name + assertTrue(Labels.isValidLabel("peter jack")); // alphabets only + assertTrue(Labels.isValidLabel("12345")); // numbers only + assertTrue(Labels.isValidLabel("peter the 2nd")); // alphanumeric characters + assertTrue(Labels.isValidLabel("Capital Tan")); // with capital letters + assertTrue(Labels.isValidLabel("David Roger Jackson Ray Jr 2nd")); // long names + assertTrue(Labels.isValidLabel("peter*")); // contains non-alphanumeric characters + assertTrue(Labels.isValidLabel("^")); // only non-alphanumeric characters + } +} +``` +###### \java\seedu\progresschecker\model\issue\MilestoneTest.java +``` java +public class MilestoneTest { + +} +``` +###### \java\seedu\progresschecker\model\issue\TitleTest.java +``` java +public class TitleTest { + + @Test + public void constructor_null_throwsNullPointerException() { + Assert.assertThrows(NullPointerException.class, () -> new Title(null)); + } + + @Test + public void constructor_invalidTitle_throwsIllegalArgumentException() { + String invalidTitle = ""; + Assert.assertThrows(IllegalArgumentException.class, () -> new Title(invalidTitle)); + } + + @Test + public void isValidTitle() { + // null name + Assert.assertThrows(NullPointerException.class, () -> Title.isValidTitle(null)); + + // invalid name + assertFalse(Title.isValidTitle("")); // empty string + assertFalse(Title.isValidTitle(" ")); // spaces only + + // valid name + assertTrue(Title.isValidTitle("peter jack")); // alphabets only + assertTrue(Title.isValidTitle("12345")); // numbers only + assertTrue(Title.isValidTitle("peter the 2nd")); // alphanumeric characters + assertTrue(Title.isValidTitle("Capital Tan")); // with capital letters + assertTrue(Title.isValidTitle("David Roger Jackson Ray Jr 2nd")); // long names + assertTrue(Title.isValidTitle("peter*")); // contains non-alphanumeric characters + assertTrue(Title.isValidTitle("^")); // only non-alphanumeric characters + + } +} +``` +###### \java\seedu\progresschecker\testutil\GitDetailsBuilder.java +``` java +/** + * A utility class to help with building GitDetails objects. + */ +public class GitDetailsBuilder { + + public static final String DEFAULT_REPO = "AdityaA1998/CS2103TESTING"; + public static final String DEFAULT_USERNAME = "anminkang"; + public static final String DEFAULT_PASSCDE = "aditya2018"; + + private Repository repository; + private Username username; + private Passcode passcode; + + public GitDetailsBuilder() { + repository = new Repository(DEFAULT_REPO); + username = new Username(DEFAULT_USERNAME); + passcode = new Passcode(DEFAULT_PASSCDE); + } + + /** + * Initializes the GitDetailsBuilder with the data of {@code detailsToCopy}. + */ + public GitDetailsBuilder (GitDetails detailsToCopy) { + repository = detailsToCopy.getRepository(); + passcode = detailsToCopy.getPasscode(); + username = detailsToCopy.getUsername(); + } + + /** + * Sets the {@code Repository} of the {@code GitDetails} that we are building. + */ + public GitDetailsBuilder withRepository(String repository) { + this.repository = new Repository(repository); + return this; + } + + /** + * Sets the {@code Username} of the {@code GitDetails} that we are building. + */ + public GitDetailsBuilder withUsername(String username) { + this.username = new Username(username); + return this; + } + + /** + * Sets the {@code Passcode} of the {@code GitDetails} that we are building. + */ + public GitDetailsBuilder withPasscode(String passcode) { + this.passcode = new Passcode(passcode); + return this; + } + + public GitDetails build() { + return new GitDetails(username, passcode, repository); + } +} +``` +###### \java\seedu\progresschecker\testutil\IssueBuilder.java +``` java +/** + * A utility class to help with building Issue objects. + */ +public class IssueBuilder { + + public static final String DEFAULT_TITLE = "CS2103 software engneering"; + public static final String DEFAULT_ASSIGNEE = "anminkang"; + public static final String DEFAULT_BODY = "This an issue created for testing purposes"; + public static final String DEFAULT_MIILESTONE = "v1.1"; + public static final String DEFAULT_LABELS = "testing"; + + private Title title; + private List<Assignees> assignees; + private Body body; + private Milestone milestone; + private List<Labels> labels; + + public IssueBuilder() { + title = new Title(DEFAULT_TITLE); + assignees = SampleDataUtil.getAssigneeList(DEFAULT_ASSIGNEE); + body = new Body(DEFAULT_BODY); + milestone = new Milestone(DEFAULT_MIILESTONE); + labels = SampleDataUtil.getLabelsList(DEFAULT_LABELS); + } + + /** + * Initializes the IssueBuilder with the data of {@code issueToCopy}. + */ + public IssueBuilder (Issue issueToCopy) { + title = issueToCopy.getTitle(); + assignees = new ArrayList<>(issueToCopy.getAssignees()); + body = issueToCopy.getBody(); + milestone = issueToCopy.getMilestone(); + labels = new ArrayList<>(issueToCopy.getLabelsList()); + } + + /** + * Sets the {@code Title} of the {@code Issue} that we are building. + */ + public IssueBuilder withTitle(String title) { + this.title = new Title(title); + return this; + } + + /** + * Parses the {@code assignees} into a {@code List<Assignee>} and set it to the {@code Issues} that we are building. + */ + public IssueBuilder withAssignees(String... assignees) { + this.assignees = SampleDataUtil.getAssigneeList(assignees); + return this; + } + + /** + * Sets the {@code Body} of the {@code Issue} that we are building. + */ + public IssueBuilder withBody(String body) { + this.body = new Body(body); + return this; + } + + /** + * Sets the {@code Milestone} of the {@code Issue} that we are building. + */ + public IssueBuilder withMilestone(String milestone) { + this.milestone = new Milestone(milestone); + return this; + } + + /** + * Parses the {@code labels} into a {@code List<Labels>} and set it to the {@code Issues} that we are building. + */ + public IssueBuilder withLabels(String... labels) { + this.labels = SampleDataUtil.getLabelsList(labels); + return this; + } + + public Issue build() { + return new Issue(title, assignees, milestone, body, labels); + } +} +``` +###### \java\seedu\progresschecker\testutil\TypicalIssue.java +``` java +/** + * A utility class containing a list of {@code Issue} objects to be used in tests. + */ +public class TypicalIssue { + + public static final Issue TEST_ONE = new IssueBuilder().withTitle("Test one") + .withAssignees("anminkang").withBody("Test 1 body") + .withMilestone("v1.1").withLabels("test1").build(); + public static final Issue TEST_TWO = new IssueBuilder().withTitle("Test two") + .withAssignees("adityaa1998").withBody("Test 2 body") + .withMilestone("v1.2").withLabels("test2").build(); + public static final Issue TEST_THREE = new IssueBuilder().withTitle("Test three") + .withAssignees("kush1509").withBody("Test 3 body") + .withMilestone("v1.3").withLabels("test3").build(); + public static final Issue TEST_FOUR = new IssueBuilder().withTitle("Test four") + .withBody("Test 4 body") + .withMilestone("v1.3").withLabels("test4").build(); + public static final Issue TEST_FIVE = new IssueBuilder().withTitle("Test five") + .withAssignees("anminkang") + .withMilestone("v1.3").withLabels("test5").build(); + public static final Issue TEST_SIX = new IssueBuilder().withTitle("Test six") + .withAssignees("anminkang").withBody("Test 6 body") + .withLabels("test6").build(); + public static final Issue TEST_SEVEN = new IssueBuilder().withTitle("Test seven") + .withAssignees("anminkang").withBody("Test 7 body") + .withMilestone("v1.3").build(); + + //Manually added - Issue's details found in {@code CommandTestUtil} + public static final Issue ISSUE_ONE = new IssueBuilder().withTitle(VALID_TITLE_ONE) + .withAssignees(VALID_ASSIGNEE_ANMIN) + .withBody(VALID_BODY_ONE).withMilestone(VALID_MILESTONE_ONE) + .withLabels(VALID_LABEL_TASK).build(); + public static final Issue ISSUE_TWO = new IssueBuilder().withTitle(VALID_TITLE_TWO) + .withAssignees(VALID_ASSIGNEE_BOB) + .withBody(VALID_BODY_TWO).withMilestone(VALID_MILESTONE_TWO) + .withLabels(VALID_LABEL_STORY).build(); +} +``` +###### \java\seedu\progresschecker\ui\CommandBoxTest.java +``` java + @Test + public void handleKeyPress_tab() { + // add command + commandBoxHandle.setInput(COMMAND_ADD_INCOMPLETE); + assertInputHistory(KeyCode.TAB, COMMAND_ADD_COMPLETE); + + // edit command + commandBoxHandle.setInput(COMMAND_EDIT_INCOMPLETE); + assertInputHistory(KeyCode.TAB, COMMAND_EDIT_COMPLETE); + + // invalid command + commandBoxHandle.setInput(COMMAND_THAT_FAILS); + assertInputHistory(KeyCode.TAB, COMMAND_THAT_FAILS); + } +``` +###### \java\seedu\progresschecker\ui\testutil\EventsCollectorRule.java +``` java + /** + * Returns the second last event collected + */ + public BaseEvent getSecondLast() { + if (events.isEmpty()) { + return null; + } + + return events.get(events.size() - 2); + } +``` +###### \java\systemtests\CreateIssueCommandSystemTest.java +``` java +public class CreateIssueCommandSystemTest extends ProgressCheckerSystemTest { + private final String gitlogin = "gl r/AdityaA1998/CS2103TESTING gu/anminkang pc/aditya2018"; + private final String gitlogout = "gitlogout"; + + @Before + public void setUpCreateIssue() throws Exception { + + Model model = getModel(); + GitDetails validDetails = new GitDetailsBuilder().build(); + model.loginGithub(validDetails); + } + + @Test + public void add() throws Exception { + + /* ------------------------------- Perform create issue operations ----------------------------------- */ + + Issue toCreate = ISSUE_ONE; + + String command = " " + CreateIssueCommand.COMMAND_WORD + " " + TITLE_DESC_ONE + + " " + ASSIGNEE_DESC_ANMIN + " " + + MILESTONE_DESC_ONE + " " + + BODY_DESC_ONE + " " + + LABEL_DEC_TASK + " "; + assertCommandSuccess(command, toCreate); + + /* Case: create a issue, missing assignee -> created */ + assertCommandSuccess(TEST_FOUR); + + /* Case: create a issue, missing body -> created */ + assertCommandSuccess(TEST_FIVE); + + /* Case: create a issue, missing milestone -> created */ + assertCommandSuccess(TEST_SIX); + + /* Case: create a issue, missing labels -> created */ + assertCommandSuccess(TEST_SEVEN); + + /* ------------------------------- Perform invalid create issue operations ------------------------------- */ + + /* Case: Github not authenticated -> rejected */ + command = CreateIssueCommand.COMMAND_WORD + " " + TITLE_DESC_ONE + + " " + ASSIGNEE_DESC_ANMIN + " " + + MILESTONE_DESC_ONE + " " + + BODY_DESC_ONE + " " + + LABEL_DEC_TASK + " "; + assertCommandFailureWithoutAuthentication(command, CreateIssueCommand.MESSAGE_FAILURE); + + /* Case: missing title -> rejected */ + command = CreateIssueCommand.COMMAND_WORD + " " + + " " + ASSIGNEE_DESC_ANMIN + " " + + MILESTONE_DESC_ONE + " " + + BODY_DESC_ONE + " " + + LABEL_DEC_TASK + " "; + assertCommandFailure(command, String.format(MESSAGE_INVALID_COMMAND_FORMAT, CreateIssueCommand.MESSAGE_USAGE)); + + /* Case: invalid keyword -> rejected */ + command = "+issues " + IssueUtil.getCreateIssueCommand(toCreate); + assertCommandFailure(command, Messages.MESSAGE_UNKNOWN_COMMAND); + + /* Case: invalid title -> rejected */ + command = " " + CreateIssueCommand.COMMAND_WORD + " " + INVALID_TITLE_DESC + + " " + ASSIGNEE_DESC_ANMIN + " " + + MILESTONE_DESC_ONE + " " + + BODY_DESC_ONE + " " + + LABEL_DEC_TASK + " "; + assertCommandFailure(command, Title.MESSAGE_TITLE_CONSTRAINTS); + + /* Case: invalid assignee -> rejected */ + command = " " + CreateIssueCommand.COMMAND_WORD + " " + TITLE_DESC_ONE + + " " + INVALID_ASSIGNEE_DESC + " " + + MILESTONE_DESC_ONE + " " + + BODY_DESC_ONE + " " + + LABEL_DEC_TASK + " "; + assertCommandFailure(command, Assignees.MESSAGE_ASSIGNEES_CONSTRAINTS); + + /* Case: invalid milestone -> rejected */ + //command = CreateIssueCommand.COMMAND_WORD + " " + TITLE_DESC_ONE + //+ " " + ASSIGNEE_DESC_ANMIN + " " + //+ INVALID_MILESTONE_DESC + " " + //+ BODY_DESC_ONE + " " + //+ LABEL_DEC_TASK + " "; + + /* Case: invalid labels -> rejected */ + command = CreateIssueCommand.COMMAND_WORD + " " + TITLE_DESC_ONE + + " " + ASSIGNEE_DESC_ANMIN + " " + + MILESTONE_DESC_ONE + " " + + INVALID_BODY_DESC + " " + + INVALID_LABEL_DESC + " "; + assertCommandFailure(command, Labels.MESSAGE_LABEL_CONSTRAINTS); + + } + + /** + * Executes the {@code CreateIssueCommand} that adds {@code toCreate} to the model and asserts that the,<br> + * 1. Command box displays an empty string.<br> + * 2. Command box has the default style class.<br> + * 3. Result display box displays the success message of executing {@code CreateIssueCommand} with the details of + * {@code toCreate}.<br> + * 4. {@code Model}, {@code Storage} and {@code issueListPanel} equal to the corresponding components in + * the current model added with {@code toCreate}.<br> + * 5. Browser url and selected card remain unchanged.<br> + * 6. Status bar's sync status changes.<br> + * Verifications 1, 3 and 4 are performed by + * {@code ProgressCheckerSystemTest#assertApplicationDisplaysExpected(String, String, Model)}.<br> + * @see ProgressCheckerSystemTest#assertApplicationDisplaysExpected(String, String, Model) + */ + private void assertCommandSuccess(Issue toCreate) throws Exception { + assertCommandSuccess(IssueUtil.getCreateIssueCommand(toCreate), toCreate); + } + + /** + * Performs the same verification as {@code assertCommandSuccess(issue)}. Executes {@code command} + * instead. + */ + private void assertCommandSuccess(String command, Issue toCreate) throws Exception { + Model expectedModel = getModel(); + GitDetails validDetails = new GitDetailsBuilder().build(); + expectedModel.loginGithub(validDetails); + try { + expectedModel.createIssueOnGitHub(toCreate); + } catch (IOException | CommandException e) { + throw new IllegalArgumentException("Check authentication or parameters"); + } + String expectedResultMessage = CreateIssueCommand.MESSAGE_SUCCESS; + + assertCommandSuccess(command, expectedModel, expectedResultMessage); + + } + + /** + * Performs the same verification as {@code assertCommandSuccess(String, Issue)} except asserts that + * the,<br> + * 1. Result display box displays {@code expectedResultMessage}.<br> + * 2. {@code Model}, {@code Storage} and {@code issueListPanel} equal to the corresponding components in + * {@code expectedModel}.<br> + */ + private void assertCommandSuccess(String command, Model expectedModel, String expectedResultMessage) { + executeCommand(gitlogin); + executeCommand(command); + assertApplicationDisplaysExpectedForIssue("", expectedResultMessage, expectedModel); + assertCommandBoxShowsDefaultStyle(); + assertStatusBarUnchangedExceptSyncStatus(); + } + + /** + * Executes {@code command} and asserts that the,<br> + * 1. Command box displays {@code command}.<br> + * 2. Command box has the error style class.<br> + * 3. Result display box displays {@code expectedResultMessage}.<br> + * 4. {@code Model}, {@code Storage} and {@code PersonListPanel} remain unchanged.<br> + * 5. Browser url, selected card and status bar remain unchanged.<br> + * Verifications 1, 3 and 4 are performed by + * {@code ProgressCheckerSystemTest#assertApplicationDisplaysExpected(String, String, Model)}.<br> + * @see ProgressCheckerSystemTest#assertApplicationDisplaysExpected(String, String, Model) + */ + private void assertCommandFailure(String command, String expectedResultMessage) { + Model expectedModel = getModel(); + + executeCommand(gitlogin); + executeCommand(command); + assertApplicationDisplaysExpectedForIssue(command, expectedResultMessage, expectedModel); + assertCommandBoxShowsErrorStyle(); + assertStatusBarUnchanged(); + } + + /** + * Executes {@code command} and asserts that the,<br> + * 1. Command box displays {@code command}.<br> + * 2. Command box has the error style class.<br> + * 3. Result display box displays {@code expectedResultMessage}.<br> + * 4. {@code Model}, {@code Storage} and {@code PersonListPanel} remain unchanged.<br> + * 5. Browser url, selected card and status bar remain unchanged.<br> + * Verifications 1, 3 and 4 are performed by + * {@code ProgressCheckerSystemTest#assertApplicationDisplaysExpected(String, String, Model)}.<br> + * @see ProgressCheckerSystemTest#assertApplicationDisplaysExpected(String, String, Model) + */ + private void assertCommandFailureWithoutAuthentication(String command, String expectedResultMessage) { + Model expectedModel = getModel(); + + executeCommand(gitlogout); + executeCommand(command); + assertApplicationDisplaysExpectedForIssue(command, expectedResultMessage, expectedModel); + assertCommandBoxShowsErrorStyle(); + assertStatusBarUnchanged(); + } +} +``` +###### \java\systemtests\ProgressCheckerSystemTest.java +``` java + /** + * Asserts that the {@code CommandBox} displays {@code expectedCommandInput}, the {@code ResultDisplay} displays + * {@code expectedResultMessage}, the model and storage contains the same person objects as {@code expectedModel} + * and the person list panel displays the persons in the model correctly. + */ + protected void assertApplicationDisplaysExpectedForIssue(String expectedCommandInput, String expectedResultMessage, + Model expectedModel) { + assertEquals(expectedCommandInput, getCommandBox().getInput()); + assertEquals(expectedResultMessage, getResultDisplay().getText()); + } +``` diff --git a/collated/test/iNekox3.md b/collated/test/iNekox3.md new file mode 100644 index 000000000000..77e590a08cad --- /dev/null +++ b/collated/test/iNekox3.md @@ -0,0 +1,192 @@ +# iNekox3 +###### \java\seedu\progresschecker\logic\commands\AnswerCommandTest.java +``` java +public class AnswerCommandTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void constructor_nullExercise_throwsNullPointerException() { + thrown.expect(NullPointerException.class); + new AnswerCommand(null, null); + } + + @Test + public void equals() { + Exercise exercise1 = new ExerciseBuilder().withStudentAnswer("a b c").build(); + Exercise exercise2 = new ExerciseBuilder().withStudentAnswer("d e f").build(); + AnswerCommand answerExercise1Command = new AnswerCommand(exercise1.getQuestionIndex(), + new StudentAnswer("a b c")); + AnswerCommand answerExercise2Command = new AnswerCommand(exercise2.getQuestionIndex(), + new StudentAnswer("d e f")); + + // same object -> returns true + assertTrue(answerExercise1Command.equals(answerExercise1Command)); + + // different types -> returns false + assertFalse(answerExercise1Command.equals(1)); + + // null -> returns false + assertFalse(answerExercise1Command.equals(null)); + + // different person -> returns false + assertFalse(answerExercise1Command.equals(answerExercise2Command)); + } +} +``` +###### \java\seedu\progresschecker\logic\commands\ViewCommandTest.java +``` java +/** + * Contains assertion tests for {@code ViewCommand}. + */ +public class ViewCommandTest { + @Test + public void equals() { + ViewCommand viewFirstCommand = new ViewCommand(TYPE_TASK, -1, false); + ViewCommand viewSecondCommand = new ViewCommand(TYPE_EXERCISE, 11, true); + + // same object -> returns true + assertTrue(viewFirstCommand.equals(viewFirstCommand)); + + // same values -> returns true + ViewCommand viewFirstCommandCopy = new ViewCommand(TYPE_TASK, -1, false); + assertTrue(viewFirstCommand.equals(viewFirstCommandCopy)); + + // different types -> returns false + assertFalse(viewFirstCommand.equals(1)); + + // null -> returns false + assertFalse(viewFirstCommand.equals(null)); + + // different type -> returns false + assertFalse(viewFirstCommand.equals(viewSecondCommand)); + } +} +``` +###### \java\seedu\progresschecker\logic\parser\AnswerCommandParserTest.java +``` java +public class AnswerCommandParserTest { + + private AnswerCommandParser parser = new AnswerCommandParser(); + + @Test + public void parse_invalidArgsIndex_throwsParseException() { + assertParseFailure(parser, "ans 11.50.80 b", MESSAGE_INVALID_WEEK_NUMBER + + " \n" + AnswerCommand.MESSAGE_USAGE); + } +} +``` +###### \java\seedu\progresschecker\logic\parser\ProgressCheckerParserTest.java +``` java + @Test + public void parseCommand_view() throws Exception { + ViewCommand command = (ViewCommand) parser.parseCommand( + ViewCommand.COMMAND_WORD + " " + TYPE_EXERCISE + " 11 true"); + assertEquals(new ViewCommand(TYPE_EXERCISE, 11, true), command); + } + +``` +###### \java\seedu\progresschecker\logic\parser\ViewCommandParserTest.java +``` java +public class ViewCommandParserTest { + + private ViewCommandParser parser = new ViewCommandParser(); + + @Test + public void parse_validArgsType_returnsViewCommand() { + assertParseSuccess(parser, "exercise", new ViewCommand(TYPE_EXERCISE, -1, + false)); + } + + @Test + public void parse_validArgsWeekNumber_returnsViewCommand() { + assertParseSuccess(parser, "exercise 5", new ViewCommand(TYPE_EXERCISE, 5, + true)); + } + + @Test + public void parse_invalidArgsType_throwsParseException() { + assertParseFailure(parser, "invalid type", MESSAGE_INVALID_TAB_TYPE + + " \n" + ViewCommand.MESSAGE_USAGE); + } + + @Test + public void parse_invalidArgsWeekNumber_throwsParseException() { + assertParseFailure(parser, "exercise 0", MESSAGE_INVALID_WEEK_NUMBER + + " \n" + ViewCommand.MESSAGE_USAGE); + } +} +``` +###### \java\seedu\progresschecker\testutil\ExerciseBuilder.java +``` java +/** + * A utility class to help with building Exercise objects. + */ +public class ExerciseBuilder { + + public static final String DEFAULT_QUESTION_INDEX = "11.1.1"; + public static final String DEFAULT_QUESTION_TYPE = "choice"; + public static final String DEFAULT_QUESTION = "What is the main difference between" + + "a class diagram and and an OO domain model?\n" + + "a. One is about the problem domain while the other is about the solution domain.\n" + + "b. One has more classes than the other.\n" + + "c. One shows more details than the other.\n" + + "d. One is a UML diagram, while the other is not a UML diagram."; + public static final String DEFAULT_STUDENT_ANSWER = ""; + public static final String DEFAULT_MODEL_ANSWER = "a. Both are UML diagrams, and use the class diagram notation. " + + "While it is true that often a class diagram may have more classes and more details, " + + "the main difference is that the OO domain model describes the problem domain " + + "while the class diagram describes the solution."; + + private QuestionIndex questionIndex; + private QuestionType questionType; + private Question question; + private StudentAnswer studentAnswer; + private ModelAnswer modelAnswer; + + public ExerciseBuilder() { + questionIndex = new QuestionIndex(DEFAULT_QUESTION_INDEX); + questionType = new QuestionType(DEFAULT_QUESTION_TYPE); + question = new Question(DEFAULT_QUESTION); + studentAnswer = new StudentAnswer(DEFAULT_STUDENT_ANSWER); + modelAnswer = new ModelAnswer(DEFAULT_MODEL_ANSWER); + } + + /** + * Initializes the ExerciseBuilder with the data of {@code exerciseToCopy}. + */ + public ExerciseBuilder(Exercise exerciseToCopy) { + questionIndex = exerciseToCopy.getQuestionIndex(); + questionType = exerciseToCopy.getQuestionType(); + question = exerciseToCopy.getQuestion(); + studentAnswer = exerciseToCopy.getStudentAnswer(); + modelAnswer = exerciseToCopy.getModelAnswer(); + } + + /** + * Sets the {@code StudentAnswer} of the {@code Exercise} that we are building. + */ + public ExerciseBuilder withStudentAnswer(String studentAnswer) { + this.studentAnswer = new StudentAnswer(studentAnswer); + return this; + } + + public Exercise build() { + return new Exercise(questionIndex, questionType, question, studentAnswer, modelAnswer); + } + +} +``` +###### \java\seedu\progresschecker\testutil\TypicalTabTypes.java +``` java +/** + * A utility class containing a list of {@code String} objects to be used in tests. + */ +public class TypicalTabTypes { + public static final String TYPE_PROFILE = "profile"; + public static final String TYPE_TASK = "task"; + public static final String TYPE_EXERCISE = "exercise"; + public static final String TYPE_ISSUES = "issues"; +} +``` diff --git a/collated/unused/EdwardKSG-unused.md b/collated/unused/EdwardKSG-unused.md new file mode 100644 index 000000000000..87bff2d2d8ba --- /dev/null +++ b/collated/unused/EdwardKSG-unused.md @@ -0,0 +1,98 @@ +# EdwardKSG-unused +###### \remotePostgresConnection.java +``` java +//unused because relational database is not allowed in this project +/** + * Tests connection to remote Postgres database server (Amazon AWS). + */ +public class remotePostgresConnectionTest { + + public static void main(String[] args) { + + //information for connecting to the remote postgresql server + String url = "jdbc:postgresql://rds-postgresql-addressbook.cnpjakv2naou.ap-southeast-1.rds.amazonaws.com:5434/addressbook"; + String user = "anminkang"; + String password = "addressbook"; + + try { + Class.forName("org.postgresql.Driver"); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + + try (Connection con = DriverManager.getConnection(url, user, password); + Statement st = con.createStatement(); + ResultSet rs = st.executeQuery("SELECT VERSION()")) { + + if (rs.next()) { + System.out.println(rs.getString(1)); + } + + } catch (SQLException ex) { + + Logger lgr = Logger.getLogger(remotePostgresConnectionTest.class.getName()); + lgr.log(Level.SEVERE, ex.getMessage(), ex); + } + } +} +``` +###### \schema.sql +``` sql +/* unused because relational database is not allowed in this project*/ +CREATE TABLE user ( + /*NUS net id*/ + netid VARCHAR(8) PRIMARY KEY , + name VARCHAR(100) NOT NULL , + phone NUMERIC NOT NULL UNIQUE , + + /*alternative email besides the default email which is auto-generated based on NETID*/ + email VARCHAR(50), + + /*year of study*/ + year NUMERIC, + + /*major course*/ + course VARCHAR(30), + gender BOOLEAN, + + /*accumulated points earned by a user, as an evidence of the person's learning progress*/ + progress NUMERIC, + grpid VARCHAR(6) REFERENCES group(grpid) ON DELETE CASCADE ON UPDATE CASCADE +); + +CREATE TABLE question ( + index VARCHAR(10) PRIMARY KEY , + + /*the question itself*/ + content VARCHAR(200) NOT NULL +); + +CREATE TABLE answer_question ( + index VARCHAR(10) REFERENCES question(index) ON DELETE CASCADE ON UPDATE CASCADE , + + /*answer filled by a user*/ + answer VARCHAR(1000), + userid VARCHAR(8) REFERENCES user(netid) ON DELETE CASCADE ON UPDATE CASCADE , + PRIMARY KEY (index, userid) +); + +/*project group of CS2103 and CS2103T*/ +CREATE TABLE group ( + grpid VARCHAR(6) PRIMARY KEY , + member1 VARCHAR(8) REFERENCES user(netid) ON DELETE CASCADE ON UPDATE CASCADE , + member2 VARCHAR(8) REFERENCES user(netid) ON DELETE CASCADE ON UPDATE CASCADE , + member3 VARCHAR(8) REFERENCES user(netid) ON DELETE CASCADE ON UPDATE CASCADE , + member4 VARCHAR(8) REFERENCES user(netid) ON DELETE CASCADE ON UPDATE CASCADE , + + /*in case we have extra students to squeeze in one project group*/ + member5 VARCHAR(8) REFERENCES user(netid) ON DELETE CASCADE ON UPDATE CASCADE +); + +CREATE TABLE announcement ( + userid VARCHAR(8) REFERENCES user(netid) ON DELETE CASCADE ON UPDATE CASCADE , + + /*time of creation*/ + time TIME, + PRIMARY KEY (userid, time) +) +``` diff --git a/copyright.txt b/copyright.txt index 93aa2a39ce25..bb4005f57a24 100644 --- a/copyright.txt +++ b/copyright.txt @@ -1,8 +1,8 @@ Some code adapted from http://code.makery.ch/library/javafx-8-tutorial/ by Marco Jakob Copyright by Susumu Yoshida - http://www.mcdodesign.com/ -- address_book_32.png -- AddressApp.ico +- progress_checker_32.png +- ProgressChecker.ico Copyright by Jan Jan Kovařík - http://glyphicons.com/ - calendar.png diff --git a/docs/AboutUs.adoc b/docs/AboutUs.adoc index 0f0a8e7ab51e..377ce372750c 100644 --- a/docs/AboutUs.adoc +++ b/docs/AboutUs.adoc @@ -3,53 +3,50 @@ :imagesDir: images :stylesDir: stylesheets -AddressBook - Level 4 was developed by the https://se-edu.github.io/docs/Team.html[se-edu] team. + -_{The dummy content given below serves as a placeholder to be used by future forks of the project.}_ + +ProgressChecker was developed by the https://github.com/CS2103JAN2018-T09-B3/main[T09-B3] team. + +ProgressChecker helps to remind students learning outcomes every week. + +With a simple click, students will never miss their weekly LOs again. + +It also keeps track of the progress of each team member. + +Thus it is convenient for teammates to compare help each other. + {empty} + We are a team based in the http://www.comp.nus.edu.sg[School of Computing, National University of Singapore]. == Project Team -=== John Doe -image::damithc.jpg[width="150", align="left"] -{empty}[http://www.comp.nus.edu.sg/~damithch[homepage]] [https://github.com/damithc[github]] [<<johndoe#, portfolio>>] - -Role: Project Advisor - ''' -=== John Roe -image::lejolly.jpg[width="150", align="left"] -{empty}[http://github.com/lejolly[github]] [<<johndoe#, portfolio>>] +=== Aditya Agarwal +image::adityaa1998.png[width="150", align="left"] +{empty}[http://github.com/adityaa1998[github]] [<<aditya#, portfolio>>] Role: Team Lead + -Responsibilities: UI +Responsibilities: Integration, code quality ''' -=== Johnny Doe -image::yijinl.jpg[width="150", align="left"] -{empty}[http://github.com/yijinl[github]] [<<johndoe#, portfolio>>] +=== Lai Liwen +image::livian1107.png[width="150", align="left"] +{empty}[https://github.com/Livian1107[github]] [<<liwen#, portfolio>>] Role: Developer + -Responsibilities: Data +Responsibilities: UI + Documentation + Testing(helper) ''' -=== Johnny Roe -image::m133225.jpg[width="150", align="left"] -{empty}[http://github.com/m133225[github]] [<<johndoe#, portfolio>>] +=== Koh Yee Ru +image::inekox3.png[width="150", align="left"] +{empty}[http://github.com/inekox3[github]] [<<yeeru#, portfolio>>] Role: Developer + -Responsibilities: Dev Ops + Threading +Responsibilities: Logic + Scheduling & Tracking + Deliverables & Deadlines ''' -=== Benson Meier -image::yl_coder.jpg[width="150", align="left"] -{empty}[http://github.com/yl-coder[github]] [<<johndoe#, portfolio>>] +=== Kang Anmin +image::edwardksg.png[width="150", align="left"] +{empty}[https://github.com/EdwardKSG[github]] [<<anmin#, portfolio>>] Role: Developer + -Responsibilities: UI +Responsibilities: Model + Testing + Scheduling & Tracking(helper) ''' diff --git a/docs/DeveloperGuide.adoc b/docs/DeveloperGuide.adoc index 1733af113b29..0e2c5866b3a9 100644 --- a/docs/DeveloperGuide.adoc +++ b/docs/DeveloperGuide.adoc @@ -1,4 +1,4 @@ -= AddressBook Level 4 - Developer Guide += ProgressChecker - Developer Guide :toc: :toc-title: :toc-placement: preamble @@ -10,12 +10,34 @@ ifdef::env-github[] :tip-caption: :bulb: :note-caption: :information_source: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level4/tree/master +:repoURL: https://github.com/CS2103JAN2018-T09-B3/main/tree/master -By: `Team SE-EDU`      Since: `Jun 2016`      Licence: `MIT` +By: `Team T09-B3`      Since: `Feb 2018`      Licence: `MIT` + +== Introduction + +ProgressChecker is for *students* who *prefer to use a desktop app* to keep track of their learning progressthroughout the certain module. (Current version is available for CS2103/T) + + +For the current version, you can add your teammates details into ProgressChecker. You can also create a new task list through google task. By default, app will display all the link:DeveloperGuide.adoc#Learning-Outcomes[Learning Outcomes] taken from the https://nus-cs2103-ay1718s2.github.io/website/index.html[CS2103/T module website] for this week in the task list. Students can use this task list to track their weekly homework and the progress of the project. + +More importantly, ProgressChecker is *optimized for students who prefer to work with a Command Line Interface* (CLI) while still having the benefits of a Graphical User Interface (link:DeveloperGuide.adoc#GUI[GUI]). +If you can type fast, ProgressChecker can get your learning outcome tasks done faster than traditional GUI apps. + + +Now you are ready to jump to the <<Setting-up,Setting up>> to get started. Enjoy! + +== Icons Meaning +You will be seeing these icons throughout the guide. Each icon display specific information. + +[TIP] +This lightbulb icon means tips that you can try when using ProgressChecker. + +[NOTE] +This info icon means notes that you should pay attention to when using ProgressChecker. == Setting up +There are some things you will need to set up before getting started in contributing to ProgressChecker. Below lists the important key elements you will have to configure. + === Prerequisites . *JDK `1.8.0_60`* or later @@ -34,21 +56,27 @@ Do not disable them. If you have disabled them, go to `File` > `Settings` > `Plu === Setting up the project in your computer -. Fork this repo, and clone the fork to your computer +. https://www.atlassian.com/git/tutorials/comparing-workflows#forking-workflow[Fork] this repo, and https://nus-cs2103-ay1718s2.github.io/website/book/gitAndGithub/init/index.html[clone] the fork to your computer . Open IntelliJ (if you are not in the welcome screen, click `File` > `Close Project` to close the existing project dialog first) . Set up the correct JDK version for Gradle -.. Click `Configure` > `Project Defaults` > `Project Structure` -.. Click `New...` and find the directory of the JDK +... Click `Configure` > `Project Defaults` > `Project Structure` +image:setup2.png[width="600"] + +_step 3.i_ +... Click `New...` and find the directory of the JDK . Click `Import Project` -. Locate the `build.gradle` file and select it. Click `OK` +. Locate the `build.gradle` file and select it. Click `OK` + +image:setup3.png[width="600"] + +_step 5_ . Click `Open as Project` . Click `OK` to accept the default settings . Open a console and run the command `gradlew processResources` (Mac/Linux: `./gradlew processResources`). It should finish with the `BUILD SUCCESSFUL` message. + This will generate all resources required by the application and tests. +image:setup5.png[width="600"] + +_step 8_ === Verifying the setup -. Run the `seedu.address.MainApp` and try a few commands +. Run the `gradlew.bat run` and try a few commands . <<Testing,Run the tests>> to ensure they all pass. === Configurations to do before writing code @@ -68,7 +96,7 @@ Optionally, you can follow the <<UsingCheckstyle#, UsingCheckstyle.adoc>> docume ==== Updating documentation to match your fork -After forking the repo, links in the documentation will still point to the `se-edu/addressbook-level4` repo. If you plan to develop this as a separate product (i.e. instead of contributing to the `se-edu/addressbook-level4`) , you should replace the URL in the variable `repoURL` in `DeveloperGuide.adoc` and `UserGuide.adoc` with the URL of your fork. +After forking the repo, links in the documentation will still point to the `CS2103JAN2018-T09-B3/main` repo. If you plan to develop this as a separate product (i.e. instead of contributing to the `CS2103JAN2018-T09-B3/main`) , you should replace the URL in the variable `repoURL` in `DeveloperGuide.adoc` and `UserGuide.adoc` with the URL of your fork. ==== Setting up CI @@ -86,28 +114,33 @@ Having both Travis and AppVeyor ensures your App works on both Unix-based platfo ==== Getting started with coding -When you are ready to start coding, +Now you are ready to start coding! You can: -1. Get some sense of the overall design by reading <<Design-Architecture>>. +1. Get some sense of the overall design by reading <<Design-Architecture,Design Architecture>>. 2. Take a look at <<GetStartedProgramming>>. == Design [[Design-Architecture]] + +ProgressChecker consists of multiple components that work together via an event-driven structure. This section will break down the various components in details to help you jump straight into understanding the architecture in depth. + === Architecture +The *_Architecture Diagram_* given below explains the high-level design of the App. Given below is a quick overview of each component. + .Architecture Diagram image::Architecture.png[width="600"] - -The *_Architecture Diagram_* given above explains the high-level design of the App. Given below is a quick overview of each component. - +{sp} + [TIP] The `.pptx` files used to create diagrams in this document can be found in the link:{repoURL}/docs/diagrams/[diagrams] folder. To update a diagram, modify the diagram in the pptx file, select the objects of the diagram, and choose `Save as picture`. -`Main` has only one class called link:{repoURL}/src/main/java/seedu/address/MainApp.java[`MainApp`]. It is responsible for, +{sp} + -* At app launch: Initializes the components in the correct sequence, and connects them up with each other. -* At shut down: Shuts down the components and invokes cleanup method where necessary. +`Main` has only one class called link:{repoURL}/src/main/java/seedu/progresschecker/MainApp.java[`MainApp`]. It is responsible for: + + +* Initializing the components in the correct sequence, and connects them up with each other at app launch. +* Shutting down the components and invokes cleanup method where necessary. <<Design-Commons,*`Commons`*>> represents a collection of classes used by multiple other components. Two of those classes play important roles at the architecture level. @@ -131,25 +164,36 @@ For example, the `Logic` component (see the class diagram given below) defines i .Class Diagram of the Logic Component image::LogicClassDiagram.png[width="800"] +{sp}+ + [discrete] ==== Events-Driven nature of the design The _Sequence Diagram_ below shows how the components interact for the scenario where the user issues the command `delete 1`. .Component interactions for `delete 1` command (part 1) + image::SDforDeletePerson.png[width="800"] +{sp}+ + [NOTE] -Note how the `Model` simply raises a `AddressBookChangedEvent` when the Address Book data are changed, instead of asking the `Storage` to save the updates to the hard disk. +Note how the `Model` simply raises a `ProgressCheckerChangedEvent` when the Address Book data are changed, instead of asking the `Storage` to save the updates to the hard disk. + +{sp} + The diagram below shows how the `EventsCenter` reacts to that event, which eventually results in the updates being saved to the hard disk and the status bar of the UI being updated to reflect the 'Last Updated' time. .Component interactions for `delete 1` command (part 2) image::SDforDeletePersonEventHandling.png[width="800"] +{sp} + + [NOTE] Note how the event is propagated through the `EventsCenter` to the `Storage` and `UI` without `Model` having to be coupled to either of them. This is an example of how this Event Driven approach helps us reduce direct coupling between components. +{sp} + + The sections below give more details of each component. [[Design-Ui]] @@ -158,11 +202,13 @@ The sections below give more details of each component. .Structure of the UI Component image::UiClassDiagram.png[width="800"] -*API* : link:{repoURL}/src/main/java/seedu/address/ui/Ui.java[`Ui.java`] +{sp}+ + +*API* : link:{repoURL}/src/main/java/seedu/progresschecker/ui/Ui.java[`Ui.java`] The UI consists of a `MainWindow` that is made up of parts e.g.`CommandBox`, `ResultDisplay`, `PersonListPanel`, `StatusBarFooter`, `BrowserPanel` etc. All these, including the `MainWindow`, inherit from the abstract `UiPart` class. -The `UI` component uses JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files that are in the `src/main/resources/view` folder. For example, the layout of the link:{repoURL}/src/main/java/seedu/address/ui/MainWindow.java[`MainWindow`] is specified in link:{repoURL}/src/main/resources/view/MainWindow.fxml[`MainWindow.fxml`] +The `UI` component uses JavaFx UI framework. The layout of these UI parts are defined in matching `.fxml` files that are in the `src/main/resources/view` folder. For example, the layout of the link:{repoURL}/src/main/java/seedu/progresschecker/ui/MainWindow.java[`MainWindow`] is specified in link:{repoURL}/src/main/resources/view/MainWindow.fxml[`MainWindow.fxml`] The `UI` component, @@ -177,15 +223,19 @@ The `UI` component, .Structure of the Logic Component image::LogicClassDiagram.png[width="800"] +{sp}+ + .Structure of Commands in the Logic Component. This diagram shows finer details concerning `XYZCommand` and `Command` in <<fig-LogicClassDiagram>> image::LogicCommandClassDiagram.png[width="800"] +{sp}+ + *API* : -link:{repoURL}/src/main/java/seedu/address/logic/Logic.java[`Logic.java`] +link:{repoURL}/src/main/java/seedu/progresschecker/logic/Logic.java[`Logic.java`] -. `Logic` uses the `AddressBookParser` class to parse the user command. +. `Logic` uses the `ProgressCheckerParser` class to parse the user command. . This results in a `Command` object which is executed by the `LogicManager`. -. The command execution can affect the `Model` (e.g. adding a person) and/or raise events. +. The command execution can affect the `Model` (e.g. adding a teammate) and/or raise events. . The result of the command execution is encapsulated as a `CommandResult` object which is passed back to the `Ui`. Given below is the Sequence Diagram for interactions within the `Logic` component for the `execute("delete 1")` API call. @@ -199,7 +249,7 @@ image::DeletePersonSdForLogic.png[width="800"] .Structure of the Model Component image::ModelClassDiagram.png[width="800"] -*API* : link:{repoURL}/src/main/java/seedu/address/model/Model.java[`Model.java`] +*API* : link:{repoURL}/src/main/java/seedu/progresschecker/model/Model.java[`Model.java`] The `Model`, @@ -214,7 +264,9 @@ The `Model`, .Structure of the Storage Component image::StorageClassDiagram.png[width="800"] -*API* : link:{repoURL}/src/main/java/seedu/address/storage/Storage.java[`Storage.java`] +{sp}+ + +*API* : link:{repoURL}/src/main/java/seedu/progresschecker/storage/Storage.java[`Storage.java`] The `Storage` component, @@ -224,7 +276,7 @@ The `Storage` component, [[Design-Commons]] === Common classes -Classes used by multiple components are in the `seedu.addressbook.commons` package. +Classes used by multiple components are in the `seedu.progresschecker.commons` package. == Implementation @@ -234,13 +286,16 @@ This section describes some noteworthy details on how certain features are imple === Undo/Redo feature ==== Current Implementation -The undo/redo mechanism is facilitated by an `UndoRedoStack`, which resides inside `LogicManager`. It supports undoing and redoing of commands that modifies the state of the address book (e.g. `add`, `edit`). Such commands will inherit from `UndoableCommand`. +The undo/redo mechanism is facilitated by an `UndoRedoStack`, which resides inside `LogicManager`. It supports undoing and redoing of commands that modifies the state of the ProgressChecker (e.g. `add`, `edit`). Such commands will inherit from `UndoableCommand`. `UndoRedoStack` only deals with `UndoableCommands`. Commands that cannot be undone will inherit from `Command` instead. The following diagram shows the inheritance diagram for commands: +.Structure of Commands in the Logic Component image::LogicCommandClassDiagram.png[width="800"] -As you can see from the diagram, `UndoableCommand` adds an extra layer between the abstract `Command` class and concrete commands that can be undone, such as the `DeleteCommand`. Note that extra tasks need to be done when executing a command in an _undoable_ way, such as saving the state of the address book before execution. `UndoableCommand` contains the high-level algorithm for those extra tasks while the child classes implements the details of how to execute the specific command. Note that this technique of putting the high-level algorithm in the parent class and lower-level steps of the algorithm in child classes is also known as the https://www.tutorialspoint.com/design_pattern/template_pattern.htm[template pattern]. +{sp}+ + +As you can see from the diagram, `UndoableCommand` adds an extra layer between the abstract `Command` class and concrete commands that can be undone, such as the `DeleteCommand`. Note that extra tasks need to be done when executing a command in an _undoable_ way, such as saving the state of the ProgressChecker before execution. `UndoableCommand` contains the high-level algorithm for those extra tasks while the child classes implements the details of how to execute the specific command. Note that this technique of putting the high-level algorithm in the parent class and lower-level steps of the algorithm in child classes is also known as the https://www.tutorialspoint.com/design_pattern/template_pattern.htm[template pattern]. Commands that are not undoable are implemented this way: [source,java] @@ -275,616 +330,1786 @@ public class DeleteCommand extends UndoableCommand { Suppose that the user has just launched the application. The `UndoRedoStack` will be empty at the beginning. -The user executes a new `UndoableCommand`, `delete 5`, to delete the 5th person in the address book. The current state of the address book is saved before the `delete 5` command executes. The `delete 5` command will then be pushed onto the `undoStack` (the current state is saved together with the command). +The user executes a new `UndoableCommand`, `delete 5`, to delete the 5th teammate in the ProgressChecker. The current state of the ProgressChecker is saved before the `delete 5` command executes. The `delete 5` command will then be pushed onto the `undoStack` (the current state is saved together with the command). +.Undo/Redo Stack at Starting Point image::UndoRedoStartingStackDiagram.png[width="800"] -As the user continues to use the program, more commands are added into the `undoStack`. For example, the user may execute `add n/David ...` to add a new person. +{sp}+ +As the user continues to use the program, more commands are added into the `undoStack`. For example, the user may execute `add n/David ...` to add a new teammate. + +.Undo/Redo Stack with New Command `add` image::UndoRedoNewCommand1StackDiagram.png[width="800"] +{sp}+ + [NOTE] If a command fails its execution, it will not be pushed to the `UndoRedoStack` at all. -The user now decides that adding the person was a mistake, and decides to undo that action using `undo`. +The user now decides that adding the teammate was a mistake, and decides to undo that action using `undo`. -We will pop the most recent command out of the `undoStack` and push it back to the `redoStack`. We will restore the address book to the state before the `add` command executed. +We will pop the most recent command out of the `undoStack` and push it back to the `redoStack`. We will restore the ProgressChecker to the state before the `add` command executed. +.Undo/Redo Stack with Command `undo` image::UndoRedoExecuteUndoStackDiagram.png[width="800"] +{sp}+ + [NOTE] If the `undoStack` is empty, then there are no other commands left to be undone, and an `Exception` will be thrown when popping the `undoStack`. The following sequence diagram shows how the undo operation works: +.Sequence Diagram of Undo/Redo image::UndoRedoSequenceDiagram.png[width="800"] -The redo does the exact opposite (pops from `redoStack`, push to `undoStack`, and restores the address book to the state after the command is executed). +{sp}+ + +The redo does the exact opposite (pops from `redoStack`, push to `undoStack`, and restores the ProgressChecker to the state after the command is executed). [NOTE] If the `redoStack` is empty, then there are no other commands left to be redone, and an `Exception` will be thrown when popping the `redoStack`. The user now decides to execute a new command, `clear`. As before, `clear` will be pushed into the `undoStack`. This time the `redoStack` is no longer empty. It will be purged as it no longer make sense to redo the `add n/David` command (this is the behavior that most modern desktop applications follow). +.Undo/Redo Stack with New Command `clear` image::UndoRedoNewCommand2StackDiagram.png[width="800"] +{sp}+ + Commands that are not undoable are not added into the `undoStack`. For example, `list`, which inherits from `Command` rather than `UndoableCommand`, will not be added after execution: +.Undo/Redo Stack with Command `list` image::UndoRedoNewCommand3StackDiagram.png[width="800"] +{sp}+ + The following activity diagram summarize what happens inside the `UndoRedoStack` when a user executes a new command: +.Activity Diagram of Undo/Redo image::UndoRedoActivityDiagram.png[width="650"] ==== Design Considerations ===== Aspect: Implementation of `UndoableCommand` -* **Alternative 1 (current choice):** Add a new abstract method `executeUndoableCommand()` -** Pros: We will not lose any undone/redone functionality as it is now part of the default behaviour. Classes that deal with `Command` do not have to know that `executeUndoableCommand()` exist. -** Cons: Hard for new developers to understand the template pattern. -* **Alternative 2:** Just override `execute()` -** Pros: Does not involve the template pattern, easier for new developers to understand. -** Cons: Classes that inherit from `UndoableCommand` must remember to call `super.execute()`, or lose the ability to undo/redo. +|=== +|Alternative | Pros | Cons + +|**Add a new abstract method `executeUndoableCommand()`** + +(current choice) +|We will not lose any undone/redone functionality as it is now part of the default behaviour. Classes that deal with `Command` do not have to know that `executeUndoableCommand()` exist. +|Hard for new developers to understand the template pattern. + +|**Override `execute()`** +|Does not involve the template pattern, easier for new developers to understand. +|Cons: Classes that inherit from `UndoableCommand` must remember to call `super.execute()`, or lose the ability to undo/redo. + +|=== + +{sp}+ ===== Aspect: How undo & redo executes -* **Alternative 1 (current choice):** Saves the entire address book. -** Pros: Easy to implement. -** Cons: May have performance issues in terms of memory usage. -* **Alternative 2:** Individual command knows how to undo/redo by itself. -** Pros: Will use less memory (e.g. for `delete`, just save the person being deleted). -** Cons: We must ensure that the implementation of each individual command are correct. +|=== +|Alternative | Pros | Cons +|**Save the entire ProgressChecker** + +(current choice) +|Easy to implement. +|May have performance issues in terms of memory usage. + +|**Individual command knows how to undo/redo by itself** +|Will use less memory (e.g. for `delete`, just save the teammate being deleted). +|We must ensure that the implementation of each individual command are correct. + +|=== +{sp} + ===== Aspect: Type of commands that can be undone/redone -* **Alternative 1 (current choice):** Only include commands that modifies the address book (`add`, `clear`, `edit`). -** Pros: We only revert changes that are hard to change back (the view can easily be re-modified as no data are * lost). -** Cons: User might think that undo also applies when the list is modified (undoing filtering for example), * only to realize that it does not do that, after executing `undo`. -* **Alternative 2:** Include all commands. -** Pros: Might be more intuitive for the user. -** Cons: User have no way of skipping such commands if he or she just want to reset the state of the address * book and not the view. +|=== +|Alternative | Pros | Cons +|**Only include commands that modifies the ProgressChecker (`add`, `clear`, `edit`)** + +(current choice) +|We only revert changes that are hard to change back (the view can easily be re-modified as no data are * lost). +|User might think that undo also applies when the list is modified (undoing filtering for example), * only to realize that it does not do that, after executing `undo`. + +|**Include all commands** +|Might be more intuitive for the user. +| User have no way of skipping such commands if he or she just want to reset the state of the ProgressChecker and not the view. + +|=== + +[NOTE] **Additional Info:** See our discussion https://github.com/se-edu/addressbook-level4/issues/390#issuecomment-298936672[here]. +{sp} + ===== Aspect: Data structure to support the undo/redo commands -* **Alternative 1 (current choice):** Use separate stack for undo and redo -** Pros: Easy to understand for new Computer Science student undergraduates to understand, who are likely to be * the new incoming developers of our project. -** Cons: Logic is duplicated twice. For example, when a new command is executed, we must remember to update * both `HistoryManager` and `UndoRedoStack`. -* **Alternative 2:** Use `HistoryManager` for undo/redo -** Pros: We do not need to maintain a separate stack, and just reuse what is already in the codebase. -** Cons: Requires dealing with commands that have already been undone: We must remember to skip these commands. Violates Single Responsibility Principle and Separation of Concerns as `HistoryManager` now needs to do two * different things. -// end::undoredo[] - -// tag::dataencryption[] -=== [Proposed] Data Encryption +|=== +|Alternative | Pros | Cons -_{Explain here how the data encryption feature will be implemented}_ +|**Use separate stack for undo and redo** + +(current choice) +|Easy to understand for new Computer Science student undergraduates to understand, who are likely to be * the new incoming developers of our project. +|Logic is duplicated twice. For example, when a new command is executed, we must remember to update * both `HistoryManager` and `UndoRedoStack`. -// end::dataencryption[] +|**Use `HistoryManager` for undo/redo** +|We do not need to maintain a separate stack, and just reuse what is already in the codebase. +|Cons: Requires dealing with commands that have already been undone: We must remember to skip these commands. Violates Single Responsibility Principle and Separation of Concerns as `HistoryManager` now needs to do two * different things. -=== Logging +|=== -We are using `java.util.logging` package for logging. The `LogsCenter` class is used to manage the logging levels and logging destinations. +// end::undoredo[] -* The logging level can be controlled using the `logLevel` setting in the configuration file (See <<Implementation-Configuration>>) -* The `Logger` for a class can be obtained using `LogsCenter.getLogger(Class)` which will log messages according to the specified logging level -* Currently log messages are output through: `Console` and to a `.log` file. +// tag::upload[] +=== Upload feature +==== Planned Implementation -*Logging Levels* +The Upload command will allow users to upload their preferred image to replace the default profile photo. -* `SEVERE` : Critical problem detected which may possibly cause the termination of the application -* `WARNING` : Can continue, but with caution -* `INFO` : Information showing the noteworthy actions by the App -* `FINE` : Details that is not usually noteworthy but may be useful in debugging e.g. print the actual list instead of just its size +The valid photo to be upload will be copies from local path inside resources folder under /images/contact. The name of the file will be renamed according to the time that the photo is uploaded. -[[Implementation-Configuration]] -=== Configuration +Upload can be undoable. The diagram below shows how the `EventsCenter` reacts to `uploadPhoto` event. -Certain properties of the application can be controlled (e.g App name, logging level) through the configuration file (default: `config.json`). +.Component Interactions for `uploadPhoto` Command +image::SDforUploadPhoto.png[width="800"] -== Documentation +{sp} + -We use asciidoc for writing documentation. +UploadCommand is implemented this way: +[source,java] +---- +public class UploadCommand extends UndoableCommand { + @Override + public CommandResult executeUndoableCommand() throws CommandException { + requireNonNull(personToUpdate); + try { + model.addPhoto(photoPath); + model.uploadPhoto(personToUpdate, savePath); + return new CommandResult(MESSAGE_SUCCESS); + } catch (PersonNotFoundException pnfe) { + throw new AssertionError("The target person cannot be missing"); + } catch (DuplicatePhotoException e) { + throw new CommandException(MESSAGE_IMAGE_DUPLICATE); + } catch (DuplicatePersonException e) { + throw new CommandException(MESSAGE_IMAGE_DUPLICATE); + } + } +} +---- [NOTE] -We chose asciidoc over Markdown because asciidoc, although a bit more complex than Markdown, provides more flexibility in formatting. +Users are allowed to reload the image if they want to update the profile photo. -=== Editing Documentation +Here is the code to copy the photo from local path inside resources folder. +[source,java] +---- +public String copyLocalPhoto(String localPath) throws IOException { + File localFile = new File(localPath); + String newPath = createSavePath(localPath); + if (!localFile.exists()) { + throw new FileNotFoundException(MESSAGE_LOCAL_PATH_CONSTRAINTS); + } + createSavedPhoto(newPath); + try { + copyFile(localPath, newPath); + } catch (IOException e) { + throw new IOException(MESSAGE_COPY_FAIL); + } + return newPath; +} +---- -See <<UsingGradle#rendering-asciidoc-files, UsingGradle.adoc>> to learn how to render `.adoc` files locally to preview the end result of your edits. -Alternatively, you can download the AsciiDoc plugin for IntelliJ, which allows you to preview the changes you have made to your `.adoc` files in real-time. +[NOTE] +If the local path is invalid or the image cannot be found, the upload will not be successful. The extension of the file can only be 'jpg', 'jpeg' or 'png'. User will be asked to write the correct path to image again. -=== Publishing Documentation +==== Design Considerations -See <<UsingTravis#deploying-github-pages, UsingTravis.adoc>> to learn how to deploy GitHub Pages using Travis. +===== Aspect: Implementation of `UploadCommand` -=== Converting Documentation to PDF format +|=== +|Alternative | Pros | Cons -We use https://www.google.com/chrome/browser/desktop/[Google Chrome] for converting documentation to PDF format, as Chrome's PDF engine preserves hyperlinks used in webpages. +|**User will provide the path of image** + +(current choice) +|The path can be used directly to find the image and display it in the app. +|Image may be a local file. When other users open the app, they cannot see the update. -Here are the steps to convert the project documentation files to PDF format. +|**User will upload image into our github folder manually** +|Everyone can see the update of profile photo. +|Quite trobulesome to upload photo manually first. -. Follow the instructions in <<UsingGradle#rendering-asciidoc-files, UsingGradle.adoc>> to convert the AsciiDoc files in the `docs/` directory to HTML format. -. Go to your generated HTML files in the `build/docs` folder, right click on them and select `Open with` -> `Google Chrome`. -. Within Chrome, click on the `Print` option in Chrome's menu. -. Set the destination to `Save as PDF`, then click `Save` to save a copy of the file in PDF format. For best results, use the settings indicated in the screenshot below. +|=== +// end::upload[] -.Saving documentation as PDF files in Chrome -image::chrome_save_as_pdf.png[width="300"] +// tag::search[] +=== Dynamic Search Implementation +==== Current Implementation -[[Testing]] -== Testing +The `find` command shows the searched contact currently. However, the user does not need to type the complete name press enter, the whole search is dynamic. As soon as the user types the command `find` dynamic search state is toggled. After typing `find` command, whichever character is entered by the user, the results which contain + the typed keywords appear. -=== Running Tests +To implement the dynamic search, we used the following method - as soon as the user enters any character in the command box, the text is retrieved from +the command box and checked if it is the `find` command. If it is the `find` command, dynamic search is started. After the `find` command is detected in the +command box, every key that is pressed is parsed and sent to the `find` command parser. After that the basic functionality of find is used and the results are displayed. -There are three ways to run tests. +The code snippet for the implementation is: +[source,java] +---- +if ((commandTextField.getText().trim().equalsIgnoreCase(CORRECT_COMMAND_WORD) + || isCorrectCommandWord)) { + isCorrectCommandWord = !commandTextField.getText().trim().isEmpty(); + CommandResult commandResult; + if (keyEvent.getCode() != KeyCode.BACK_SPACE && keyEvent.getCode() != KeyCode.DELETE) { + commandResult = logic.execute(commandTextField.getText() + keyEvent.getText()); + } else { + commandResult = logic.execute(commandTextField.getText().substring(0, + commandTextField.getText().length() - 1)); + } + // process result of the command + logger.info("Result: " + commandResult.feedbackToUser); + raise(new NewResultAvailableEvent(commandResult.feedbackToUser)); + } + } +---- +[NOTE] +The entered key is not instantly updated in the command box thats why after the `commandTextField.getText()` is executed we need to append\delete a character for the + code to the result to process the right input - the one that the user can see on their screens. -[TIP] -The most reliable way to run tests is the 3rd one. The first two methods might fail some GUI tests due to platform/resolution-specific idiosyncrasies. +==== Design consideration +===== Aspect: User Interface (UI) -*Method 1: Using IntelliJ JUnit test runner* +|=== +|Alternative | Pros | Cons -* To run all tests, right-click on the `src/test/java` folder and choose `Run 'All Tests'` -* To run a subset of tests, you can right-click on a test package, test class, or a test and choose `Run 'ABC'` +|**Show the search results without actually highlighting the keywords** + +(current choice) +|Allows more readability of the of the results as they contain multiple fields and not just user name. +|User needs to manually search for the keywords entered by him in the search results. -*Method 2: Using Gradle* +|**Show the search results WITH highlighting the keywords in the searched name** + +|It will make it easier for the user to view the user to identify the searched keyword in the displayed results. +|Adding highlights to the results might make the displayed results a bit too cluttered specially with the presence of tags which are colored as well. -* Open a console and run the command `gradlew clean allTests` (Mac/Linux: `./gradlew clean allTests`) +|=== +// end::search[] -[NOTE] -See <<UsingGradle#, UsingGradle.adoc>> for more info on how to run tests using Gradle. +// tag::view[] +=== Toggling between tab views +==== Current Implementation -*Method 3: Using Gradle (headless)* +This command toggles the view between the different type of tabs in the software. + -Thanks to the https://github.com/TestFX/TestFX[TestFX] library we use, our GUI tests can be run in the _headless_ mode. In the headless mode, GUI tests do not show up on the screen. That means the developer can do other things on the Computer while the tests are running. +.Reference of the tab view in the software +image::TabView.png[width="600"] -To run tests in headless mode, open a console and run the command `gradlew clean headless allTests` (Mac/Linux: `./gradlew clean headless allTests`) +{sp} + -=== Types of tests +It inherits `Command` and executes on an _Event Driven_ design between the Logic and UI component. -We have two types of tests: +Suppose that the user is on the `Task` tab and wants to toggle to the `Exercise` tab. The user executes a new `Command`, `view exercise`, to switch to the `Exercise` tab. The _Sequence Diagram_ below shows how the components interact with each other. -. *GUI Tests* - These are tests involving the GUI. They include, -.. _System Tests_ that test the entire App by simulating user actions on the GUI. These are in the `systemtests` package. -.. _Unit tests_ that test the individual components. These are in `seedu.address.ui` package. -. *Non-GUI Tests* - These are tests not involving the GUI. They include, -.. _Unit tests_ targeting the lowest level methods/classes. + -e.g. `seedu.address.commons.StringUtilTest` -.. _Integration tests_ that are checking the integration of multiple code units (those code units are assumed to be working). + -e.g. `seedu.address.storage.StorageManagerTest` -.. Hybrids of unit and integration tests. These test are checking multiple code units as well as how the are connected together. + -e.g. `seedu.address.logic.LogicManagerTest` +.Logic and UI component interaction for `view exercise` command (part 1) +image::SDforViewExercise.png[width="600"] +{sp} + -=== Troubleshooting Testing -**Problem: `HelpWindowTest` fails with a `NullPointerException`.** +[NOTE] +Note how the `Logic` simply raises a `TabLoadChangedEvent` when the `view` command gets executed. The `TabLoadChangedEvent` is implemented as follows: +[source,java] +---- +public class TabLoadChangedEvent extends BaseEvent { + public final String type; -* Reason: One of its dependencies, `UserGuide.html` in `src/main/resources/docs` is missing. -* Solution: Execute Gradle task `processResources`. + public TabLoadChangedEvent(String type) { + this.type = type; + } -== Dev Ops + @Override + public String toString() { + return this.getClass().getSimpleName(); + } -=== Build Automation + public String getTabName() { + return type; + } +} +---- -See <<UsingGradle#, UsingGradle.adoc>> to learn how to use Gradle for build automation. +The diagram below shows how the `EventCenter` reacts to that event, which eventually results in the UI updating to which tab view is to be in selection. -=== Continuous Integration +.Logic and UI component interaction for `view exercise` command (part 2) +image::SDforViewExerciseEventHandling.png[width="600"] -We use https://travis-ci.org/[Travis CI] and https://www.appveyor.com/[AppVeyor] to perform _Continuous Integration_ on our projects. See <<UsingTravis#, UsingTravis.adoc>> and <<UsingAppVeyor#, UsingAppVeyor.adoc>> for more details. +{sp} + -=== Coverage Reporting +[NOTE] +The UI scene's elements are automatically populated in `MainWindow.java` due to using JavaFX FXML Controller. That is, a reference to a particular UI element will be available as long as it has its `fx:id` specified in `MainWindow.fxml`. -We use https://coveralls.io/[Coveralls] to track the code coverage of our projects. See <<UsingCoveralls#, UsingCoveralls.adoc>> for more details. +The code snippet below shows how the UI component executes the toggling of tab view upon receiving the event change. +[source,java] +---- +@Subscribe +private void handleTabLoadChangedEvent(TabLoadChangedEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + SingleSelectionModel<Tab> selectionModel = tabPlaceholder.getSelectionModel(); + switch (event.getTabName()) { + case "profile": + selectionModel.select(profilePlaceholder); + break; + case "task": + selectionModel.select(taskPlaceholder); + break; + case "exercise": + selectionModel.select(exercisePlaceholder); + break; + case "issues": + selectionModel.select(issuePlaceholder); + break; + default: + selectionModel.select(selectionModel.getSelectedItem()); + } +} +---- +// end::view[] -=== Documentation Previews -When a pull request has changes to asciidoc files, you can use https://www.netlify.com/[Netlify] to see a preview of how the HTML version of those asciidoc files will look like when the pull request is merged. See <<UsingNetlify#, UsingNetlify.adoc>> for more details. +// tag::answer[] +=== View, answer, and save for an exercise +==== Current Implementation -=== Making a Release +This command allows user to answer an exercise based on the question index shown in the software. -Here are the steps to create a new release. +.Reference of the question index in the software +image::QuestionIndexIndication.png[width="300"] -. Update the version number in link:{repoURL}/src/main/java/seedu/address/MainApp.java[`MainApp.java`]. -. Generate a JAR file <<UsingGradle#creating-the-jar-file, using Gradle>>. -. Tag the repo with the version number. e.g. `v0.1` -. https://help.github.com/articles/creating-releases/[Create a new release using GitHub] and upload the JAR file you created. +{sp} + -=== Managing Dependencies +It inherits `UndoableCommand` and executes through all four components in the code base. -A project often depends on third-party libraries. For example, Address Book depends on the http://wiki.fasterxml.com/JacksonHome[Jackson library] for XML parsing. Managing these _dependencies_ can be automated using Gradle. For example, Gradle can download the dependencies automatically, which is better than these alternatives. + -a. Include those libraries in the repo (this bloats the repo size) + -b. Require developers to download those libraries manually (this creates extra work for developers) +Suppose that the user wants to answer an exercise with index 11.1.1. The user executes a new `Command`, `ans 11.1.1 a`, to answer the exercise. The Sequence Diagram below shows how the components interact with each other. -[[GetStartedProgramming]] -[appendix] -== Suggested Programming Tasks to Get Started +.Component interactions for `ans 11.1.1 a` command (part 1) +image::SDforAnswerExercise.png[width="800"] -Suggested path for new programmers: +{sp}+ -1. First, add small local-impact (i.e. the impact of the change does not go beyond the component) enhancements to one component at a time. Some suggestions are given in <<GetStartedProgramming-EachComponent>>. +[NOTE] +Note how the `Model` simply raises a `ProgressCheckerChangedEvent` when the ProgressChecker data has been changed, instead of asking the `Storage` to save the updates to the hard disk. -2. Next, add a feature that touches multiple components to learn how to implement an end-to-end feature across all components. <<GetStartedProgramming-RemarkCommand>> explains how to go about adding such a feature. +{sp} + -[[GetStartedProgramming-EachComponent]] -=== Improving each component +The diagram below shows how the `EventsCenter` reacts to that event, which eventually results in the updates being saved to the hard disk. -Each individual exercise in this section is component-based (i.e. you would not need to modify the other components to get it to work). +.Component interactions for `ans 11.1.1 a` command (part 2) +image::SDforAnswerExerciseEventHandling.png[width="800"] -[discrete] -==== `Logic` component +{sp} + -*Scenario:* You are in charge of `logic`. During dog-fooding, your team realize that it is troublesome for the user to type the whole command in order to execute a command. Your team devise some strategies to help cut down the amount of typing necessary, and one of the suggestions was to implement aliases for the command words. Your job is to implement such aliases. +Before the `Logic` component executes the `Undoable Command` which calls the `Model`, it prepares the exercise that needs to be updated by going through the internal list of exercises stored in model. The code that searches for the exercise is as follows: +[source,java] +---- +for (Exercise e : exerciseList) { + if (e.getQuestionIndex().toString().equals(questionIndex.toString())) { + exerciseToEdit = exerciseList.get(exerciseList.indexOf(e)); + editedExercise = createEditedExercise(exerciseToEdit, studentAnswer); + isFound = true; + break; + } +} +---- -[TIP] -Do take a look at <<Design-Logic>> before attempting to modify the `Logic` component. +The internal list, `exerciseList`, is implemented as an observable list of filtered exercises in `Model`. Upon calling the `ProgressCheckerChangedEvent`, `Storage` will run `saveProgressChecker`. Subsequently, to load the data on the next software start up requires parsing of xml data into `Model`. The following code snippet shows how `Storage` does so: +[source,java] +---- +public Exercise toModelType() throws IllegalValueException { + if (this.questionIndex == null) { + throw new IllegalValueException( + String.format(MISSING_FIELD_MESSAGE_FORMAT, + QuestionIndex.class.getSimpleName())); + } + if (!QuestionIndex.isValidIndex(this.questionIndex)) { + throw new IllegalValueException(QuestionIndex.MESSAGE_INDEX_CONSTRAINTS); + } + final QuestionIndex questionIndex = new QuestionIndex(this.questionIndex); -. Add a shorthand equivalent alias for each of the individual commands. For example, besides typing `clear`, the user can also type `c` to remove all persons in the list. -+ -**** -* Hints -** Just like we store each individual command word constant `COMMAND_WORD` inside `*Command.java` (e.g. link:{repoURL}/src/main/java/seedu/address/logic/commands/FindCommand.java[`FindCommand#COMMAND_WORD`], link:{repoURL}/src/main/java/seedu/address/logic/commands/DeleteCommand.java[`DeleteCommand#COMMAND_WORD`]), you need a new constant for aliases as well (e.g. `FindCommand#COMMAND_ALIAS`). -** link:{repoURL}/src/main/java/seedu/address/logic/parser/AddressBookParser.java[`AddressBookParser`] is responsible for analyzing command words. -* Solution -** Modify the switch statement in link:{repoURL}/src/main/java/seedu/address/logic/parser/AddressBookParser.java[`AddressBookParser#parseCommand(String)`] such that both the proper command word and alias can be used to execute the same intended command. -** Add new tests for each of the aliases that you have added. -** Update the user guide to document the new aliases. -** See this https://github.com/se-edu/addressbook-level4/pull/785[PR] for the full solution. -**** + if (this.questionType == null) { + throw new IllegalValueException( + String.format(MISSING_FIELD_MESSAGE_FORMAT, + QuestionType.class.getSimpleName())); + } + if (!QuestionType.isValidType(this.questionType)) { + throw new IllegalValueException(QuestionType.MESSAGE_TYPE_CONSTRAINTS); + } + final QuestionType questionType = new QuestionType(this.questionType); -[discrete] -==== `Model` component + ... -*Scenario:* You are in charge of `model`. One day, the `logic`-in-charge approaches you for help. He wants to implement a command such that the user is able to remove a particular tag from everyone in the address book, but the model API does not support such a functionality at the moment. Your job is to implement an API method, so that your teammate can use your API to implement his command. + return new Exercise(questionIndex, questionType, question, studentAnswer, modelAnswer); +} +---- -[TIP] -Do take a look at <<Design-Model>> before attempting to modify the `Model` component. +Additionally, since it is an observable list, the UI element harboring this list will update any changes made to this list accordingly. In viewing of exercises by week, the list is filtered with predicate as follows: +[source,java] +---- +model.updateFilteredExerciseList(exercise -> exercise.getQuestionIndex().getWeekNumber() + == editedExercise.getQuestionIndex().getWeekNumber()); +---- -. Add a `removeTag(Tag)` method. The specified tag will be removed from everyone in the address book. -+ -**** -* Hints -** The link:{repoURL}/src/main/java/seedu/address/model/Model.java[`Model`] and the link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`] API need to be updated. -** Think about how you can use SLAP to design the method. Where should we place the main logic of deleting tags? -** Find out which of the existing API methods in link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`] and link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`] classes can be used to implement the tag removal logic. link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`] allows you to update a person, and link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`] allows you to update the tags. -* Solution -** Implement a `removeTag(Tag)` method in link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`]. Loop through each person, and remove the `tag` from each person. -** Add a new API method `deleteTag(Tag)` in link:{repoURL}/src/main/java/seedu/address/model/ModelManager.java[`ModelManager`]. Your link:{repoURL}/src/main/java/seedu/address/model/ModelManager.java[`ModelManager`] should call `AddressBook#removeTag(Tag)`. -** Add new tests for each of the new public methods that you have added. -** See this https://github.com/se-edu/addressbook-level4/pull/790[PR] for the full solution. -*** The current codebase has a flaw in tags management. Tags no longer in use by anyone may still exist on the link:{repoURL}/src/main/java/seedu/address/model/AddressBook.java[`AddressBook`]. This may cause some tests to fail. See issue https://github.com/se-edu/addressbook-level4/issues/753[`#753`] for more information about this flaw. -*** The solution PR has a temporary fix for the flaw mentioned above in its first commit. -**** +==== Design Considerations +===== Aspect: Viewing of exercises by week -[discrete] -==== `Ui` component +|=== +|Alternative | Pros | Cons -*Scenario:* You are in charge of `ui`. During a beta testing session, your team is observing how the users use your address book application. You realize that one of the users occasionally tries to delete non-existent tags from a contact, because the tags all look the same visually, and the user got confused. Another user made a typing mistake in his command, but did not realize he had done so because the error message wasn't prominent enough. A third user keeps scrolling down the list, because he keeps forgetting the index of the last person in the list. Your job is to implement improvements to the UI to solve all these problems. +|**Adapt from `View Command` by adding additional WEEK_NUMBER parameter to type `exercise`** + +(current choice) +|Not required to create a new command and hence more cohesive with the existing commands as well as one less command for users to learn +|`ViewCommandParser` requires additional parser check to separate between the `View Command` that can take in WEEK_NUMBER to one that doesn't which might violate SLAP principle -[TIP] -Do take a look at <<Design-Ui>> before attempting to modify the `UI` component. +|**Create a new command to list exercises by week** + +|Standalone from existing commands and hence easier to be built upon or removed without consequences +|Creates an extra unnecessary complication for users having to learn a new command when the existing `View Command` essentially does something similar -. Use different colors for different tags inside person cards. For example, `friends` tags can be all in brown, and `colleagues` tags can be all in yellow. -+ -**Before** -+ -image::getting-started-ui-tag-before.png[width="300"] -+ -**After** -+ -image::getting-started-ui-tag-after.png[width="300"] -+ -**** -* Hints -** The tag labels are created inside link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[the `PersonCard` constructor] (`new Label(tag.tagName)`). https://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/Label.html[JavaFX's `Label` class] allows you to modify the style of each Label, such as changing its color. -** Use the .css attribute `-fx-background-color` to add a color. -** You may wish to modify link:{repoURL}/src/main/resources/view/DarkTheme.css[`DarkTheme.css`] to include some pre-defined colors using css, especially if you have experience with web-based css. -* Solution -** You can modify the existing test methods for `PersonCard` 's to include testing the tag's color as well. -** See this https://github.com/se-edu/addressbook-level4/pull/798[PR] for the full solution. -*** The PR uses the hash code of the tag names to generate a color. This is deliberately designed to ensure consistent colors each time the application runs. You may wish to expand on this design to include additional features, such as allowing users to set their own tag colors, and directly saving the colors to storage, so that tags retain their colors even if the hash code algorithm changes. -**** +|=== -. Modify link:{repoURL}/src/main/java/seedu/address/commons/events/ui/NewResultAvailableEvent.java[`NewResultAvailableEvent`] such that link:{repoURL}/src/main/java/seedu/address/ui/ResultDisplay.java[`ResultDisplay`] can show a different style on error (currently it shows the same regardless of errors). -+ -**Before** -+ -image::getting-started-ui-result-before.png[width="200"] -+ -**After** -+ -image::getting-started-ui-result-after.png[width="200"] -+ -**** -* Hints -** link:{repoURL}/src/main/java/seedu/address/commons/events/ui/NewResultAvailableEvent.java[`NewResultAvailableEvent`] is raised by link:{repoURL}/src/main/java/seedu/address/ui/CommandBox.java[`CommandBox`] which also knows whether the result is a success or failure, and is caught by link:{repoURL}/src/main/java/seedu/address/ui/ResultDisplay.java[`ResultDisplay`] which is where we want to change the style to. -** Refer to link:{repoURL}/src/main/java/seedu/address/ui/CommandBox.java[`CommandBox`] for an example on how to display an error. -* Solution -** Modify link:{repoURL}/src/main/java/seedu/address/commons/events/ui/NewResultAvailableEvent.java[`NewResultAvailableEvent`] 's constructor so that users of the event can indicate whether an error has occurred. -** Modify link:{repoURL}/src/main/java/seedu/address/ui/ResultDisplay.java[`ResultDisplay#handleNewResultAvailableEvent(NewResultAvailableEvent)`] to react to this event appropriately. -** You can write two different kinds of tests to ensure that the functionality works: -*** The unit tests for `ResultDisplay` can be modified to include verification of the color. -*** The system tests link:{repoURL}/src/test/java/systemtests/AddressBookSystemTest.java[`AddressBookSystemTest#assertCommandBoxShowsDefaultStyle() and AddressBookSystemTest#assertCommandBoxShowsErrorStyle()`] to include verification for `ResultDisplay` as well. -** See this https://github.com/se-edu/addressbook-level4/pull/799[PR] for the full solution. -*** Do read the commits one at a time if you feel overwhelmed. -**** +===== Aspect: Loading of exercises data on fresh start -. Modify the link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter`] to show the total number of people in the address book. -+ -**Before** -+ -image::getting-started-ui-status-before.png[width="500"] -+ -**After** -+ -image::getting-started-ui-status-after.png[width="500"] -+ -**** -* Hints -** link:{repoURL}/src/main/resources/view/StatusBarFooter.fxml[`StatusBarFooter.fxml`] will need a new `StatusBar`. Be sure to set the `GridPane.columnIndex` properly for each `StatusBar` to avoid misalignment! -** link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter`] needs to initialize the status bar on application start, and to update it accordingly whenever the address book is updated. -* Solution -** Modify the constructor of link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter`] to take in the number of persons when the application just started. -** Use link:{repoURL}/src/main/java/seedu/address/ui/StatusBarFooter.java[`StatusBarFooter#handleAddressBookChangedEvent(AddressBookChangedEvent)`] to update the number of persons whenever there are new changes to the addressbook. -** For tests, modify link:{repoURL}/src/test/java/guitests/guihandles/StatusBarFooterHandle.java[`StatusBarFooterHandle`] by adding a state-saving functionality for the total number of people status, just like what we did for save location and sync status. -** For system tests, modify link:{repoURL}/src/test/java/systemtests/AddressBookSystemTest.java[`AddressBookSystemTest`] to also verify the new total number of persons status bar. -** See this https://github.com/se-edu/addressbook-level4/pull/803[PR] for the full solution. -**** +|=== +|Alternative | Pros | Cons -[discrete] -==== `Storage` component +|**Include all exercises data in `SampleDataUtil` and read from there** + +(current choice) +|No additional processing required, is easy to modify whenever default data needs to be changed +|Is directly affected by the `Clear Command` that is meant for the list of `Persons` which user may not expect it to be for -*Scenario:* You are in charge of `storage`. For your next project milestone, your team plans to implement a new feature of saving the address book to the cloud. However, the current implementation of the application constantly saves the address book after the execution of each command, which is not ideal if the user is working on limited internet connection. Your team decided that the application should instead save the changes to a temporary local backup file first, and only upload to the cloud after the user closes the application. Your job is to implement a backup API for the address book storage. +|**Read from stored text file, parse accordingly, and load into software on fresh start** + +|Standalone data and will not be affected by changes made to list of `Persons` +|Incurs extra overhead when parsing the text file into Java objects + +|=== +// end::answer[] + +// tag::tasks[] +=== Tasks +==== Current Implementation + +The default LOs for all weeks would be stored in a local file, which will be loaded as input to create a task list on the +user's Google Account with Google Tasks API. + +There are several commands related to tasks, including `newtasklist` to add and upload the default task list, `viewtasklist FILTER_KEYWORD` +to view the default task list with filtering, `completetask INDEX`/`resettask INDEX` to mark a task as completed/not completed, +and `goto INDEX` to open the URL of a task. As an example, the _High Level Sequence Diagram_ and _Sequence Diagram_ below shows how the components interact +for the scenario where the user issues the command `viewtasklist 5`. + +.Component Interactions for `viewtasklist 5` Command (High Level) +image::HighLevelSDforViewTaskListCommand.png[width="800"] + +{sp} + + +.Component Interactions for `viewtasklist 5` Command +image::SDforViewTaskListCommand.png[width="800"] + +{sp} + + +We apply Google Tasks API to help us save user tasks data online. This offers back up data which allow our users to recover +their tasks and status of each task even after uninstalling the application. The task list will be ready to display once the user +reinstall and open the application. +To use Google Tasks API, we fist need to register this project on google developer console and retrieve a client credential file (client_id.json) to authorize our project. +Then, add corresponding dependencies to build.gradle, the library files will be downloaded automatically upon project rebuild. + +Here is the code snippet to add dependencies: +[source,java] +---- +compile ( + ['com.google.api-client:google-api-client:1.23.0'], + ['com.google.apis:google-api-services-tasks:v1-rev49-1.23.0'], + ['com.google.oauth-client:google-oauth-client-jetty:1.23.0'], +) +---- +[NOTE] +Simply downloading JAR files without editing gradle is not suggested. JARs are not in git thus our co-developers will rely on the dependencies to retrieve the libraries. +Also, set gradleVersion to 4.6 if it is an older version, otherwise runtime compilation of Google API library will affect Junit tests. + +We write a program to authorize our project (by loading the aforementioned client credential file), trigger user loggin and build service. +Note that when users are using ProgressChecker, only the first tasks command requires them to log in and authorize ProgressChecker to access +their Google Tasks data with their google accounts. + +Google Tasks API helps us save time building massive data structures (ie. Tasks, TaskLists, Lists of TaskLists, as well as many methods and exceptions). +However, we do have a few classes (eg. TaskUtil, TaskListUtil) in the modeling part that further add customized methods which are useful for current commands and even future commands. +In this way, we avoid repetition of code snippet and having big chunks of import statements in numerous commands. + +Here is a code snippet that can find a task list by its title (while the native method only finds task by its id which is not memorable or even known by our users): +[source,java] +---- +/** + * Finds the task list with title {@code String listTitle} from the user's task lists + * + * @param listTitle title of the task list we look for + * @return the task list instance + */ +public static TaskList searchTaskList(String listTitle) throws CommandException { + TaskList taskList = null; + ConnectTasksApi connection = new ConnectTasksApi(); + try { + connection.authorize(); + } catch (Exception e) { + throw new CommandException(AUTHORIZE_FAILURE); + } + Tasks service = connection.getTasksService(); + try { + TaskLists taskLists = service.tasklists().list().execute(); + taskList = taskLists.getItems().stream() + .filter(t -> t.getTitle().equals(listTitle)) + .findFirst() + .orElse(null); + } catch (IOException ioe) { + throw new CommandException(LOAD_FAILURE); + } + + return taskList; +} +---- + +==== Design Considerations + +===== Aspect: Implementation of tasks commands + +* All these commands extend `Command` but not extend `UndoableCommand`. `AddDefaultTasksCommand`, `CompleteTaskCommand` and `ResetTaskCommand` make + external changes that update task list in users' Google account, which is out of the scope of undo command. `ViewTaskListCommand` and + `GoToTaskUrlCommand` do not make changes to the data, thus no applicable to undo command. + +===== Aspect: How `AddDefaultTasksCommand` is executed + +* This command will load the tasks from local storage and add a task list filled with these tasks to the user's Google account. +|=== +|Alternative | Pros | Cons +|**Find the user's Google task list with ID "@default" (this is the default task list in Google Task and not removable). Create a new +task list and transfer the tasks from @default to the new one. Then change the title of @default to "CS2103 LOs", and add the tasks +loaded from local storage** + +(current choice) +|The other tasks commands will only need to refer to the ID "@default" to find the task list, which is faster and more accurate than searching with title ( +as list ID is unique while list title can duplicate and the native API method only supports finding list with ID). +|It requires more steps, thus slower (but fortunately this command should only be executed ONCE in the lifetime of this application). + +|**Create a new list with title "CS2103 LOs", then load and push all tasks from local storage** +|Will be a bit faster. +|The other task commands will be slower since they will be finding the list with title. The commands may also encounter error if +there are task list with the same name in the user's Google account. + +|=== + +{sp} + + +===== Aspect: How `ViewTaskListCommand` is executed + +* This command will load the tasks from task list @default from the user's Google account and apply user-specified filter before displaying +|=== +|Alternative | Pros | Cons +|**Find the user's @default task list and load the whole list. Then apply user-specified filter to select applicable +tasks to form a new list. The new list will be ready to be displayed** + +(current choice) +|Easy to implement, well modularized. +|More repetitions of list traversal. + +|**Find the user's @default task list and load the whole list. Then apply user-specified filter to select applicable + tasks while processing the methods to display it** +|Easy to implement. +|Might lead to complicated methods to display list (eg. multi-level abstraction). + +|=== + +{sp} + + +===== Aspect: How `CompleteTaskCommand` and `ResetTaskCommand` is executed + +* This command will set the task with user-input index number as completed/ not completed. +|=== +|Alternative | Pros | Cons +|**Find the user's @default task list and retrieve the task with user-input index number. Check if it needs update, and update it +if necessary.** + +(current choice) +|Easy to implement. +| + +|=== + +{sp} + + +===== Aspect: How `GoToTaskUrl` is executed + +* This command will open the URL of the task with the user-input index number. +|=== +|Alternative | Pros | Cons +|**Find the user's @default task list and retrieved the task based on the input index. Get the URL in the task object and open it in +the browser panel** + +(current choice) +|Easy to implement. +| + +|**No need for implementation, the user can click the hyperlink while viewing the task list** +|No need for implementation. +|Not command line based. + +|=== + +{sp} + + +===== Aspect: What UI structure to show the task list + +|=== +|Alternative | Pros | Cons +|**Use a browser panel.** + +(current choice) +|Can show task list and external websites linked to tasks in the same panel. +|Exercise list, issue list and person list are all shown in a list panel. The handling of browser panel and list panel is different, +which leads to inconsistency. + +|**Use a list panel to display tasks, and a browser panel to display external webpages** +|Guarantees consistency between task list, exercise list, issue list and person list. +|Takes more space. + +|=== + +{sp} + + +===== Aspect: What can we improve / what command can we add in v2.0 + +* Send reminder email to the user when a deadline is near +* Back/Forward the browser panel +* View teammates' task list and progress (Google Tasks does not support it. Thus, a possible implementation is to sync data +with the help of Google Drive API. After every transaction with Google Tasks, we retrieve the task list and save in Google Drive. +Students in the same team will use a shared folder on Google Drive, thus can access each other's task list data. ProgressChecker will +retrieve teammate task list data from the shared folder in Google Drive). + +{sp} + +// end::tasks[] + +// tag::github[] + +=== Github Login +==== Current Implemetation + +The `GitDetails` represents an object that is used to authenticate github. It contains `Username`, `Passcode`, and `Repository` object which represent the github account's username, password and repository respectively. +[NOTE] +All fields are compulsory for github authentication. +.UML diagram for github details +image::gitdetails.png[width="800"] + + +`GitDetails` object is not stored locally as it can violate user's data and privacy. +To manage the github account following command classes can be used: +**** +* GitLoginCommand +* GitLogoutCommand +**** + +`GitLoginCommand` needs to be used for tracking any issue activity on the ProgressChecker application. After the `GitDetails` object is created, its member's are used to create a `Github` object from the Github API library which is used to authenticate github. + +Implementation of github login and issue tracking is done with the help of GitHub API for Java (org.kohsuke.github). +==== Logging into github + +User can log into github after using the `GitLoginCommand`. After executing the command, a `GitDetails` object is created + +Given below is a sequence diagram for authenticating github. + +.UML Diagram for Github details +image::gitlogin1.png[width="800"] + + +The following code snippet shows how GitLoginCommand#execute() will update the model by creating `Github` object which will be used to authenticate github. + +---- +public class GitLoginCommand extends Command { +@Override + public CommandResult execute() throws CommandException { + + try { + model.loginGithub(toAuthenticate); + return new CommandResult(MESSAGE_SUCCESS); + } catch (IOException e) { + throw new CommandException(MESSAGE_FAILURE); + } catch (CommandException ce) { + throw new CommandException(ce.getMessage()); + } + } +} +---- + +==== Design considerations + +===== Aspect: Using password for authentication + +|=== +|Alternative | Pros | Cons +|**Used github password for authentication** + +(current choice) +|User easily remebers his password, thus logging in is easy. +|Password cannot be stored offline to protect users data and privacy. + +|**Using OAuth token for authentication** +|OAuth token can be stored offline which can provide one-time login functionality, as we can restrict the token's usage for only ProgressChecker application. +|Manually generating a token by the user is a tedious task and github tokens expire regularly which can be a pain for the user. +|=== +=== Github Issue Tracker +==== Current Implementation +The `Issue` object represents an issue that is to be created on github. It contains `Title`, `Assignees`, `Milestone`, `Body`, and `Labels` which are the different attributes of an issue on github. +[NOTE] +Only the `Title` field is compulsory for `Issue` as this the only limitation set by github. + +.UML diagram for github Issue +image::issueobject.png[width="800"] + + +`Issue` objects are not stored in memory after an issue is created on github. The issues are not stored in a local file to protect users confidential data and privacy. + +Issue tracking is done by several command classes, namely: +**** +* CreateIssueCommand +* CloseIssueCommand +* EditIssueCommand +* ReopenIssueCommand +* ListIssueCommand +**** + +All the above commands will only work after you have logged into github. Use 'gitLogin' command to login. + +==== Creating an issue +An issue is created on github using the CreateIssueCommand. After executing the command, an `Issue` object is created which is then converted to a `GHIssue` object present in the Github Library. GHIssue is then posted online using the Github API library. + +Given below is the sequence diagram for creating an issue on github. + +.High level sequence diagram for creating a new issue on github +image::finalcreateIssue.png[width="800"] + + +The following code snippet shows how `CreateIssueCommand#execute()` will update the model of the application by creating an issue `toCreate` on github and later updating the `GitIssueList`. +Note: This an issue will not be created if you haven't logged into github. + +---- +public class CloseIssueCommandTest { +... +@Override + public CommandResult execute() throws CommandException { + + try { + model.createIssueOnGitHub(toCreate); + return new CommandResult(MESSAGE_SUCCESS); + } catch (IOException | CommandException e) { + throw new CommandException(MESSAGE_FAILURE); + } + } + ... +} +---- + +The issue created will be shown on the `Issues` tab in the application. + +==== Closing an issue + +An issue can be closed on github using the CloseIssueCommand. After executing the command, a `GHIssue` object of the specified index is retrieved from the Github database. The state of the GHIssue is checked and it is marked as closed if it is open. + +Given below is the sequence diagram for closing an issue. + +.High Level Sequence Diagram for closing an issue on github +image::closeissue.png[width="800"] + +The following code snippet shows how `CloseIssueCommand#execute()` will update the model of application by closing an issue updating the `GitIssueList`. +Note: The entered index number should be a valid issue index, and the user should be logged into github before using the command. + +---- +public class CloseIssueCommand extends Command { +@Override + public CommandResult execute() throws CommandException { + try { + model.closeIssueOnGithub(targetIndex); + } catch (IOException ie) { + throw new CommandException(MESSAGE_FAILURE); + } catch (CommandException ce) { + throw new CommandException(MESSAGE_AUTHENTICATION_FAILURE); + } + + return new CommandResult(String.format(MESSAGE_SUCCESS, targetIndex.getOneBased())); + } +} +---- +The issue created will be removed from the `Issues` tab in the application, as by default only open issues are displayed. + +==== Design considerations + +===== Aspect: Storing issues on a local file + +|=== +|Alternative | Pros | Cons +|**Not storing the issues offline** + +(current choice) +|Users data and privacy is protected, as issues on github might contain very confidential data regarding the product's information. +|The user cannot view the exisitng issues offline and he can not use the software to work offline and then push everything online once the internet connection is available. + +|**Implementing data encryption so that the issues can be stored offline** +|User will be able work offline on issues and post changes when internet connection is availabe. +|In order to do offline authentication and decrypt the issue data, the application will have to store the user credentials offline which might violate Github's API policy. +|=== + +// end::github[] + +=== Logging + +We are using `java.util.logging` package for logging. The `LogsCenter` class is used to manage the logging levels and logging destinations. + +* The logging level can be controlled using the `logLevel` setting in the configuration file (See <<Implementation-Configuration>>) +* The `Logger` for a class can be obtained using `LogsCenter.getLogger(Class)` which will log messages according to the specified logging level +* Currently log messages are output through: `Console` and to a `.log` file. + +*Logging Levels* + +* `SEVERE` : Critical problem detected which may possibly cause the termination of the application +* `WARNING` : Can continue, but with caution +* `INFO` : Information showing the noteworthy actions by the App +* `FINE` : Details that is not usually noteworthy but may be useful in debugging e.g. print the actual list instead of just its size + +[[Implementation-Configuration]] +=== Configuration + +Certain properties of the application can be controlled (e.g App name, logging level) through the configuration file (default: `config.json`). + +== Documentation + +We use asciidoc for writing documentation. This section talks about how you can modify and publish the existing documentations. + +[NOTE] +We chose asciidoc over Markdown because asciidoc, although a bit more complex than Markdown, provides more flexibility in formatting. + +=== Editing Documentation + +See <<UsingGradle#rendering-asciidoc-files, UsingGradle.adoc>> to learn how to render `.adoc` files locally to preview the end result of your edits. +Alternatively, you can download the AsciiDoc plugin for IntelliJ, which allows you to preview the changes you have made to your `.adoc` files in real-time. + +=== Publishing Documentation + +See <<UsingTravis#deploying-github-pages, UsingTravis.adoc>> to learn how to deploy GitHub Pages using Travis. + +=== Converting Documentation to PDF format + +We use https://www.google.com/chrome/browser/desktop/[Google Chrome] for converting documentation to PDF format, as Chrome's PDF engine preserves hyperlinks used in webpages. + +Here are the steps to convert the project documentation files to PDF format. + +. Follow the instructions in <<UsingGradle#rendering-asciidoc-files, UsingGradle.adoc>> to convert the AsciiDoc files in the `docs/` directory to HTML format. +. Go to your generated HTML files in the `build/docs` folder, right click on them and select `Open with` -> `Google Chrome`. +. Within Chrome, click on the `Print` option in Chrome's menu. +. Set the destination to `Save as PDF`, then click `Save` to save a copy of the file in PDF format. For best results, use the settings indicated in the screenshot below. + +.Saving documentation as PDF files in Chrome +image::chrome_save_as_pdf.png[width="300"] + +[[Testing]] +== Testing + +ProgressChecker uses JUnit tests to check for its correctness. This section covers the type of tests and how to run them. + +=== Types of tests + +We have two types of tests: + +. *GUI Tests* - These are tests involving the GUI. They include, +.. _System Tests_ that test the entire App by simulating user actions on the GUI. These are in the `systemtests` package. +.. _Unit tests_ that test the individual components. These are in `seedu.progresschecker.ui` package. +. *Non-GUI Tests* - These are tests not involving the GUI. They include, +.. _Unit tests_ targeting the lowest level methods/classes. + +e.g. `seedu.progresschecker.commons.StringUtilTest` +.. _Integration tests_ that are checking the integration of multiple code units (those code units are assumed to be working). + +e.g. `seedu.progresschecker.storage.StorageManagerTest` +.. Hybrids of unit and integration tests. These test are checking multiple code units as well as how the are connected together. + +e.g. `seedu.progresschecker.logic.LogicManagerTest` + + +=== Troubleshooting Testing +**Problem: `HelpWindowTest` fails with a `NullPointerException`.** + +* Reason: One of its dependencies, `UserGuide.html` in `src/main/resources/docs` is missing. +* Solution: Execute Gradle task `processResources`. + +== Dev Ops + +=== Build Automation + +See <<UsingGradle#, UsingGradle.adoc>> to learn how to use Gradle for build automation. + +=== Continuous Integration + +We use https://travis-ci.org/[Travis CI] and https://www.appveyor.com/[AppVeyor] to perform _Continuous Integration_ on our projects. See <<UsingTravis#, UsingTravis.adoc>> and <<UsingAppVeyor#, UsingAppVeyor.adoc>> for more details. + +=== Coverage Reporting + +We use https://coveralls.io/[Coveralls] to track the code coverage of our projects. See <<UsingCoveralls#, UsingCoveralls.adoc>> for more details. + +=== Documentation Previews +When a pull request has changes to asciidoc files, you can use https://www.netlify.com/[Netlify] to see a preview of how the HTML version of those asciidoc files will look like when the pull request is merged. See <<UsingNetlify#, UsingNetlify.adoc>> for more details. + +=== Making a Release + +Here are the steps to create a new release. + +. Update the version number in link:{repoURL}/src/main/java/seedu/progresschecker/MainApp.java[`MainApp.java`]. +. Generate a JAR file <<UsingGradle#creating-the-jar-file, using Gradle>>. +. Tag the repo with the version number. e.g. `v0.1` +. https://help.github.com/articles/creating-releases/[Create a new release using GitHub] and upload the JAR file you created. + +=== Managing Dependencies + +A project often depends on third-party libraries. For example, Address Book depends on the http://wiki.fasterxml.com/JacksonHome[Jackson library] for XML parsing. Managing these _dependencies_ can be automated using Gradle. For example, Gradle can download the dependencies automatically, which is better than these alternatives. + +a. Include those libraries in the repo (this bloats the repo size) + +b. Require developers to download those libraries manually (this creates extra work for developers) + +[[GetStartedProgramming]] +[appendix] +== Suggested Programming Tasks to Get Started + +It might be your first time working with a large code base. If so, here is a suggested path for new programmers to kick start your first functionality: + +1. First, add small local-impact (i.e. the impact of the change does not go beyond the component) enhancements to one component at a time. Some suggestions are given in <<GetStartedProgramming-EachComponent>>. + +2. Next, add a feature that touches multiple components to learn how to implement an end-to-end feature across all components. <<GetStartedProgramming-RemarkCommand>> explains how to go about adding such a feature. + +[[GetStartedProgramming-EachComponent]] +=== Improving each component + +Each individual exercise in this section is component-based (i.e. you would not need to modify the other components to get it to work). + +[discrete] +==== `Logic` component + +*Scenario:* You are in charge of `logic`. During dog-fooding, your team realize that it is troublesome for the user to type the whole command in order to execute a command. Your team devise some strategies to help cut down the amount of typing necessary, and one of the suggestions was to implement aliases for the command words. Your job is to implement such aliases. + +[TIP] +Do take a look at <<Design-Logic>> before attempting to modify the `Logic` component. + +. Add a shorthand equivalent alias for each of the individual commands. For example, besides typing `clear`, the user can also type `c` to remove teammates in the list. ++ +**** +* Hints +** Just like we store each individual command word constant `COMMAND_WORD` inside `*Command.java` (e.g. link:{repoURL}/src/main/java/seedu/progresschecker/logic/commands/FindCommand.java[`FindCommand#COMMAND_WORD`], link:{repoURL}/src/main/java/seedu/progresschecker/logic/commands/DeleteCommand.java[`DeleteCommand#COMMAND_WORD`]), you need a new constant for aliases as well (e.g. `FindCommand#COMMAND_ALIAS`). +** link:{repoURL}/src/main/java/seedu/progresschecker/logic/parser/ProgressCheckerParser.java[`ProgressCheckerParser`] is responsible for analyzing command words. +* Solution +** Modify the switch statement in link:{repoURL}/src/main/java/seedu/progresschecker/logic/parser/ProgressCheckerParser.java[`ProgressCheckerParser#parseCommand(String)`] such that both the proper command word and alias can be used to execute the same intended command. +** Add new tests for each of the aliases that you have added. +** Update the user guide to document the new aliases. +** See this https://github.com/se-edu/addressbook-level4/pull/785[PR] for the full solution. +**** + +[discrete] +==== `Model` component + +*Scenario:* You are in charge of `model`. One day, the `logic`-in-charge approaches you for help. He wants to implement a command such that the user is able to remove a particular tag from everyone in the ProgressChecker, but the model API does not support such a functionality at the moment. Your job is to implement an API method, so that your teammate can use your API to implement his command. + +[TIP] +Do take a look at <<Design-Model>> before attempting to modify the `Model` component. + +. Add a `removeTag(Tag)` method. The specified tag will be removed from everyone in the ProgressChecker. ++ +**** +* Hints +** The link:{repoURL}/src/main/java/seedu/progresschecker/model/Model.java[`Model`] and the link:{repoURL}/src/main/java/seedu/progresschecker/model/ProgressChecker.java[`ProgressChecker`] API need to be updated. +** Think about how you can use SLAP to design the method. Where should we place the main logic of deleting tags? +** Find out which of the existing API methods in link:{repoURL}/src/main/java/seedu/progresschecker/model/ProgressChecker.java[`ProgressChecker`] and link:{repoURL}/src/main/java/seedu/progresschecker/model/person/Person.java[`Person`] classes can be used to implement the tag removal logic. link:{repoURL}/src/main/java/seedu/progresschecker/model/ProgressChecker.java[`ProgressChecker`] allows you to update a teammate, and link:{repoURL}/src/main/java/seedu/progresschecker/model/person/Person.java[`Person`] allows you to update the tags. +* Solution +** Implement a `removeTag(Tag)` method in link:{repoURL}/src/main/java/seedu/progresschecker/model/ProgressChecker.java[`ProgressChecker`]. Loop through each teammates, and remove the `tag` from each teammate. +** Add a new API method `deleteTag(Tag)` in link:{repoURL}/src/main/java/seedu/progresschecker/model/ModelManager.java[`ModelManager`]. Your link:{repoURL}/src/main/java/seedu/progresschecker/model/ModelManager.java[`ModelManager`] should call `ProgressChecker#removeTag(Tag)`. +** Add new tests for each of the new public methods that you have added. +** See this https://github.com/se-edu/addressbook-level4/pull/790[PR] for the full solution. +*** The current codebase has a flaw in tags management. Tags no longer in use by anyone may still exist on the link:{repoURL}/src/main/java/seedu/progresschecker/model/ProgressChecker.java[`ProgressChecker`]. This may cause some tests to fail. See issue https://github.com/se-edu/addressbook-level4/issues/753[`#753`] for more information about this flaw. +*** The solution PR has a temporary fix for the flaw mentioned above in its first commit. +**** + +[discrete] +==== `Ui` component + +*Scenario:* You are in charge of `ui`. During a beta testing session, your team is observing how the users use your ProgressChecker application. You realize that one of the users occasionally tries to delete non-existent tags from a contact, because the tags all look the same visually, and the user got confused. Another user made a typing mistake in his command, but did not realize he had done so because the error message wasn't prominent enough. A third user keeps scrolling down the list, because he keeps forgetting the index of the last teammate in the list. Your job is to implement improvements to the UI to solve all these problems. + +[TIP] +Do take a look at <<Design-Ui>> before attempting to modify the `UI` component. + +. Use different colors for different tags inside teammate cards. For example, `friends` tags can be all in brown, and `colleagues` tags can be all in yellow. ++ +**Before** ++ +image::getting-started-ui-tag-before.png[width="300"] ++ +**After** ++ +image::getting-started-ui-tag-after.png[width="300"] ++ +**** +* Hints +** The tag labels are created inside link:{repoURL}/src/main/java/seedu/progresschecker/ui/PersonCard.java[the `PersonCard` constructor] (`new Label(tag.tagName)`). https://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/Label.html[JavaFX's `Label` class] allows you to modify the style of each Label, such as changing its color. +** Use the .css attribute `-fx-background-color` to add a color. +** You may wish to modify link:{repoURL}/src/main/resources/view/DarkTheme.css[`DarkTheme.css`] to include some pre-defined colors using css, especially if you have experience with web-based css. +* Solution +** You can modify the existing test methods for `PersonCard` 's to include testing the tag's color as well. +** See this https://github.com/se-edu/addressbook-level4/pull/798[PR] for the full solution. +*** The PR uses the hash code of the tag names to generate a color. This is deliberately designed to ensure consistent colors each time the application runs. You may wish to expand on this design to include additional features, such as allowing users to set their own tag colors, and directly saving the colors to storage, so that tags retain their colors even if the hash code algorithm changes. +**** + +. Modify link:{repoURL}/src/main/java/seedu/progresschecker/commons/events/ui/NewResultAvailableEvent.java[`NewResultAvailableEvent`] such that link:{repoURL}/src/main/java/seedu/progresschecker/ui/ResultDisplay.java[`ResultDisplay`] can show a different style on error (currently it shows the same regardless of errors). ++ +**Before** ++ +image::getting-started-ui-result-before.png[width="200"] ++ +**After** ++ +image::getting-started-ui-result-after.png[width="200"] ++ +**** +* Hints +** link:{repoURL}/src/main/java/seedu/progresschecker/commons/events/ui/NewResultAvailableEvent.java[`NewResultAvailableEvent`] is raised by link:{repoURL}/src/main/java/seedu/progresschecker/ui/CommandBox.java[`CommandBox`] which also knows whether the result is a success or failure, and is caught by link:{repoURL}/src/main/java/seedu/progresschecker/ui/ResultDisplay.java[`ResultDisplay`] which is where we want to change the style to. +** Refer to link:{repoURL}/src/main/java/seedu/progresschecker/ui/CommandBox.java[`CommandBox`] for an example on how to display an error. +* Solution +** Modify link:{repoURL}/src/main/java/seedu/progresschecker/commons/events/ui/NewResultAvailableEvent.java[`NewResultAvailableEvent`] 's constructor so that users of the event can indicate whether an error has occurred. +** Modify link:{repoURL}/src/main/java/seedu/progresschecker/ui/ResultDisplay.java[`ResultDisplay#handleNewResultAvailableEvent(NewResultAvailableEvent)`] to react to this event appropriately. +** You can write two different kinds of tests to ensure that the functionality works: +*** The unit tests for `ResultDisplay` can be modified to include verification of the color. +*** The system tests link:{repoURL}/src/test/java/systemtests/ProgressCheckerSystemTest.java[`ProgressCheckerSystemTest#assertCommandBoxShowsDefaultStyle() and ProgressCheckerSystemTest#assertCommandBoxShowsErrorStyle()`] to include verification for `ResultDisplay` as well. +** See this https://github.com/se-edu/addressbook-level4/pull/799[PR] for the full solution. +*** Do read the commits one at a time if you feel overwhelmed. +**** + +. Modify the link:{repoURL}/src/main/java/seedu/progresschecker/ui/StatusBarFooter.java[`StatusBarFooter`] to show the total number of people in the ProgressChecker. ++ +**Before** ++ +image::getting-started-ui-status-before.png[width="500"] ++ +**After** ++ +image::getting-started-ui-status-after.png[width="500"] ++ +**** +* Hints +** link:{repoURL}/src/main/resources/view/StatusBarFooter.fxml[`StatusBarFooter.fxml`] will need a new `StatusBar`. Be sure to set the `GridPane.columnIndex` properly for each `StatusBar` to avoid misalignment! +** link:{repoURL}/src/main/java/seedu/progresschecker/ui/StatusBarFooter.java[`StatusBarFooter`] needs to initialize the status bar on application start, and to update it accordingly whenever the ProgressChecker is updated. +* Solution +** Modify the constructor of link:{repoURL}/src/main/java/seedu/progresschecker/ui/StatusBarFooter.java[`StatusBarFooter`] to take in the number of teammates when the application just started. +** Use link:{repoURL}/src/main/java/seedu/progresschecker/ui/StatusBarFooter.java[`StatusBarFooter#handleProgressCheckerChangedEvent(ProgressCheckerChangedEvent)`] to update the number of teammates whenever there are new changes to the progresschecker. +** For tests, modify link:{repoURL}/src/test/java/guitests/guihandles/StatusBarFooterHandle.java[`StatusBarFooterHandle`] by adding a state-saving functionality for the total number of people status, just like what we did for save location and sync status. +** For system tests, modify link:{repoURL}/src/test/java/systemtests/ProgressCheckerSystemTest.java[`ProgressCheckerSystemTest`] to also verify the new total number of teammates status bar. +** See this https://github.com/se-edu/addressbook-level4/pull/803[PR] for the full solution. +**** + +[discrete] +==== `Storage` component + +*Scenario:* You are in charge of `storage`. For your next project milestone, your team plans to implement a new feature of saving the ProgressChecker to the cloud. However, the current implementation of the application constantly saves the ProgressChecker after the execution of each command, which is not ideal if the user is working on limited internet connection. Your team decided that the application should instead save the changes to a temporary local backup file first, and only upload to the cloud after the user closes the application. Your job is to implement a backup API for the ProgressChecker storage. [TIP] Do take a look at <<Design-Storage>> before attempting to modify the `Storage` component. -. Add a new method `backupAddressBook(ReadOnlyAddressBook)`, so that the address book can be saved in a fixed temporary location. +. Add a new method `backupProgressChecker(ReadOnlyProgressChecker)`, so that the ProgressChecker can be saved in a fixed temporary location. ++ +**** +* Hint +** Add the API method in link:{repoURL}/src/main/java/seedu/progresschecker/storage/ProgressCheckerStorage.java[`ProgressCheckerStorage`] interface. +** Implement the logic in link:{repoURL}/src/main/java/seedu/progresschecker/storage/StorageManager.java[`StorageManager`] and link:{repoURL}/src/main/java/seedu/progresschecker/storage/XmlProgressCheckerStorage.java[`XmlProgressCheckerStorage`] class. +* Solution +** See this https://github.com/se-edu/addressbook-level4/pull/594[PR] for the full solution. +**** + +[[GetStartedProgramming-RemarkCommand]] +=== Creating a new command: `remark` + +By creating this command, you will get a chance to learn how to implement a feature end-to-end, touching all major components of the app. + +*Scenario:* You are a software maintainer for `progresschecker`, as the former developer team has moved on to new projects. The current users of your application have a list of new feature requests that they hope the software will eventually have. The most popular request is to allow adding additional comments/notes about a particular contact, by providing a flexible `remark` field for each contact, rather than relying on tags alone. After designing the specification for the `remark` command, you are convinced that this feature is worth implementing. Your job is to implement the `remark` command. + +==== Description +Edits the remark for a teammate specified in the `INDEX`. + +Format: `remark INDEX r/[REMARK]` + +Examples: + +* `remark 1 r/Likes to drink coffee.` + +Edits the remark for the first teammate to `Likes to drink coffee.` +* `remark 1 r/` + +Removes the remark for the first teammate. + +==== Step-by-step Instructions + +===== [Step 1] Logic: Teach the app to accept 'remark' which does nothing +Let's start by teaching the application how to parse a `remark` command. We will add the logic of `remark` later. + +**Main:** + +. Add a `RemarkCommand` that extends link:{repoURL}/src/main/java/seedu/progresschecker/logic/commands/UndoableCommand.java[`UndoableCommand`]. Upon execution, it should just throw an `Exception`. +. Modify link:{repoURL}/src/main/java/seedu/progresschecker/logic/parser/ProgressCheckerParser.java[`ProgressCheckerParser`] to accept a `RemarkCommand`. + +**Tests:** + +. Add `RemarkCommandTest` that tests that `executeUndoableCommand()` throws an Exception. +. Add new test method to link:{repoURL}/src/test/java/seedu/progresschecker/logic/parser/ProgressCheckerParserTest.java[`ProgressCheckerParserTest`], which tests that typing "remark" returns an instance of `RemarkCommand`. + +===== [Step 2] Logic: Teach the app to accept 'remark' arguments +Let's teach the application to parse arguments that our `remark` command will accept. E.g. `1 r/Likes to drink coffee.` + +**Main:** + +. Modify `RemarkCommand` to take in an `Index` and `String` and print those two parameters as the error message. +. Add `RemarkCommandParser` that knows how to parse two arguments, one index and one with prefix 'r/'. +. Modify link:{repoURL}/src/main/java/seedu/progresschecker/logic/parser/ProgressCheckerParser.java[`ProgressCheckerParser`] to use the newly implemented `RemarkCommandParser`. + +**Tests:** + +. Modify `RemarkCommandTest` to test the `RemarkCommand#equals()` method. +. Add `RemarkCommandParserTest` that tests different boundary values +for `RemarkCommandParser`. +. Modify link:{repoURL}/src/test/java/seedu/progresschecker/logic/parser/ProgressCheckerParserTest.java[`ProgressCheckerParserTest`] to test that the correct command is generated according to the user input. + +===== [Step 3] Ui: Add a placeholder for remark in `PersonCard` +Let's add a placeholder on all our link:{repoURL}/src/main/java/seedu/progresschecker/ui/PersonCard.java[`PersonCard`] s to display a remark for each person later. + +**Main:** + +. Add a `Label` with any random text inside link:{repoURL}/src/main/resources/view/PersonListCard.fxml[`PersonListCard.fxml`]. +. Add FXML annotation in link:{repoURL}/src/main/java/seedu/progresschecker/ui/PersonCard.java[`PersonCard`] to tie the variable to the actual label. + +**Tests:** + +. Modify link:{repoURL}/src/test/java/guitests/guihandles/PersonCardHandle.java[`PersonCardHandle`] so that future tests can read the contents of the remark label. + +===== [Step 4] Model: Add `Remark` class +We have to properly encapsulate the remark in our link:{repoURL}/src/main/java/seedu/progresschecker/model/person/Person.java[`Person`] class. Instead of just using a `String`, let's follow the conventional class structure that the codebase already uses by adding a `Remark` class. + +**Main:** + +. Add `Remark` to model component (you can copy from link:{repoURL}/src/main/java/seedu/progresschecker/model/person/Address.java[`Address`], remove the regex and change the names accordingly). +. Modify `RemarkCommand` to now take in a `Remark` instead of a `String`. + +**Tests:** + +. Add test for `Remark`, to test the `Remark#equals()` method. + +===== [Step 5] Model: Modify `Person` to support a `Remark` field +Now we have the `Remark` class, we need to actually use it inside link:{repoURL}/src/main/java/seedu/progresschecker/model/person/Person.java[`Person`]. + +**Main:** + +. Add `getRemark()` in link:{repoURL}/src/main/java/seedu/progresschecker/model/person/Person.java[`Person`]. +. You may assume that the user will not be able to use the `add` and `edit` commands to modify the remarks field (i.e. the person will be created without a remark). +. Modify link:{repoURL}/src/main/java/seedu/progresschecker/model/util/SampleDataUtil.java/[`SampleDataUtil`] to add remarks for the sample data (delete your `progressChecker.xml` so that the application will load the sample data when you launch it.) + +===== [Step 6] Storage: Add `Remark` field to `XmlAdaptedPerson` class +We now have `Remark` s for `Person` s, but they will be gone when we exit the application. Let's modify link:{repoURL}/src/main/java/seedu/progresschecker/storage/XmlAdaptedPerson.java[`XmlAdaptedPerson`] to include a `Remark` field so that it will be saved. + +**Main:** + +. Add a new Xml field for `Remark`. + +**Tests:** + +. Fix `invalidAndValidPersonProgressChecker.xml`, `typicalPersonsProgressChecker.xml`, `validProgressChecker.xml` etc., such that the XML tests will not fail due to a missing `<remark>` element. + +===== [Step 6b] Test: Add withRemark() for `PersonBuilder` +Since `Person` can now have a `Remark`, we should add a helper method to link:{repoURL}/src/test/java/seedu/progresschecker/testutil/PersonBuilder.java[`PersonBuilder`], so that users are able to create remarks when building a link:{repoURL}/src/main/java/seedu/progresschecker/model/person/Person.java[`Person`]. + +**Tests:** + +. Add a new method `withRemark()` for link:{repoURL}/src/test/java/seedu/progresschecker/testutil/PersonBuilder.java[`PersonBuilder`]. This method will create a new `Remark` for the person that it is currently building. +. Try and use the method on any sample `us` in link:{repoURL}/src/test/java/seedu/progresschecker/testutil/TypicalPersons.java[`TypicalPersons`]. + +===== [Step 7] Ui: Connect `Remark` field to `PersonCard` +Our remark label in link:{repoURL}/src/main/java/seedu/progresschecker/ui/PersonCard.java[`PersonCard`] is still a placeholder. Let's bring it to life by binding it with the actual `remark` field. + +**Main:** + +. Modify link:{repoURL}/src/main/java/seedu/progresschecker/ui/PersonCard.java[`PersonCard`]'s constructor to bind the `Remark` field to the `Person` 's remark. + +**Tests:** + +. Modify link:{repoURL}/src/test/java/seedu/progresschecker/ui/testutil/GuiTestAssert.java[`GuiTestAssert#assertCardDisplaysPerson(...)`] so that it will compare the now-functioning remark label. + +===== [Step 8] Logic: Implement `RemarkCommand#execute()` logic +We now have everything set up... but we still can't modify the remarks. Let's finish it up by adding in actual logic for our `remark` command. + +**Main:** + +. Replace the logic in `RemarkCommand#execute()` (that currently just throws an `Exception`), with the actual logic to modify the remarks of a teammate. + +**Tests:** + +. Update `RemarkCommandTest` to test that the `execute()` logic works. + +==== Full Solution + +See this https://github.com/se-edu/addressbook-level4/pull/599[PR] for the step-by-step solution. + +[appendix] +== Product Scope + +This section covers what ProgressChecker is meant to be and what it can do for the users. + +*Target user profile*: + +* is taking CS2103T in NUS +* has a need to manage a up to 4 contacts +* wants to have a centralized hub for managing his/her learning and software development +* wants to keep track on his/her learning outcomes and progress +* wants to save and refer to their answers for the weekly CS2103/T exercises +* wants to manage GitHub issues efficiently +* prefers desktop apps over other platforms +* prefers typing over mouse input +* is reasonably comfortable using CLI apps + +*Value proposition*: + +* keep track of your teammates' details +* keep track of your own progress on a week by week basis +* never miss any learning outcomes due to missing them out in nested collapsible list +* keep track of completed and incomplete (compulsory) learning outcomes +* view and save your answers for the exercises (as proof of completion and for future revision) +* manage issues from GitHub straight from the software along with other tracking + +[appendix] +== Feature Contribution + +The names of the contributors and their contributions to the project are listed here in brief. + +=== Koh Yee Ru + +. (Major) View, answer and save responses for weekly CS2103/T exercises +. (Minor) View command that toggles the tab view +image:TabView.png[width="600"] + +=== Kang Anmin + +. (Major) Task management: Add LOs to google tasks (the users google account, load tasks and sign completion. +. (Minor) Progress Bar: to give a graphic view of tasks completeness +. (Minor) Change/Add more fields of information for teammates in the contact list, in order to fit the specific context of this software. It also lays a foundation for other operations. + +=== Lai Liwen + +. (Major) Revamp the UI: rearrange the different sections and panels to best suit audience's needs +. (Major) Upload profile photo: students will be able to upload a photo to their profile +. (Minor) HighLight the key word: the key word will be highlighted in command find + +=== Aditya Agarwal + +. (Major) Create a github issue tracker which will be used to track issues on github using the ProgressChecker application. +. (Minor) Implement dynamic search + +[appendix] +== User Stories + +This section lists the actions that both new and long-time users can and may want to perform with ProgressChecker. + +Priorities: High (must have) - `* * \*`, Medium (nice to have) - `* \*`, Low (unlikely to have) - `*` + +[width="59%",cols="22%,<23%,<25%,<30%",options="header",] +|======================================================================= +|Priority |As a ... |I want to ... |So that I can... +|`* * *` |new user |see usage instructions |refer to instructions when I forget how to use the App + +|`* * *` |new user |fill in my details such as name, email, 8 digits phone number |provide necessary information for platform maintenance + +|`* * *` |new user |fill in optional fields such as faculty, year of study, etc. |help my teammates know me better + +|`* * *` |user |update information of certain field(s) |keep my information up-to-date + +|`* * *` |user |add a teammate's details |help myself to track my current teammates' progress + +|`* * *` |user |delete a teammate's details |remove an entry of a teammate's details that I'm no longer grouped with + +|`* * *` |new user |upload a photo for myself or my teammates |help me to recognize my teammates + +|`* * *` |user |view my to-do <<learning outcomes,learning outcomes>> |know all the weekly deliverables and not miss them out + +|`* * *` |user |mark a to-do learning outcome as completed |focus on the tasks I have not done + +|`* * *` |user |answer and save my responses for the weekly exercises |show to tutor as proof of my learning outcome and revise before exams + +|`* * *` |user |know if my answer for an exercise is correct |learn from any mistakes I made + +|`* * *` |user |list issues (tasks) on GitHub |easily inform my teammates of my upcoming plans even before I send any pull requests to the team's repository + +|`* * *` |user |assign issues (tasks) to my teammates |track who is doing what + +|`* * *` |user |see the issues (tasks) listed on GitHub |easily know the upcoming plans of my teammates even before they send any pull requests to the team's repository + +|`* * *` |user |close issues (tasks) on GitHub |easily inform my teammates of a completed task if no particular pull requests closes it + +|`* *` |user |see the timeline showing the learning progress of me and my teammates |make sure everyone is on track + +|`* *` |new user |load a photo of myself or my teammates from GitHub |help me to recognize my teammates + +|`* *` |user |see the list of completed/incomplete <<learning outcomes,learning outcomes>> of my teammates |help to remind my teammate of the task or know which task to offer help with if they are having difficulties + +|`* *` |user |search information in our module website based on keywords |navigate and reference the information I need quickly + +|`* *` |user |hide <<private-contact-detail,private contact details>> by default |minimize chance of someone else seeing them by accident + +|`* *` |user with many teammates in the ProgressChecker |sort teammates by name |locate a teammate easily +|======================================================================= + +[appendix] +== Use Cases + +This section list the sequence of events for a feature. It includes possible scenarios in which a feature is not interacted with as intended which you can defense against. + +(For all use cases below, the *System* is the `ProgressChecker` and the *Actor* is the `user`, unless specified otherwise) + +// tag::viewusecase[] +[discrete] +=== Use case: View (toggle) a different tab + +*MSS* + +1. User requests to view a specific tab type +2. ProgressChecker toggles tab view to show the requested tab ++ +Use case ends. + +*Extensions* + +* 1a. The given tab type is invalid. ++ +[none] +** 1a1. ProgressChecker shows an error message. +Use case ends. + +[none] +* 2a. There is no content to be shown. ++ +Use case ends. +// end::viewusecase[] + +[discrete] +=== Use case: Add teammate + +*MSS* + +1. User requests to add a specific teammate in the list +2. ProgressChecker add the teammate ++ +Use case ends. + +*Extensions* + +* 1a. The teammate has already been existing in the list. ++ + +** 1a1. ProgressChecker shows an error message. ++ +Use case resumes at step 1. + +* 1a. The given information is invalid. ++ + +** 1a1. ProgressChecker shows an error message. ++ +Use case resumes at step 1. + +[discrete] +=== Use case: Add the default task list + +*MSS* + +1. User requests to add the task list +2. If this is the first google-task-relevant command used by the user in this session, user is requested to log in his/her google account +3. ProgressChecker loads and parses local file, adds the task list to user's google account ++ +Use case ends. + +*Extensions* + +* 2a. No Internet Access. ++ +Use case ends. + +* 2b. Invalid client credential file. ++ +Use case ends. + +* 2c. Invalid user log in information. ++ +Use case ends. + +* 3a. The file is not found. ++ +Use case ends. + +* 3b. The file is corrupted. ++ +Use case ends. + +[discrete] +=== Use case: View Task List + +*MSS* + +1. User requests to view the task list with a filter argument +2. If this is the first google-task-relevant command used by the user in this session, user is requested to log in his/her google account +3. ProgressChecker makes request to the user's google account to load the task list. + -**** -* Hint -** Add the API method in link:{repoURL}/src/main/java/seedu/address/storage/AddressBookStorage.java[`AddressBookStorage`] interface. -** Implement the logic in link:{repoURL}/src/main/java/seedu/address/storage/StorageManager.java[`StorageManager`] and link:{repoURL}/src/main/java/seedu/address/storage/XmlAddressBookStorage.java[`XmlAddressBookStorage`] class. -* Solution -** See this https://github.com/se-edu/addressbook-level4/pull/594[PR] for the full solution. -**** +Use case ends. -[[GetStartedProgramming-RemarkCommand]] -=== Creating a new command: `remark` +*Extensions* -By creating this command, you will get a chance to learn how to implement a feature end-to-end, touching all major components of the app. +* 1a. The argument is invalid. ++ +Use case ends. -*Scenario:* You are a software maintainer for `addressbook`, as the former developer team has moved on to new projects. The current users of your application have a list of new feature requests that they hope the software will eventually have. The most popular request is to allow adding additional comments/notes about a particular contact, by providing a flexible `remark` field for each contact, rather than relying on tags alone. After designing the specification for the `remark` command, you are convinced that this feature is worth implementing. Your job is to implement the `remark` command. +* 2a. No Internet Access. ++ +Use case ends. -==== Description -Edits the remark for a person specified in the `INDEX`. + -Format: `remark INDEX r/[REMARK]` +* 2b. Invalid client credential file. ++ +Use case ends. -Examples: +* 2c. Invalid user log in information. ++ +Use case ends. -* `remark 1 r/Likes to drink coffee.` + -Edits the remark for the first person to `Likes to drink coffee.` -* `remark 1 r/` + -Removes the remark for the first person. +[discrete] +=== Use case: Complete a task -==== Step-by-step Instructions +*MSS* -===== [Step 1] Logic: Teach the app to accept 'remark' which does nothing -Let's start by teaching the application how to parse a `remark` command. We will add the logic of `remark` later. +1. User requests to mark a task as completed +2. If this is the first google-task-relevant command used by the user in this session, user is requested to log in his/her google account +3. ProgressChecker marks the task as completed ++ +Use case ends. -**Main:** +*Extensions* -. Add a `RemarkCommand` that extends link:{repoURL}/src/main/java/seedu/address/logic/commands/UndoableCommand.java[`UndoableCommand`]. Upon execution, it should just throw an `Exception`. -. Modify link:{repoURL}/src/main/java/seedu/address/logic/parser/AddressBookParser.java[`AddressBookParser`] to accept a `RemarkCommand`. +* 1a. The index is invalid. ++ +Use case ends. -**Tests:** +* 2a. No Internet Access. ++ +Use case ends. -. Add `RemarkCommandTest` that tests that `executeUndoableCommand()` throws an Exception. -. Add new test method to link:{repoURL}/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java[`AddressBookParserTest`], which tests that typing "remark" returns an instance of `RemarkCommand`. +* 2b. Invalid client credential file. ++ +Use case ends. -===== [Step 2] Logic: Teach the app to accept 'remark' arguments -Let's teach the application to parse arguments that our `remark` command will accept. E.g. `1 r/Likes to drink coffee.` +* 2c. Invalid user log in information. ++ +Use case ends. -**Main:** +* 3a. The index is valid but out of bound. ++ +Use case ends. -. Modify `RemarkCommand` to take in an `Index` and `String` and print those two parameters as the error message. -. Add `RemarkCommandParser` that knows how to parse two arguments, one index and one with prefix 'r/'. -. Modify link:{repoURL}/src/main/java/seedu/address/logic/parser/AddressBookParser.java[`AddressBookParser`] to use the newly implemented `RemarkCommandParser`. +[discrete] +=== Use case: Reset a task -**Tests:** +*MSS* -. Modify `RemarkCommandTest` to test the `RemarkCommand#equals()` method. -. Add `RemarkCommandParserTest` that tests different boundary values -for `RemarkCommandParser`. -. Modify link:{repoURL}/src/test/java/seedu/address/logic/parser/AddressBookParserTest.java[`AddressBookParserTest`] to test that the correct command is generated according to the user input. +1. User requests to reset a task as not completed +2. If this is the first google-task-relevant command used by the user in this session, user is requested to log in his/her google account +3. ProgressChecker resets the task as not completed ++ +Use case ends. -===== [Step 3] Ui: Add a placeholder for remark in `PersonCard` -Let's add a placeholder on all our link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[`PersonCard`] s to display a remark for each person later. +*Extensions* -**Main:** +* 1a. The index is invalid. ++ +Use case ends. -. Add a `Label` with any random text inside link:{repoURL}/src/main/resources/view/PersonListCard.fxml[`PersonListCard.fxml`]. -. Add FXML annotation in link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[`PersonCard`] to tie the variable to the actual label. +* 2a. No Internet Access. ++ +Use case ends. -**Tests:** +* 2b. Invalid client credential file. ++ +Use case ends. -. Modify link:{repoURL}/src/test/java/guitests/guihandles/PersonCardHandle.java[`PersonCardHandle`] so that future tests can read the contents of the remark label. +* 2c. Invalid user log in information. ++ +Use case ends. -===== [Step 4] Model: Add `Remark` class -We have to properly encapsulate the remark in our link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`] class. Instead of just using a `String`, let's follow the conventional class structure that the codebase already uses by adding a `Remark` class. +* 3a. The index is valid but out of bound. ++ +Use case ends. -**Main:** +[discrete] +=== Use case: Open URL of a task -. Add `Remark` to model component (you can copy from link:{repoURL}/src/main/java/seedu/address/model/person/Address.java[`Address`], remove the regex and change the names accordingly). -. Modify `RemarkCommand` to now take in a `Remark` instead of a `String`. +*MSS* -**Tests:** +1. User requests to open URL of a task +2. If this is the first google-task-relevant command used by the user in this session, user is requested to log in his/her google account +3. ProgressChecker opens the URL and show in browser panel ++ +Use case ends. -. Add test for `Remark`, to test the `Remark#equals()` method. +*Extensions* -===== [Step 5] Model: Modify `Person` to support a `Remark` field -Now we have the `Remark` class, we need to actually use it inside link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`]. +* 1a. The index is invalid. ++ +Use case ends. -**Main:** +* 2a. No Internet Access. ++ +Use case ends. -. Add `getRemark()` in link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`]. -. You may assume that the user will not be able to use the `add` and `edit` commands to modify the remarks field (i.e. the person will be created without a remark). -. Modify link:{repoURL}/src/main/java/seedu/address/model/util/SampleDataUtil.java/[`SampleDataUtil`] to add remarks for the sample data (delete your `addressBook.xml` so that the application will load the sample data when you launch it.) +* 2b. Invalid client credential file. ++ +Use case ends. -===== [Step 6] Storage: Add `Remark` field to `XmlAdaptedPerson` class -We now have `Remark` s for `Person` s, but they will be gone when we exit the application. Let's modify link:{repoURL}/src/main/java/seedu/address/storage/XmlAdaptedPerson.java[`XmlAdaptedPerson`] to include a `Remark` field so that it will be saved. +* 2c. Invalid user log in information. ++ +Use case ends. -**Main:** +* 3a. The index is valid but out of bound. ++ +Use case ends. -. Add a new Xml field for `Remark`. +// tag::answerusecase[] +[discrete] +=== Use case: Answer a question and save -**Tests:** +*MSS* + +1. User requests to view the exercise tab of week X +2. ProgressChecker toggles to exercise tab and list week X's exercises +3. User requests to key in and save an answer to a question +4. ProgressChecker takes in input and saves ++ +Use case ends. -. Fix `invalidAndValidPersonAddressBook.xml`, `typicalPersonsAddressBook.xml`, `validAddressBook.xml` etc., such that the XML tests will not fail due to a missing `<remark>` element. +*Extensions* -===== [Step 6b] Test: Add withRemark() for `PersonBuilder` -Since `Person` can now have a `Remark`, we should add a helper method to link:{repoURL}/src/test/java/seedu/address/testutil/PersonBuilder.java[`PersonBuilder`], so that users are able to create remarks when building a link:{repoURL}/src/main/java/seedu/address/model/person/Person.java[`Person`]. +* 1a. The given tab type is invalid. ++ +[none] +** 1a1. ProgressChecker shows an error message. +Use case ends. -**Tests:** +* 1b. Specified week does not exist. ++ +[none] +** 1b1. ProgressChecker shows an error message. ++ +Use case ends. -. Add a new method `withRemark()` for link:{repoURL}/src/test/java/seedu/address/testutil/PersonBuilder.java[`PersonBuilder`]. This method will create a new `Remark` for the person that it is currently building. -. Try and use the method on any sample `Person` in link:{repoURL}/src/test/java/seedu/address/testutil/TypicalPersons.java[`TypicalPersons`]. +[none] +* 2a. There are no exercises to be shown. ++ +Use case ends. -===== [Step 7] Ui: Connect `Remark` field to `PersonCard` -Our remark label in link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[`PersonCard`] is still a placeholder. Let's bring it to life by binding it with the actual `remark` field. +* 3a. User did not provide a question index. ++ +[none] +** 3a1. ProgressChecker shows an error message. ++ +Use case ends. -**Main:** +* 3b. User did not provide an answer. ++ +[none] +** 3b1. ProgressChecker shows an error message. ++ +Use case ends. -. Modify link:{repoURL}/src/main/java/seedu/address/ui/PersonCard.java[`PersonCard`]'s constructor to bind the `Remark` field to the `Person` 's remark. +* 3c. The given question index does not exists. ++ +[none] +** 3c1. ProgressChecker shows an error message. ++ +Use case ends. +// end::answerusecase[] -**Tests:** +[discrete] +=== Use case: Assign an issue to a teammate -. Modify link:{repoURL}/src/test/java/seedu/address/ui/testutil/GuiTestAssert.java[`GuiTestAssert#assertCardDisplaysPerson(...)`] so that it will compare the now-functioning remark label. +_{ to be added }_ -===== [Step 8] Logic: Implement `RemarkCommand#execute()` logic -We now have everything set up... but we still can't modify the remarks. Let's finish it up by adding in actual logic for our `remark` command. +[discrete] +=== Use case: Autocomplete a command -**Main:** +*MSS* -. Replace the logic in `RemarkCommand#execute()` (that currently just throws an `Exception`), with the actual logic to modify the remarks of a person. +1. User types an incomplete command +2. User presses `tab` key to complete the command +3. ProgessChecker returns the completed command with dummy fields if there exists a specific format ++ +Use case ends. -**Tests:** +*Extensions* -. Update `RemarkCommandTest` to test that the `execute()` logic works. +* 1a. Specified command does not exist. ++ +[none] +** 1a1. ProgressChecker doesn't do anything and waits for the right key/command to be entered. +** 1a2. It waits for the right letter to be pressed or the correct command to be entered. ++ +Use case resumes at step 1. -==== Full Solution +[discrete] +=== Use case: Delete teammate -See this https://github.com/se-edu/addressbook-level4/pull/599[PR] for the step-by-step solution. +*MSS* -[appendix] -== Product Scope +1. User requests to list teammates +2. ProgressChecker shows a list of teammates +3. User requests to delete a specific teammate in the list +4. ProgressChecker deletes the teammate ++ +Use case ends. -*Target user profile*: +*Extensions* -* has a need to manage a significant number of contacts -* prefer desktop apps over other types -* can type fast -* prefers typing over mouse input -* is reasonably comfortable using CLI apps +* 2a. The list is empty. ++ +Use case ends. -*Value proposition*: manage contacts faster than a typical mouse/GUI driven app +* 3a. The given index is invalid. ++ -[appendix] -== User Stories +** 3a1. ProgressChecker shows an error message. ++ +Use case resumes at step 2. -Priorities: High (must have) - `* * \*`, Medium (nice to have) - `* \*`, Low (unlikely to have) - `*` +[discrete] +=== Use case: Close an issue -[width="59%",cols="22%,<23%,<25%,<30%",options="header",] -|======================================================================= -|Priority |As a ... |I want to ... |So that I can... -|`* * *` |new user |see usage instructions |refer to instructions when I forget how to use the App +_{ to be added }_ + +[discrete] +=== Use case: Find teammate -|`* * *` |user |add a new person | +*MSS* -|`* * *` |user |delete a person |remove entries that I no longer need +1. User types find +2. ProgressChecker automatically shows the list dynamically without the user needing to press enter key +3. User need not need to type the whole name, substrings will generate results +4. ProgressChecker displays the necessary results ++ +Use case ends. -|`* * *` |user |find a person by name |locate details of persons without having to go through the entire list +*Extensions* -|`* *` |user |hide <<private-contact-detail,private contact details>> by default |minimize chance of someone else seeing them by accident +[none] +* 2a. The contact list is empty. ++ +Use case resumes at step 2. -|`*` |user with many persons in the address book |sort persons by name |locate a person easily -|======================================================================= +* 3a. The given substring doesn't exist in any name ++ +[none] +** 3a1. ProgressChecker shows an error message. ++ +Use case resumes at step 2. -_{More to be added}_ -[appendix] -== Use Cases +[discrete] +=== Use case: List an issue -(For all use cases below, the *System* is the `AddressBook` and the *Actor* is the `user`, unless specified otherwise) +_{ to be added }_ [discrete] -=== Use case: Delete person +=== Use case: Mark a learning outcome as completed *MSS* -1. User requests to list persons -2. AddressBook shows a list of persons -3. User requests to delete a specific person in the list -4. AddressBook deletes the person +1. User requests to list tasks(LOs) +2. ProgressChecker shows a list of tasks(LOs) +3. User provides an index to requests to mark the corresponding LO in the list as completed +4. If this is the first google-task-relevant command used by the user in this session, user is requested to log in his/her google account +5. ProgressChecker executes command to mark the LO as completed in google tasks under the user's google account + Use case ends. *Extensions* -[none] * 2a. The list is empty. + Use case ends. +* 2b. The list has not been created yet (invalid list name). ++ +Use case ends. + * 3a. The given index is invalid. + -[none] -** 3a1. AddressBook shows an error message. + +** 3a1. ProgressChecker shows an error message. + Use case resumes at step 2. -_{More to be added}_ +[discrete] +=== Use case: Search for information + +_{ to be added }_ + +[discrete] +=== Use case: Upload a photo for the profile + +*MSS* + +1. User requests to view their profile +2. ProgressChecker shows the profile of the user +3. User requests to upload a new photo to the profile +4. ProgressChecker adds a new photo to the profile of user +5. Profile displays the new photo ++ +Use case ends. + +*Extensions* + +* 1a. Picture intented to add cannot be found. ++ +[none] +** 1a1. ProgressChecker shows an error message. ++ +Use case resumes at step 2. [appendix] == Non Functional Requirements +This sections list the criteria needed for the system and software. + . Should work on any <<mainstream-os,mainstream OS>> as long as it has Java `1.8.0_60` or higher installed. -. Should be able to hold up to 1000 persons without a noticeable sluggishness in performance for typical usage. . A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse. - -_{More to be added}_ +. The data cannot be retrieved from outside. +. The product may need 3-5 minutes to build up for the first time. +. User need to authenticate with their Google Tasks credentials. [appendix] == Glossary -[[mainstream-os]] Mainstream OS:: -Windows, Linux, Unix, OS-X - -[[private-contact-detail]] Private contact detail:: -A contact detail that is not meant to be shared with others - -[appendix] -== Product Survey +[[Build-Automation]] Build Automation:: +Build automation is the process of automating the creation of a software build and the associated processes including: compiling computer source code into binary code, packaging binary code, and running automated tests. -*Product Name* +[[Gradle]] Gradle:: +Gradle is an open-source build automation system. -Author: ... +[[GUI]] GUI:: +Graphical User Interface. -Pros: +[[Learning-Outcomes (LO)]] Learning Outcomes:: +Exercises that need to be done through GitHub for module CS2103/T. -* ... -* ... +[[mainstream-os]] Mainstream OS:: +Windows, Linux, Unix, MAC-OS(OS-X). -Cons: +[[private-contact-detail]] Private contact detail:: +A contact detail that is not meant to be shared with others. -* ... -* ... +[[sequence-diagram]] Sequence Diagram:: +A <<sequence diagram,sequence diagram>> shows object interactions shown in time sequence. [appendix] == Instructions for Manual Testing -Given below are instructions to test the app manually. +You may want to do manual testing to familiarise yourself with the software. Given below are instructions to test the app manually. [NOTE] These instructions only provide a starting point for testers to work on; testers are expected to do more _exploratory_ testing. @@ -903,21 +2128,100 @@ These instructions only provide a starting point for testers to work on; testers .. Re-launch the app by double-clicking the jar file. + Expected: The most recent window size and location is retained. -_{ more test cases ... }_ - -=== Deleting a person +=== Deleting a teammate -. Deleting a person while all persons are listed +. Deleting a teammate while all teammates are listed -.. Prerequisites: List all persons using the `list` command. Multiple persons in the list. +.. Prerequisites: List all teammates using the `list` command. Multiple teammates in the list. .. Test case: `delete 1` + Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated. .. Test case: `delete 0` + - Expected: No person is deleted. Error details shown in the status message. Status bar remains the same. + Expected: No teammate is deleted. Error details shown in the status message. Status bar remains the same. .. Other incorrect delete commands to try: `delete`, `delete x` (where x is larger than the list size) _{give more}_ + Expected: Similar to previous. -_{ more test cases ... }_ +=== Log into Github + +. Logging in to github when not not logged in + +.. Prerequisites: User shouldn't have logged into github +.. Test case: `gitlogin gu/USERNAME pc/PASSCODE r/REPOSITORY` + + Expected: You have successfully logged into github! +.. Test case: `gitlogin gu/WRONG_USERNAME pc/PASSCODE r/REPOSITORY` + + Expected: Enter correct username and password. + +.. Other incorrect gitlogin commands to try: `gitlogin`, + + Expected: Invalid command format. + +=== Create an issue on github + +. Create issue on github after logging in + +.. Prerequisites: User should have logged into github with correct repository +.. Test case: `+issue ti/Test b/test body ms/v1.1 a/johndoe l/bug` + + Expected: You have successfully created an issue on github! +.. Test case: `+issue ti/Test b/test body ms/INVALID_MILESTONE a/johndoe l/bug` + + Expected: Enter correct milestone. + +.. Other incorrect +issue commands to try: `+issue`, + + Expected: Invalid command format. + +=== Edit an issue on github + +. Edit issue on github after logging in + +.. Prerequisites: User should have logged into github with correct repository +.. Test case: `editissue 123 ti/Test b/test body ms/v1.1 a/johndoe l/bug` + + Expected: You have successfully editted an issue on github! +.. Test case: `editissue 99999 ti/Test b/test body ms/v1.1 a/johndoe l/bug` + + Expected: Issue not edited. Enter correct index number. + +.. Other incorrect editissue commands to try: `editissue`, + + Expected: Invalid command format. + +=== Close an issue on github + +. Close an issue on github after logging in + +.. Prerequisites: User should have logged into github with correct repository +.. Test case: `-issue 37` + + Expected: Issue #37 has successfully been closed! +.. Test case: `-issue 9999` + + Expected: Issue not closed. Enter correct index number. + +.. Other incorrect close issue commands to try: `-issue 3 text`, + + Expected: Invalid command format. + +=== Reopen an issue on github + +. Reopen an issue on github after logging in + +.. Prerequisites: User should have logged into github with correct repository +.. Test case: `reopenissue 37` + + Expected: Issue #37 has successfully been reopened! +.. Test case: `reopenissue 9999` + + Expected: Issue not reopened. Enter correct index number. + +.. Other incorrect reopen issue commands to try: `reopen 3 text`, + + Expected: Invalid command format. + +=== List issues on github + +. List issues on github after logging in + +.. Prerequisites: User should have logged into github with correct repository +.. Test case: `listissues OPEN` + + Expected: All open issues are listed! +.. Test case: `listissues ssxss` + + Expected: Enter correct state value. + +.. Other incorrect list issues commands to try: `listissues`, + + Expected: Invalid command format. + +=== Logout of Github + +. Log out of github after logging in + +.. Prerequisites: User should have logged into github with correct repository +.. Test case: `gitlogout` + + Expected: You have successfully logged out of github! +.. Prerequisites: User should not have logged into github +.. Test case: `gitlogout` + + Expected: Please log into github first to logout. + === Saving data @@ -926,3 +2230,30 @@ _{ more test cases ... }_ .. _{explain how to simulate a missing/corrupted file and the expected behavior}_ _{ more test cases ... }_ + +=== Toggling a tab view + +. Navigate to another tab view + +.. Test case: `view exercise` + + Expected: UI toggles the tab view to the Exercise tab. A list of exercises should be displayed. +.. Test case: `view exercise 5` + + Expected: UI toggles the tab view to the Exercise tab. Week 5's list of exercises should be displayed. +.. Test case: `view invalidtype` + + Expected: No such tab found. Error details shown in the status message. +.. Other incorrect view commands to try: `view`, `view exercise x` (where x is an input not within 2 to 11 (inclusive) + + Expected: Similar to previous. + +=== Answering an exercise + +. Answer an exercise and see the suggested answer + +.. Prerequisites: UI view is on the Exercise tab, showing week 11's exercises. +.. Test case: `ans 11.1.1 a` + + Expected: Question index 11.1.1 turns green. Answer `a` is reflected under "Your Answer" and suggested answer for question index 11.1.1 is revealed. +.. Test case: `view 11` + + Expected: Given question index does not exist. Error details shown in the status message. +.. Other incorrect answer commands to try: `ans`, `ans 11.2` + + Expected: Similar to previous. + +Back to <<Setting up,TOP>> diff --git a/docs/LearningOutcomes.adoc b/docs/LearningOutcomes.adoc index cf153ba8b38f..e487dd3d27af 100644 --- a/docs/LearningOutcomes.adoc +++ b/docs/LearningOutcomes.adoc @@ -37,7 +37,7 @@ Note how the <<DeveloperGuide#architecture, Developer Guide>> uses events to com == Use API Design `[LO-ApiDesign]` -Note how components of AddressBook have well-defined APIs. For example, the API of the `Logic` component is given in the link:{repoURL}/src/main/java/seedu/address/logic/Logic.java[`Logic.java`] +Note how components of Addressbook have well-defined APIs. For example, the API of the `Logic` component is given in the link:{repoURL}/src/main/java/seedu/address/logic/Logic.java[`Logic.java`] image:LogicClassDiagram.png[width="800"] *Resources* diff --git a/docs/UserGuide.adoc b/docs/UserGuide.adoc index 74248917e438..595506e80560 100644 --- a/docs/UserGuide.adoc +++ b/docs/UserGuide.adoc @@ -1,5 +1,6 @@ -= AddressBook Level 4 - User Guide += ProgressChecker - User Guide :toc: +:toclevels: 4 :toc-title: :toc-placement: preamble :sectnums: @@ -8,182 +9,204 @@ :xrefstyle: full :experimental: ifdef::env-github[] +:important-caption: â— :tip-caption: :bulb: :note-caption: :information_source: endif::[] -:repoURL: https://github.com/se-edu/addressbook-level4 +:repoURL: https://github.com/CS2103JAN2018-T09-B3/main +:ext-relative: DeveloperGuide.adoc -By: `Team SE-EDU` Since: `Jun 2016` Licence: `MIT` +By: `Team T09-B3` Since: `Feb 2018` Licence: `MIT` == Introduction -AddressBook Level 4 (AB4) is for those who *prefer to use a desktop app for managing contacts*. More importantly, AB4 is *optimized for those who prefer to work with a Command Line Interface* (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, AB4 can get your contact management tasks done faster than traditional GUI apps. Interested? Jump to the <<Quick Start>> to get started. Enjoy! +=== About the App + +ProgressChecker is for *students* who *prefer to use a desktop app* to keep track of their learning progress for module CS2103/T. + +In this version, you can add your teammates details into ProgressChecker. You can also create a new task list that syncs with Google Tasks. ProgressChecker displays link:DeveloperGuide.adoc#Learning-Outcomes[Learning Outcomes] and exercises pulled from the https://nus-cs2103-ay1718s2.github.io/website/index.html[CS2103/T module website]. Students can use these information to track their weekly homework and the progress of the project. + +More importantly, ProgressChecker is *optimized for students who prefer to work with a Command Line Interface* (CLI) while still having the benefits of a Graphical User Interface (link:DeveloperGuide.adoc#GUI[GUI]). +If you are reasonably comfortable with using CLI, ProgressChecker can get your learning and project development tracking done faster than traditional GUI apps. + + +Interested? Now you are ready to jump to the <<Quick Start,Quick Start>> to get started. Enjoy! + +=== Icons Meaning + +You will be seeing these icons throughout the guide. Each icon display specific information. + +[IMPORTANT] +This exclamation mark means you should strictly follow the instruction here to avoid unwanted outcome from ProgressChecker. + +[TIP] +This lightbulb icon means tips that you can try when using ProgressChecker. + +[NOTE] +This info icon means notes that you should pay attention to when using ProgressChecker. == Quick Start -. Ensure you have Java version `1.8.0_60` or later installed in your Computer. +Getting started with ProgressChecker is as easy as downloading and launching the software in a couple of clicks! Listed below are the important *steps* that you will need to follow to get started: + +. Please ensure that you have Java version `1.8.0_60` or later installed in your Computer. + [NOTE] Having any Java 8 version is not enough. + This app will not work with earlier versions of Java 8. + -. Download the latest `addressbook.jar` link:{repoURL}/releases[here]. -. Copy the file to the folder you want to use as the home folder for your Address Book. -. Double-click the file to start the app. The GUI should appear in a few seconds. +. You can download the latest `progresschecker.jar` link:{repoURL}/releases[here]. +. Next, create a new folder on your Desktop and name it _ProgressChecker_. Then, place the downloaded progresschecker.jar file into the new _ProgressChecker_ folder you've just created. + +Your jar file should now be residing in C:\Users\%UserProfile%\Desktop\ProgressChecker. + -image::Ui.png[width="790"] +image::download3.png[width="790"] + -. Type the command in the command box and press kbd:[Enter] to execute it. + -e.g. typing *`help`* and pressing kbd:[Enter] will open the help window. -. Some example commands you can try: - -* *`list`* : lists all contacts -* **`add`**`n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` : adds a contact named `John Doe` to the Address Book. -* **`delete`**`3` : deletes the 3rd contact shown in the current list -* *`exit`* : exits the app - -. Refer to <<Features>> for details of each command. +. You can double-click the `progresschecker.jar` file to launch ProgressChecker. A GUI as shown below should appear in a few seconds. ++ +image::UI.jpg[width="790"] ++ +Here are the four tabs(profile, task, exercise and issue) that you can switch in center panel: ++ +image::profileTab.jpg[width="790"] ++ +image::exerciseTab.jpg[width="790"] ++ +image::taskTab.jpg[width="790"] ++ +image::issueTab.jpg[width="790"] ++ +. You should also notice that the following files shown below will have been automatically generated in the same directory as where your progresschecker.jar file is in upon launch. ++ +image::download2.png[width="790"] ++ +Now, you are ready to start to explore ProgressChecker! +[IMPORTANT] +Please do NOT try other task-related command before you call `newtasklist` command. [[Features]] == Features -==== -*Command Format* - -* Words in `UPPER_CASE` are the parameters to be supplied by the user e.g. in `add n/NAME`, `NAME` is a parameter which can be used as `add n/John Doe`. -* Items in square brackets are optional e.g `n/NAME [t/TAG]` can be used as `n/John Doe t/friend` or as `n/John Doe`. -* Items with `…`​ after them can be used multiple times including zero times e.g. `[t/TAG]...` can be used as `{nbsp}` (i.e. 0 times), `t/friend`, `t/friend t/family` etc. -* Parameters can be in any order e.g. if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable. -==== - -=== Viewing help : `help` - -Format: `help` +ProgressChecker is filled with lots of functionality that are easy to use. If you're new to using CLI, our next section will give you a quick start to end process of using CLI with ProgressChecker. -=== Adding a person: `add` +=== Using CLI with ProgressChecker -Adds a person to the address book + -Format: `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]...` +Let's start using ProgressChecker. To perform anything using CLI, you will have to type a command in the command box and press the kbd:[Enter] key to execute it. + +image:CommandBoxIndication.png[width="600"] [TIP] -A person can have any number of tags (including 0) - -Examples: - -* `add n/John Doe p/98765432 e/johnd@example.com a/John street, block 123, #01-01` -* `add n/Betsy Crowe t/friend e/betsycrowe@example.com a/Newgate Prison p/1234567 t/criminal` - -=== Listing all persons : `list` - -Shows a list of all persons in the address book. + -Format: `list` - -=== Editing a person : `edit` - -Edits an existing person in the address book. + -Format: `edit INDEX [n/NAME] [p/PHONE] [e/EMAIL] [a/ADDRESS] [t/TAG]...` +You can type a command and press `Tab` to auto bring out all the command parameters. + +. Start by typing the `help` command in the command box and press kbd:[Enter] to open the help window. This window contains the information you will need to learn how to use ProgressChecker. +. Next, let's add a teammate's details to your contact list. + +Type **`add`**`n/John Doe p/98765432 e/johnd@example.com m/Computer Science y/2 g/johndoe` + +Teammate John Doe should now be listed in your contact list. + +image:TeammateAddedIndication.png[width="300"] +. After adding all of your teammates' details into ProgressChecker, you will need to load the tasks you're required to do into ProgressChecker. + +Type **`nl`** to bring up the Google account authorization page. Select `Allow`. + +You should see the feedback that a verification code has been received. Return to ProgressChecker. + +[NOTE] +This step in syncing of tasks can take up to 40 seconds. ProgressChecker might be in the state of not responding in the meantime. + +. Once the tasks have been loaded, you can view them. + +Type **`view task`** then **`vt *`** + +You should now be on the Task tab and see a list of learning outcomes. +. When you have completed a learning outcome, you can mark them as complete. + +Type **`ct`**`1` to mark your first learning outcome as complete. +You should now see that the status of the learning outcome has turned green with a tick in the checkbox. +. Now, let's try answering an exercise. + +Type **`view`**`exercise` + +You should now be on the Exercise tab. You can scroll through and see the list of exercises available for week 11. Let's answer question index 11.1.1. + +Type **`ans`**`11.1.1 a` + +You should now see your answer and the suggested answer reflected under question index 11.1.1. +image:ExerciseAnsweredIndication.png[width="600"] +. You can also view exercises from other weeks. +Type **`view`**`exercise 5` + +You should now be seeing week 5's exercises in the Exercise tab. +. After checking your learning progress, you would want to manage your project development. + +Type **`view`**`issues` + +You should now be on the Issues tab. Now, let's login into GitHub. + +Type **`gitlogin`**`gu/YOUR_GITHUB_USERNAME pc/YOUR_GITHUB_PASSWORD r/YOUR_GITHUB_REPO_NAME` + +[NOTE] +Words in `UPPER_CASE` are the parameters to be supplied by you e.g. in `gitlogin gu/YOUR_GITHUB_USERNAME`, `YOUR_GITHUB_USERNAME` is a parameter which can be used as `gitlogin gu/JohnDoe`. +If you have successfully logged in, you should see the opened issues in your specified repository listed in the tab. + +. Next, let's create a GitHub issue to your repository. + +Type **`+issue`**`ti/Implement remark functionality` + +You should now see your newly created issue reflected under the Issues tab as well as under the issue tracker section of your specified GitHub repository. -**** -* Edits the person at the specified `INDEX`. The index refers to the index number shown in the last person listing. The index *must be a positive integer* 1, 2, 3, ... -* At least one of the optional fields must be provided. -* Existing values will be updated to the input values. -* When editing tags, the existing tags of the person will be removed i.e adding of tags is not cumulative. -* You can remove all the person's tags by typing `t/` without specifying any tags after it. -**** +And that is the general workflow you would have when using ProgressChecker each week! All changes you have made previously have all already been automatically saved. If you wish to close ProgressChecker now, you can type **`exit`** to quit. -Examples: +There are more commands available for you to try. Refer to the <<List of All Commands,next section>> for the details of each command. -* `edit 1 p/91234567 e/johndoe@example.com` + -Edits the phone number and email address of the 1st person to be `91234567` and `johndoe@example.com` respectively. -* `edit 2 n/Betsy Crower t/` + -Edits the name of the 2nd person to be `Betsy Crower` and clears all existing tags. +*Notes:* -=== Locating persons by name: `find` +* The commands are case-insensitive. However, for simplicity all the examples have the commands in lower case. +* You can auto-complete any command by pressing tab key. +* Words in `UPPER_CASE` are the parameters to be supplied by the user e.g. in `add n/NAME`, `NAME` is a parameter which can be used as `add n/John Doe`. +* Items in square brackets are optional e.g `n/NAME [t/TAG]` can be used as `n/John Doe t/friend` or as `n/John Doe`. +* Items with `…` after them can be used multiple times including zero times e.g. `[t/TAG]...` can be used as `{nbsp}` (i.e. 0 times), `t/friend`, `t/friend t/family` etc. +* Parameters can be in any order e.g. if the command specifies `n/NAME p/PHONE_NUMBER`, `p/PHONE_NUMBER n/NAME` is also acceptable. -Finds persons whose names contain any of the given keywords. + -Format: `find KEYWORD [MORE_KEYWORDS]` +=== List of All Commands -**** -* The search is case insensitive. e.g `hans` will match `Hans` -* The order of the keywords does not matter. e.g. `Hans Bo` will match `Bo Hans` -* Only the name is searched. -* Only full words will be matched e.g. `Han` will not match `Hans` -* Persons matching at least one keyword will be returned (i.e. `OR` search). e.g. `Hans Bo` will return `Hans Gruber`, `Bo Yang` -**** +==== General -Examples: +===== Helping user with User Guide : `help` OR `h` -* `find John` + -Returns `john` and `John Doe` -* `find Betsy Tim John` + -Returns any person having names `Betsy`, `Tim`, or `John` +You may want to refer to the User Guide when you have a question. You can read it in a separate window. + +*Format:* `help` OR `h` -=== Deleting a person : `delete` +*Examples:* -Deletes the specified person from the address book. + -Format: `delete INDEX` +* `help` +* `h` -**** -* Deletes the person at the specified `INDEX`. -* The index refers to the index number shown in the most recent listing. -* The index *must be a positive integer* 1, 2, 3, ... -**** +// tag::view[] +===== Viewing a different tab : `view` OR `v` `[since v1.5]` -Examples: +You can change the tab view to show either `Profile`, `Task`, `Exercise`, or `Issues`. + +*Format:* `view TYPE` OR `v TYPE` + +image:TabView.png[width="600"] -* `list` + -`delete 2` + -Deletes the 2nd person in the address book. -* `find Betsy` + -`delete 1` + -Deletes the 1st person in the results of the `find` command. +By default, the exercise tab shows exercises from the latest available school week. You can view the exercises in other weeks as well. + +*Format:* `view exercise WEEK_NUMBER` OR `v exercise WEEK_NUMBER` -=== Selecting a person : `select` +*Notes:* -Selects the person identified by the index number used in the last person listing. + -Format: `select INDEX` +* `TYPE` refers to the tab names you see in the GUI: `profile`, `task`, `exercise`, or `issues`. +* `WEEK_NUMBER` refers to the school week number and *must be a positive integer* in the *range of 2 to 11* `2, 3, ..., 11` -**** -* Selects the person and loads the Google search page the person at the specified `INDEX`. -* The index refers to the index number shown in the most recent listing. -* The index *must be a positive integer* `1, 2, 3, ...` -**** +*Examples:* -Examples: +* `view task` +* `view exercise` +* `view exercise 5` +// end::view[] -* `list` + -`select 2` + -Selects the 2nd person in the address book. -* `find Betsy` + -`select 1` + -Selects the 1st person in the results of the `find` command. +// tag::theme[] +===== Toggling theme : `theme` OR `t` -=== Listing entered commands : `history` +You can change the style of the app between a light and dark theme according to your preference. + +*Format:* `theme` OR `t` -Lists all the commands that you have entered in reverse chronological order. + -Format: `history` +*Examples:* -[NOTE] -==== -Pressing the kbd:[↑] and kbd:[↓] arrows will display the previous and next input respectively in the command box. -==== +* `theme` +* `t` +// end::theme[] // tag::undoredo[] -=== Undoing previous command : `undo` +===== Undoing previous command : `undo` OR `u` -Restores the address book to the state before the previous _undoable_ command was executed. + -Format: `undo` +You may type some commands wrongly, or some unexpected changes happen. You can restore the ProgressChecker to the state before the previous _undoable_ command is executed. + +*Format:* `undo` OR `u` -[NOTE] -==== -Undoable commands: those commands that modify the address book's content (`add`, `delete`, `edit` and `clear`). -==== +*Notes:* + +* Undoable commands: those commands that modify the ProgressChecker's content (`add`, `answer`, `delete`, `edit` and `clear`). -Examples: +*Examples:* * `delete 1` + `list` + -`undo` (reverses the `delete 1` command) + +`u` (reverses the `delete 1` command) * `select 1` + `list` + @@ -193,72 +216,717 @@ The `undo` command fails as there are no undoable commands executed previously. * `delete 1` + `clear` + `undo` (reverses the `clear` command) + -`undo` (reverses the `delete 1` command) + +`u` (reverses the `delete 1` command) -=== Redoing the previously undone command : `redo` +===== Redoing the previously undone command : `redo` OR `r` -Reverses the most recent `undo` command. + -Format: `redo` +You can reverse the most recent `undo` command if you want to go back to the previous state. + +*Format:* `redo` OR `r` -Examples: +*Examples:* * `delete 1` + `undo` (reverses the `delete 1` command) + -`redo` (reapplies the `delete 1` command) + +`redo` (reapplies the `delete 1` command) * `delete 1` + -`redo` + +`r` + The `redo` command fails as there are no `undo` commands executed previously. * `delete 1` + `clear` + `undo` (reverses the `clear` command) + `undo` (reverses the `delete 1` command) + -`redo` (reapplies the `delete 1` command) + -`redo` (reapplies the `clear` command) + +`r` (reapplies the `delete 1` command) + +`redo` (reapplies the `clear` command) // end::undoredo[] -=== Clearing all entries : `clear` +===== History viewing the list of entered commands : `history` -Clears all entries from the address book. + -Format: `clear` +You can retrace all the commands that you have entered in reverse chronological order. + +*Format:* `history` -=== Exiting the program : `exit` +[TIP] +You can press the kbd:[↑] and kbd:[↓] arrows. The previous and next input respectively will display in the command box. -Exits the program. + -Format: `exit` +*Example:* -=== Saving the data +* `history` -Address book data are saved in the hard disk automatically after any command that changes the data. + -There is no need to save manually. +===== Refreshing the content : `refresh` OR `rf` `[coming in v2.0]` + +You can refresh the program to update it to the latest content. + +*Format:* `refresh` + +*Examples:* + +* `refresh` +* `rf` + +===== Clearing all entries : `clear` OR `c` -// tag::dataencryption[] -=== Encrypting data files `[coming in v2.0]` +You can clear all information and data inside the ProgressChecker if you want to restore the app. + +*Format:* `clear` OR `c` + +[NOTE] +Running `clear` will remove the exercises in the software as well! If this was not your intent after running the command, you can undo the change as this is an undoable command! To find out how you can achieve this, check out the <<undoing-previous-command-code-undo-code-or-code-u-code,undo>> and <<redoing-the-previously-undone-command-code-redo-code-or-code-r-code,redo>> commands. + +*Examples:* + +* `clear` +* `c` + +===== Exiting the program : `exit` OR `e` + +You can exits the program when you are done with the work. + +*Format:* `exit` OR `e` + +*Examples:* + +* `exit` +* `e` + +===== Back/Forward the browser in ProgressChecker `[coming in v2.0]` + +==== Exercise + +// tag::answer[] +===== Answering an exercise : `answer` OR `ans` `[since v1.4r]` + +You can answer an exercise based on the question index. Your answer will be saved automatically upon answering. + +*Format:* `answer QUESTION_INDEX ANSWER` + +[TIP] +If you accidentally overwrite your answer to an exercise, you can undo the change as this is an undoable command! To find out how you can achieve this, check out the example section for this command or look at the <<undoing-previous-command-code-undo-code-or-code-u-code,undo>> and <<redoing-the-previously-undone-command-code-redo-code-or-code-r-code,redo>> commands. + +*Notes:* + +* `QUESTION_INDEX` refers to the question index shown in the GUI and it must be of the format `WEEK_NUMBER`.`SECTION_NUMBER`.`QUESTION_NUMBER`, e.g. 2.1.1, 3.2.5, 6.1.7 + +image:QuestionIndexFormat.png[width="360"] +[TIP] +You can see if an exercise has been answered by looking at the color of the question index label. The colors are as shown in the image above, where green means it has been answered and red means otherwise. +[NOTE] +An exercise's `SECTION_NUMBER` is directly adopted from https://nus-cs2103-ay1718s2.github.io/website/index.html[CS2103/T's website]. + +*Examples:* + +* `answer 2.1.1 Procedural languages work at simple data structures and functions level` + +* `answer 3.5.2 a. Both are UML diagrams.` + +`u` (reverses the `answer 3.5.2 a. Both are UML diagrams.` command) + +* `answer 3.5.2 a. Both are UML diagrams.` + +`u` (reverses the `answer 3.5.2 a. Both are UML diagrams.` command) + +`r` (reapplies the `answer 3.5.2 a. Both are UML diagrams.` command) +// end::answer[] + +==== Issue Tracker + +//tag::issues[] +===== Logging in to Github: `gitlogin` OR `gl` + +You can login with your Github account and prepare to work with the issues in your team repo. + +*Format:* `gitlogin gu/USERNAME pc/PASSWORD r/REPO` + +OR + +`gl gu/USERNAME pc/PASSWORD r/REPOSITORY` + +[NOTE] +Your git details are not saved in our app for security reasons. Hence, you will need to login every time you start the software. + +You need to login and mention the repo where you need to create/edit issues. + +*Examples:* + +* `gitlogin gu/johndoe pc/dummy123 r/CS2103T/main` + ++ +Given below is the execution outcome of the above command: + + You have successfully authenticated github! + +===== Creating an issue on Github: `+issue` OR `ci` + +You can create an issue in your team repository on github using this command. +*Format:* `+issue ti/TITLE [a/ASSIGNEES]... [ms/MILESTONE] [b/BODY] [l/LABELS]...` + +OR + +`ci ti/TITLE [a/ASSIGNEES]... [ms/MILESTONE] [b/BODY] [l/LABELS]...` + +[NOTE] +An issue has only 'title' field as compulsory. Rest are all optional. + +An issue can have more than one assignees and labels. + +*Examples:* + +* `+issue ti/complete issue a/johndoe ms/v1.1 b/CS2103T is a software engineering module l/type.task l/CS2103T` +* `+issue ti/TestIssue` +* `ci ti/Issue with only body b/test body l/type.test` ++ +Given below is the execution outcome of the above command + + Issue successfully created on Github + +===== Editing an issue on Github: `+issue` OR `ci` + +You can edit an existing issue in the team repository using this command. + +*Format:* `editissue INDEX [ti/TITLE] [a/ASSIGNEES]... [ms/MILESTONE] [b/BODY] [l/LABELS]...` + +OR + +`edi ti/TITLE [a/ASSIGNEES]... [ms/MILESTONE] [b/BODY] [l/LABELS]...` + +[TIP] +If you don't remember the issue number, you can take a look at the issues by using `listissues` command. + +An issue can have more than one assignees and labels. + +*Examples:* + +* `editissue 1 ti/Changed title` +* `editissue ti/changed title b/changed body` +* `edi l/type.task` ++ +Given below is the execution outcome of the above command + + Issue successfully edited on Github + +===== Closing an issue on Github: `-issue` OR `cli` + +You can close the specified issue on github after you have resolved it using this command. + +*Format:* `-issue INDEX` OR `cli INDEX` + +[TIP] +If you don't remember the issue number, you can take a look at the issues by using `listissues` command. + + +[NOTE] +`INDEX` refers to the #INDEX of an issue on github + +*Examples:* + +* `-issue 17` +* `cli 17` ++ +Given below is the execution outcome of the above command + + Issue #17 closed successfully + +===== Reopening an issue on Github: `reopenissue` OR `ri` + +You can reopen a certain issue on github using this command. + +*Format:* `reopenissue INDEX` OR `ri INDEX` + +[TIP] +If you don't remember the issue number, you can take a look at the issues by using `listissues` command. + + +[NOTE] +`INDEX` refers to the #INDEX of an issue on github + +*Examples:* + +* `reopenissue 17` +* `reopenissue 17` ++ +Given below is the execution outcome of the above command + + Issue #17 reopened successfully + +===== Listing github issues: `listissue` OR `lis` + +You can list the github issues using this command. + +*Format:* `listissue STATE` OR `lis STATE` + +[NOTE] +`STATE` refers to the state of the issue, i.e OPEN or CLOSED -_{explain how the user can enable/disable data encryption}_ -// end::dataencryption[] +*Examples:* + +* `listissue OPEN` +* `lis CLOSED` ++ +Given below is the execution outcome of the above command + + All the OPEN issues are being viewed + +===== Logging out of Github: `gitlogout` OR `glo` + +You can logout of github after logging in. + + +[NOTE] +You will get an error message if you try to logout before logging in. + +*Examples:* + +* `gitlogout` +* `glo` ++ +Given below is the execution outcome of the above command + + You have successfully logged out of github + +//end::issues[] + +// tag::tasks[] +==== Task List + +===== Add default task list: `newtasklist` OR `nl` + +You can add the default task list to the ProgressChecker and your Google Tasks to prepare for your work. + +*Format:* `newtasklist` OR `nl` + + +[IMPORTANT] +* This command should be executed before all other task-related commands +* The command requires Internet connection. You may be brought to a login page in your browser. Please do not close the tab without accepting/declining request, +otherwise the application will hang. +* This command will take a long time (roughly 40s) to load all the tasks. Fortunately, you only need to do this command ONCE in the lifetime of the application. +Please do not interrupt when this command is executing. If the app is hanging (showing no response), it means it is still processing. + +*Examples:* + +* `newtasklist` +* `nl` + +===== Viewing the default task list: `viewtask` OR `vt` + +You can view the current default task list in the browser panel in ProgressChecker. There are several FILTER_KEYWORD: +"***" means to show everything, "sub" or "submission" means to show tasks that need submission, "com" or "compulsory" means to show compulsory tasks, +an interger ranging from 1 to 13 means to show tasks in that week. + +*Format:* `viewtask FILTER_KEYWORD` OR `vt FILTER_KEYWORD` + +[IMPORTANT] +* You should NOT call this command before calling `newtasklist` command. +* The command requires Internet connection. You may be brought to a login page in your browser. Please do not close the tab without accepting/declining request, +otherwise the application will hang. + +*Examples:* + +* `viewtask compulsory` +* `viewtask 3` +* `viewtask sub` +* `vt com` +* `vt *` + +The screenshot below shows what you can see with command `vt com`. + +.Result of `vt com` Command +image::viewTaskResult.png[width="800"] + +===== Completing a task: `complete` OR `ct` + +You can mark the task as completed when you finish it. You cannot use undo command to undo it, but can use `reset INDEX` command to obtain the same effect. + +*Format:* `complete INDEX` OR `ct INDEX` + +*Notes:* + +* You can mark the task with index number `INDEX` as completed. +* The index refers to the index number shown in the most recent listing. +* The index *must be a positive integer* 1, 2, 3, ... + +[IMPORTANT] +* You should NOT call this command before calling `newtasklist` command. +* The command requires Internet connection. You may be brought to a login page in your browser. Please do not close the tab without accepting/declining request, +otherwise the application will hang. + +*Examples:* + +* `complete 1` +* `ct 1` + +Tasks that have not been completed are in red box while green boxes mean these tasks are already completed. The screenshot below shows what you can see with command `ct 2` on the compulsory list. + +.Result of `ct 2` Command +image::completeTaskResult.png[width="800"] + +===== Resetting a task as not completed: `reset` OR `rt` + +You can reset a task as not completed when you marked it as completed by mistake. You cannot use undo command to undo it, but can use `complete INDEX` command to obtain the same effect. + +*Format:* `reset INDEX` OR `rt INDEX` + +*Notes:* + +* You can reset the task with index number `INDEX` as incompleted. +* The index refers to the index number shown in the most recent listing. +* The index *must be a positive integer* 1, 2, 3, ... + +[IMPORTANT] +* You should NOT call this command before calling `newtasklist` command. +* The command requires Internet connection. You may be brought to a login page in your browser. Please do not close the tab without accepting/declining request, +otherwise the application will hang. + + *Examples:* + + * `reset 1` + * `rt 1` + +===== Open URL of a task: `goto` OR `go` + +You can visit the webpage of a task when you want to see details and work on it. + +*Format:* `goto INDEX` OR `go INDEX` + +*Notes:* + +* You can visit URL of the task with index number `INDEX` as incompleted. +* The index refers to the index number shown in the most recent listing. +* The index *must be a positive integer* 1, 2, 3, ... + +[IMPORTANT] +* You should NOT call this command before calling `newtasklist` command. +* The command requires Internet connection. You may be brought to a login page in your browser. Please do not close the tab without accepting/declining request, +otherwise the application will hang. + + *Examples:* + + * `goto 1` + * `go 1` + +The screenshot below shows what you can see with command `go 2` on the compulsory list. + +.Result of `ct 2` Command +image::goToTaskUrlResult.png[width="800"] + +===== Send reminder email when a deadline is near `[coming in v2.0]` + +===== View teammates' task list and progress `[coming in v2.0]` + +// end::tasks[] + +==== Teammate + +===== Adding a teammate: `add` OR `a` + +You can add the contact information of a new teammate to the ProgressChecker. + +*Format:* `add n/NAME p/PHONE_NUMBER e/EMAIL g/GITHUB_USERNAME m/MAJOR y/YEAR [t/TAG]...` + +OR + +`a n/NAME p/PHONE_NUMBER e/EMAIL g/GITHUB_USERNAME m/MAJOR y/YEAR [t/TAG]...` + +[TIP] +A teammate can have any number of tags (including 0) + +*Examples:* + +* `add n/John Doe p/98765432 e/johnd@example.com g/johndoeGithub m/Computer Science y/2` +* `a n/John Doe p/98765432 e/johnd@example.com g/johndoeGithub m/Computer Science y/2` +* `add n/Betsy Crowe t/friend e/betsycrowe@example.com g/betsycroweGithub m/Computer Engineering p/1234567 y/3 t/criminal` +* `a n/Betsy Crowe t/friend e/betsycrowe@example.com g/betsycroweGithub m/Information Security y/2 p/1234567 t/criminal` + +===== Editing a teammate : `edit` OR `ed` + +You can edit the information of the certain existing teammate in the ProgressChecker. + +*Format:* `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [g/GITHUB_USERNAME] [m/MAJOR] [y/YEAR] [t/TAG]...` + +OR + +`ed INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [g/GITHUB_USERNAME] [m/MAJOR] [y/YEAR] [t/TAG]...` + +*Notes:* + +* You can edit the information of the teammate at the specified `INDEX`. The index refers to the index number shown in the last teammate listing. The index *must be a positive integer* 1, 2, 3, ... +* At least one of the optional fields must be provided. +* Existing values will be updated to the input values. +* When editing tags, the existing tags of the teammate will be removed i.e adding of tags is not cumulative. +* You can remove all the teammate's tags by typing `t/` without specifying any tags after it. + +*Examples:* + +* `edit 1 p/91234567 e/johndoe@example.com` + +Edits the phone number and email address of the 1st teammate to be `91234567` and `johndoe@example.com` respectively. +* `edit 2 n/Betsy Crower t/` + +Edits the name of the 2nd teammate to be `Betsy Crower` and clears all existing tags. + +// tag::upload[] +===== Uploading profile photo + +You can upload a image from your local PC as a profile photo for yourself or your teammates in ProgressChecker. + +*Format* `upload INDEX PATH` OR `up INDEX PATH` + +*Notes:* + +* You can update the profile photo of the teammate at the specified `INDEX`. The index refers to the index number shown in the last teammate listing. The index *must be a positive integer* 1, 2, 3, ... +* The extension of the image to upload can only be _'jpg'_, _'jepg'_ or _'png'_. +* The local path cannot have white space inside. +* Image to upload will be copied to the contact folder which is inside resources. + +*Examples:* + +* `upload 1 C:\Users\Livian\Desktop\image.png` + +Uploads the image with local path _'C:\Users\Livian\image.png'_ as the profile photo of the first person. ++ +image::uploadPhoto.jpg[width:790] ++ +You will see the following changes when you upload the profile photo successfully: ++ +image::uploadSuccess.jpg[width:790] +// end::upload[] + +===== Listing all teammates : `list` OR `l` + +You can view the list of all your teammates in the ProgressChecker. + +*Format:* `list` OR `l` + +*Examples:* + +* `list` +* `l` + +// tag::upload[] +===== Uploading profile photo + +You can upload a image from your local PC as a profile photo for yourself or your teammates in ProgressChecker. + +*Format* `upload INDEX PATH` OR `up INDEX PATH` + +*Notes:* + +* You can update the profile photo of the teammate at the specified `INDEX`. The index refers to the index number shown in the last teammate listing. The index *must be a positive integer* 1, 2, 3, ... +* The extension of the image to upload can only be _'jpg'_, _'jepg'_ or _'png'_. +* Image to upload will be copied to the contact folder which is inside resources. + +*Examples:* + +* `upload 1 C:\Users\User\Desktop\profilePhoto.png` + +Uploads the image with local path _'C:\Users\User\profilePhoto.png'_ as the profile photo of the first person. +// end::upload[] + +// tag::sort[] +===== Sorting all teammates : `sort` + +You can view the list of all your teammates in the ProgressChecker with their names in alphabetical order. + +*Format:* `sort` + +*Example:* + +* `sort` +// end::sort[] + +===== Finding/Searching teammates by the keywords of name: `find` OR `search` + +You can find the certain teammates with their names contain any of the given keywords. + +*Format:* `find KEYWORD [MORE_KEYWORDS] OR search KEYWORD [MORE_KEYWORDS]` + +*Notes:* + +* The search is case insensitive. e.g `hans` will match `Hans` +* The search is dynamic. As the user types alphabets, the results will be shown without the need to press enter key +* The order of the keywords does not matter. e.g. `Hans Bo` will match `Bo Hans` +* Only the name is searched. +* Only full words will be matched e.g. `Han` will not match `Hans` +* Teammates matching at least one keyword will be returned (i.e. `OR` search). e.g. `Hans Bo` will return `Hans Gruber`, `Bo Yang` + +*Examples:* + +* `find John` + +Returns `john` and `John Doe` +* `search Betsy Tim John` + +Returns any teammate having names `Betsy`, `Tim`, or `John` + +===== Selecting a teammate : `select` OR `s` + +You can select the teammate identified by the index number used in the last teammate listing. + +*Format:* `select INDEX` OR `s INDEX` + +*Notes:* + +* You can select the teammate and loads the Google search page the teammate at the specified `INDEX`. +* The index refers to the index number shown in the most recent listing. +* The index *must be a positive integer* `1, 2, 3, ...` + +*Examples:* + +* `list` + +`select 2` + +Selects the 2nd teammate in the ProgressChecker. +* `find Betsy` + +`s 1` + +Selects the 1st teammate in the results of the `find` command. + +===== Deleting a teammate : `delete` OR `d` + +You can remove the specified teammate from the ProgressChecker. + +*Format:* `delete INDEX` OR `d INDEX` + +*Notes:* + +* You can remove the teammate at the specified `INDEX`. +* The index refers to the index number shown in the most recent listing. +* The index *must be a positive integer* 1, 2, 3, ... + +*Examples:* + +* `list` + +`delete 2` + +Deletes the 2nd teammate in the ProgressChecker. +* `find Betsy` + +`d 1` + +Deletes the 1st teammate in the results of the `find` command. + +=== Saving the data + +Progress Checker data are saved in the hard disk automatically after any command that changes the data. + +There is no need to save manually. == FAQ +You may encounter some questions related to other aspects of ProgressChecker other than what the commands are. This section list some frequently asked questions that you may find useful. + *Q*: How do I transfer my data to another Computer? + -*A*: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous Address Book folder. +*A*: Install the app in the other computer and overwrite the empty data file it creates with the file that contains the data of your previous ProgressChecker folder. + + +*Q*: Will the command work if I type in capital letters? + +*A*: Yes. The commands are case-insensitive. + + +*Q*: Will the changes I made in ProgressChecker still be intact upon closing and reopening the app? + +*A*: Yes. All changes made are saved automatically into the data folder. As long as you do not directly edit the data file, the content will remain the same when you reopen the app. + + +*Q*: How do I close the app? + +*A*: You can either type the `exit` command or click on the 'x' button in the top right corner of the app. == Command Summary -* *Add* `add n/NAME p/PHONE_NUMBER e/EMAIL a/ADDRESS [t/TAG]...` + -e.g. `add n/James Ho p/22224444 e/jamesho@example.com a/123, Clementi Rd, 1234665 t/friend t/colleague` -* *Clear* : `clear` -* *Delete* : `delete INDEX` + -e.g. `delete 3` -* *Edit* : `edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [a/ADDRESS] [t/TAG]...` + -e.g. `edit 2 n/James Lee e/jameslee@example.com` -* *Find* : `find KEYWORD [MORE_KEYWORDS]` + -e.g. `find James Jake` -* *List* : `list` -* *Help* : `help` -* *Select* : `select INDEX` + -e.g.`select 2` -* *History* : `history` -* *Undo* : `undo` -* *Redo* : `redo` +If you're looking for a quick reference list of commands without all the details, the section below summarises all the available commands. + +|=== +|Function | Command | Example +|*Add* +|`add n/NAME p/PHONE_NUMBER e/EMAIL g/USERNAME m/MAJOR y/YEAR [t/TAG]...` + +OR `a n/NAME p/PHONE_NUMBER e/EMAIL g/USERNAME m/MAJOR y/YEAR [t/TAG]...` +|add n/James Ho p/22224444 e/jamesho@example.com g/JamesGithub m/Computer Science y/2 t/friend t/colleague + +|*Answer* +|`answer INDEX ANSWER` +|answer 2.1.1 Procedural languages work at simple data structures and functions level + +|*Clear* +|`clear` OR `c` +| + +|*Close issue* +|`-issue INDEX` OR `cli INDEX` +|-issue 3 + +cli 1 + +|*Create issue* +|`+issue ti/TITLE [a/ASSIGNEES]... [ms/MILESTONE] [b/BODY] [l/LABELS]...` + + OR + + `ci ti/TITLE [a/ASSIGNEES]... [ms/MILESTONE] [b/BODY] [l/LABELS]...` +|+issue ti/complete issue a/johndoe ms/v1.1 b/CS2103T is a software engineering module l/type.task l/CS2103T + +|*Delete* +|`delete INDEX` OR `d INDEX` +|delete 3 + +|*Edit* +|`edit INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [m/MAJOR] [y/YEAR] [t/TAG]...` + +OR `ed INDEX [n/NAME] [p/PHONE_NUMBER] [e/EMAIL] [m/MAJOR] [y/YEAR] [t/TAG]...` +|edit 2 n/James Lee e/jameslee@example.com + +|*Editissue* +|`editissue INDEX [t/TITLE] [b/BODY] [ms/MILESTONE] [a/ASSIGNEE] ... [l/label]...` + +OR `edi INDEX [t/TITLE] [b/BODY] [ms/MILESTONE] [a/ASSIGNEE] ... [l/label]...` +|editissue 2 ti/new title l/bug + +|*Exit* +|`exit` OR `e` +| + +|*Find* +|`find KEYWORD [MORE_KEYWORDS]` +|find James Jake + +|*Help* +|`help` OR `h` +| + +|*History* +|`history` +| + +|*List* +|`list` Or `l` +| + +|*List issue* +|`listissue STATE` Or `lis STATE` +|listissue OPEN + +|*Log in to Github* +|`gitlogin OR gl` +| + +|*Log out of Github* +|`gitlogout OR glo` +| + +|*Add Default Task List* +|`newtasklist` OR `nl` +| + +|*Complete Task* +|`complete INDEX` OR `ct INDEX` +|complete 1 + +ct 1 + +|*Reset Task* +|`reset INDEX` OR `rt INDEX` +|reset 1 + +rt 1 + +|*Open Task URL* +|`goto INDEX` OR `go INDEX` +|goto 1 + +go 1 + +|*View Task List* +|`viewtask FILTER_KEYWORD` OR `vt FILTER_KEYWORD` +|viewtask 5 + +rt 5 + +viewtask sub + +vt com + +|*Redo* +|`redo` OR `r` +| + +|*Refresh* +|`refresh` OR `rf` +| + +|*Reopen issue* +|`reopenissue INDEX` OR `ri INDEX` +|reopenissue 3 + +ri 1 + +|*Reset a task* +|`reset OR rt` +| + +|*Search* +|`search KEYWORD [MORE_KEYWORDS]` +|search James Jake + +|*Select* +|`select INDEX` OR `s INDEX` +|select 2 + +|*Sort* +|`sort` +| + +|*Theme* +|`theme` OR `t` +| + +|*Undo* +|`undo` OR `u` +| + +|*Upload* +|`upload INDEX PATH` OR `up INDEX PATH` +|upload 1 C:\Users\User\Desktop\profile_photo.jpg + +|*View tab* +|`view TYPE` OR `v TYPE` +|view task + +view exercise + +|*View exercises by week* +|`view exercise WEEK_NUMBER` OR `v exercise WEEK_NUMBER` +|view exercise 5 + +|*View task* +|`viewtask OR vt` +| + +|=== + +Back to the <<Introduction,TOP>> diff --git a/docs/UsingCheckstyle.adoc b/docs/UsingCheckstyle.adoc index ba9b76d3918d..da5b10e6d4b0 100644 --- a/docs/UsingCheckstyle.adoc +++ b/docs/UsingCheckstyle.adoc @@ -18,7 +18,7 @@ Restart the IDE to complete the installation. + image::checkstyle-idea-scan-scope.png[width="500"] . Click the plus sign under `Configuration File` -. Enter an arbitrary description e.g. addressbook +. Enter an arbitrary description e.g. progresschecker . Select `Use a local Checkstyle file` . Use the checkstyle configuration file found at `config/checkstyle/checkstyle.xml` . Click `Next` > `Finish` diff --git a/docs/UsingGradle.adoc b/docs/UsingGradle.adoc index 84c9dd193491..369da26a405c 100644 --- a/docs/UsingGradle.adoc +++ b/docs/UsingGradle.adoc @@ -41,7 +41,7 @@ When running a Gradle task, Gradle will try to figure out if the task needs runn == Creating the JAR file * *`shadowJar`* + -Creates the `addressbook.jar` file in the `build/jar` folder, _if the current file is outdated_. + +Creates the `progresschecker.jar` file in the `build/jar` folder, _if the current file is outdated_. + e.g. `./gradlew shadowJar` **** @@ -80,9 +80,9 @@ The set of code style rules implemented can be found in `config/checkstyle/check * **`allTests`** + Runs all tests. * **`guiTests`** + -Runs all tests in the `seedu.address.ui` and `systemtests` package +Runs all tests in the `seedu.progresschecker.ui` and `systemtests` package * **`nonGuiTests`** + -Runs all non-GUI tests in the `seedu.address` +Runs all non-GUI tests in the `seedu.progresschecker` package * **`headless`** + Sets the test mode as _headless_. The mode is effective for that Gradle run only so it should be combined with other test tasks. diff --git a/docs/diagrams/~$HighLevelSequenceDiagrams.pptx b/docs/diagrams/~$HighLevelSequenceDiagrams.pptx new file mode 100644 index 000000000000..0715f7d4c410 Binary files /dev/null and b/docs/diagrams/~$HighLevelSequenceDiagrams.pptx differ diff --git a/docs/images/CommandBoxIndication.png b/docs/images/CommandBoxIndication.png new file mode 100644 index 000000000000..42e9531cd24e Binary files /dev/null and b/docs/images/CommandBoxIndication.png differ diff --git a/docs/images/ExerciseAnsweredIndication.png b/docs/images/ExerciseAnsweredIndication.png new file mode 100644 index 000000000000..0e6619790476 Binary files /dev/null and b/docs/images/ExerciseAnsweredIndication.png differ diff --git a/docs/images/HighLevelSDforViewTaskListCommand.png b/docs/images/HighLevelSDforViewTaskListCommand.png new file mode 100644 index 000000000000..920731b9f9ae Binary files /dev/null and b/docs/images/HighLevelSDforViewTaskListCommand.png differ diff --git a/docs/images/LogicClassDiagram.png b/docs/images/LogicClassDiagram.png index e0b17014bb47..c700bcd16492 100644 Binary files a/docs/images/LogicClassDiagram.png and b/docs/images/LogicClassDiagram.png differ diff --git a/docs/images/QuestionIndexFormat.png b/docs/images/QuestionIndexFormat.png new file mode 100644 index 000000000000..189382c1dd2d Binary files /dev/null and b/docs/images/QuestionIndexFormat.png differ diff --git a/docs/images/QuestionIndexIndication.png b/docs/images/QuestionIndexIndication.png new file mode 100644 index 000000000000..ba33cba29551 Binary files /dev/null and b/docs/images/QuestionIndexIndication.png differ diff --git a/docs/images/SDforAnswerExercise.png b/docs/images/SDforAnswerExercise.png new file mode 100644 index 000000000000..6ac6d6c74476 Binary files /dev/null and b/docs/images/SDforAnswerExercise.png differ diff --git a/docs/images/SDforAnswerExerciseEventHandling.png b/docs/images/SDforAnswerExerciseEventHandling.png new file mode 100644 index 000000000000..c1c5b6587e43 Binary files /dev/null and b/docs/images/SDforAnswerExerciseEventHandling.png differ diff --git a/docs/images/SDforUploadPhoto.png b/docs/images/SDforUploadPhoto.png new file mode 100644 index 000000000000..a53fc3c63e2f Binary files /dev/null and b/docs/images/SDforUploadPhoto.png differ diff --git a/docs/images/SDforViewExercise.png b/docs/images/SDforViewExercise.png new file mode 100644 index 000000000000..5890c3500a58 Binary files /dev/null and b/docs/images/SDforViewExercise.png differ diff --git a/docs/images/SDforViewExerciseEventHandling.png b/docs/images/SDforViewExerciseEventHandling.png new file mode 100644 index 000000000000..4d586c8c7897 Binary files /dev/null and b/docs/images/SDforViewExerciseEventHandling.png differ diff --git a/docs/images/SDforViewTaskListCommand.png b/docs/images/SDforViewTaskListCommand.png new file mode 100644 index 000000000000..0a76897819db Binary files /dev/null and b/docs/images/SDforViewTaskListCommand.png differ diff --git a/docs/images/TabView.png b/docs/images/TabView.png new file mode 100644 index 000000000000..b031816c6190 Binary files /dev/null and b/docs/images/TabView.png differ diff --git a/docs/images/TeammateAddedIndication.png b/docs/images/TeammateAddedIndication.png new file mode 100644 index 000000000000..4d73df8fcd00 Binary files /dev/null and b/docs/images/TeammateAddedIndication.png differ diff --git a/docs/images/Ui.png b/docs/images/Ui.png index 5ec9c527b49c..7c89d56042b4 100644 Binary files a/docs/images/Ui.png and b/docs/images/Ui.png differ diff --git a/docs/images/adityaa1998.png b/docs/images/adityaa1998.png new file mode 100644 index 000000000000..b1f82452799e Binary files /dev/null and b/docs/images/adityaa1998.png differ diff --git a/docs/images/cis.png b/docs/images/cis.png new file mode 100644 index 000000000000..e48835f2cdb1 Binary files /dev/null and b/docs/images/cis.png differ diff --git a/docs/images/close issue.png b/docs/images/close issue.png new file mode 100644 index 000000000000..cbdafc86110f Binary files /dev/null and b/docs/images/close issue.png differ diff --git a/docs/images/closeissue.png b/docs/images/closeissue.png new file mode 100644 index 000000000000..655f6a6e6410 Binary files /dev/null and b/docs/images/closeissue.png differ diff --git a/docs/images/completeTaskResult.png b/docs/images/completeTaskResult.png new file mode 100644 index 000000000000..a6cc2e0bdcbb Binary files /dev/null and b/docs/images/completeTaskResult.png differ diff --git a/docs/images/createIssue.png b/docs/images/createIssue.png new file mode 100644 index 000000000000..3ad7b24050de Binary files /dev/null and b/docs/images/createIssue.png differ diff --git a/docs/images/download1.png b/docs/images/download1.png new file mode 100644 index 000000000000..8c834c4797bf Binary files /dev/null and b/docs/images/download1.png differ diff --git a/docs/images/download2.png b/docs/images/download2.png new file mode 100644 index 000000000000..294ad7acc11f Binary files /dev/null and b/docs/images/download2.png differ diff --git a/docs/images/download3.png b/docs/images/download3.png new file mode 100644 index 000000000000..aa193c797ded Binary files /dev/null and b/docs/images/download3.png differ diff --git a/docs/images/edwardksg.png b/docs/images/edwardksg.png new file mode 100644 index 000000000000..fdebe325602b Binary files /dev/null and b/docs/images/edwardksg.png differ diff --git a/docs/images/exerciseTab.jpg b/docs/images/exerciseTab.jpg new file mode 100644 index 000000000000..cddc684fded1 Binary files /dev/null and b/docs/images/exerciseTab.jpg differ diff --git a/docs/images/finalcreateissue.png b/docs/images/finalcreateissue.png new file mode 100644 index 000000000000..9f6b2162b386 Binary files /dev/null and b/docs/images/finalcreateissue.png differ diff --git a/docs/images/gitdetails.png b/docs/images/gitdetails.png new file mode 100644 index 000000000000..b38857302aa4 Binary files /dev/null and b/docs/images/gitdetails.png differ diff --git a/docs/images/gitlogin.png b/docs/images/gitlogin.png new file mode 100644 index 000000000000..dde071794470 Binary files /dev/null and b/docs/images/gitlogin.png differ diff --git a/docs/images/gitlogin1.png b/docs/images/gitlogin1.png new file mode 100644 index 000000000000..eb0f2ba309be Binary files /dev/null and b/docs/images/gitlogin1.png differ diff --git a/docs/images/gitloginsuccess.png b/docs/images/gitloginsuccess.png new file mode 100644 index 000000000000..06482742df0e Binary files /dev/null and b/docs/images/gitloginsuccess.png differ diff --git a/docs/images/gitlogout success.png b/docs/images/gitlogout success.png new file mode 100644 index 000000000000..3672cc4d83e3 Binary files /dev/null and b/docs/images/gitlogout success.png differ diff --git a/docs/images/goToTaskUrlResult.png b/docs/images/goToTaskUrlResult.png new file mode 100644 index 000000000000..0b65edc7f77c Binary files /dev/null and b/docs/images/goToTaskUrlResult.png differ diff --git a/docs/images/inekox3.png b/docs/images/inekox3.png new file mode 100644 index 000000000000..f3bd29c215db Binary files /dev/null and b/docs/images/inekox3.png differ diff --git a/docs/images/issueTab.jpg b/docs/images/issueTab.jpg new file mode 100644 index 000000000000..5261ba3769eb Binary files /dev/null and b/docs/images/issueTab.jpg differ diff --git a/docs/images/issueobject.png b/docs/images/issueobject.png new file mode 100644 index 000000000000..1b5a5b845431 Binary files /dev/null and b/docs/images/issueobject.png differ diff --git a/docs/images/livian1107.png b/docs/images/livian1107.png new file mode 100644 index 000000000000..81bd778d308a Binary files /dev/null and b/docs/images/livian1107.png differ diff --git a/docs/images/profileTab.jpg b/docs/images/profileTab.jpg new file mode 100644 index 000000000000..1030c396a92b Binary files /dev/null and b/docs/images/profileTab.jpg differ diff --git a/docs/images/reopen issue.png b/docs/images/reopen issue.png new file mode 100644 index 000000000000..3b8b1b79690d Binary files /dev/null and b/docs/images/reopen issue.png differ diff --git a/docs/images/setup1.png b/docs/images/setup1.png new file mode 100644 index 000000000000..c7c0a64bdb8c Binary files /dev/null and b/docs/images/setup1.png differ diff --git a/docs/images/setup2.png b/docs/images/setup2.png new file mode 100644 index 000000000000..dd5020979738 Binary files /dev/null and b/docs/images/setup2.png differ diff --git a/docs/images/setup3.png b/docs/images/setup3.png new file mode 100644 index 000000000000..e94122c637d5 Binary files /dev/null and b/docs/images/setup3.png differ diff --git a/docs/images/setup5.png b/docs/images/setup5.png new file mode 100644 index 000000000000..99e314803797 Binary files /dev/null and b/docs/images/setup5.png differ diff --git a/docs/images/taskTab.jpg b/docs/images/taskTab.jpg new file mode 100644 index 000000000000..9a9581273405 Binary files /dev/null and b/docs/images/taskTab.jpg differ diff --git a/docs/images/uploadPhoto.jpg b/docs/images/uploadPhoto.jpg new file mode 100644 index 000000000000..1eb3aa9111f9 Binary files /dev/null and b/docs/images/uploadPhoto.jpg differ diff --git a/docs/images/uploadSuccess.jpg b/docs/images/uploadSuccess.jpg new file mode 100644 index 000000000000..5abd4a7465e5 Binary files /dev/null and b/docs/images/uploadSuccess.jpg differ diff --git a/docs/images/viewTaskResult.png b/docs/images/viewTaskResult.png new file mode 100644 index 000000000000..35dee382ed57 Binary files /dev/null and b/docs/images/viewTaskResult.png differ diff --git a/docs/team/Adityaa1998.adoc b/docs/team/Adityaa1998.adoc new file mode 100644 index 000000000000..94faf52ae39d --- /dev/null +++ b/docs/team/Adityaa1998.adoc @@ -0,0 +1,64 @@ += Aditya Agarwal - Project Portfolio +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: ProgressChecker + +--- + +== Overview + +ProgressChecker is a desktop ProgressChecker application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. + +This portfolio is to collect and combine my contribution towards the development of ProgressChecker. This portfolio gives a glimpse of the software programming knowledge I have imbibed during the course of CS2103T. In this portfolio I have described few enhancements I have made to the application with the skills learnt in this software engineering course. + +== Summary of contributions + +* *Major enhancement*: added *the ability to track issues on github* +** What it does: allows the teammates in a project to track issues on the team repository on github from the ProgressChecker application. +** Justification: This feature improves the teammate's efficiency to manage issues on github by giving him the ability to create, edit, close issues with minimal effort and concentrate on bigger problems. This way users can easily manage issues on github with a few key strokes, and without having to go through the complications of using a GUI. +** Highlights: This enhancement makes use of the complete software architecture by touching upon all the four design components. This enhancement needed addition of many new commands, some which are : `gitlogin`, `createissue`, `editissue`, `closeissue`. +** Credits: Development of this feature made of use of the http://github-api.kohsuke.org/[Github Java API](org.eclipse.egit.github.core). + +* *Minor enhancement*: Added a dynamic search feature which dynamically filters the contact list of a user without the user having to press enter every time. + +* *Code contributed*: [https://github.com/CS2103JAN2018-T09-B3/main/blob/master/collated/functional/adityaa1998.md[Functional code]] [https://github.com[Test code]] _{give links to collated code files}_ + +* *Other contributions*: + +** Features +*** Auto-complete: added functionality for the commands to be autocompleted on `TAB` press. (Pull request https://github.com/CS2103JAN2018-T09-B3/main/pull/108[#108]). +** Project management: +*** Managed releases `v1.2` - `v1.5rc` (3 releases) on GitHub +*** Managed milestones `v1.1` - `v1.5rc` (5 releases) on GitHub +** Enhancements to existing features: +*** Added alias to the command (Pull request https://github.com/CS2103JAN2018-T09-B3/main/pull/8[#8]) +** Documentation: +*** Made the user guide more user friendly and updated it with the all the relevant features. (Pull requests https://github.com/CS2103JAN2018-T09-B3/main/pull/41[#41], https://github.com/CS2103JAN2018-T09-B3/main/pull/158[#158], https://github.com/CS2103JAN2018-T09-B3/main/pull/176[#176]). +** Community: +*** PRs reviewed (with non-trivial review comments): https://github.com[#12], https://github.com[#32], https://github.com[#19], https://github.com[#42] +*** Reported bugs and suggestions for other teams in the class (examples: https://github.com/CS2103JAN2018-F14-B4/issues/142[#142], https://github.com/CS2103JAN2018-F14-B4/issues/144[#144], https://github.com/CS2103JAN2018-F14-B4/issues/147[#147]) +** Tools: +*** Integrated a http://github-api.kohsuke.org/[Github Java API](org.eclipse.egit.github.core) to the project https://github.com/CS2103JAN2018-T09-B3/main[ProgressChecker]. + +_{you can add/remove categories in the list above}_ + +== Contributions to the User Guide + + +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== + +include::../UserGuide.adoc[tag=issues] + +== Contributions to the Developer Guide + +|=== +|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ +|=== + +include::../DeveloperGuide.adoc[tag=search] + +include::../DeveloperGuide.adoc[tag=github] + diff --git a/docs/team/EdwardKSG.adoc b/docs/team/EdwardKSG.adoc new file mode 100644 index 000000000000..6f181698e5f8 --- /dev/null +++ b/docs/team/EdwardKSG.adoc @@ -0,0 +1,59 @@ += Kang Anmin - Project Portfolio +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: ProgressChecker + +--- + +== Overview + +ProgressChecker is *a desktop application that enables CS2103/T students to track their learning progress and project development on a centralized platform* throughout the module. The current release is meant to be used alongside with CS2103/T module website. ProgressChecker has a JavaFX GUI, but users will interact with it via CLI for efficiency. + +== Summary of contributions + +* *Major enhancement*: added *a task list which contains tasks assigned by the lecturers of CS2103/T* +** *What it does:* allows students to view tasks by week and other filters, mark each task as completed/pending, view web page of each task through URL and check progress (in fraction and percentage). +** *Justification:* this feature solves a significant issue that students faced: they are confused when viewing the collapsible nested list on module website and possibly miss some tasks residing in an inner layer. Also, there are a significant number of tasks each week, which is hard to finish in one go. Thus, they may forget what they have covered when they resume after leaving it for a while. They also cannot tell how far are they away from completing all tasks. +** *Highlights:* This enhancement touches on all 4 components in the code base, which requires a good understanding of the existing architecture of the project prototype. Furthermore, it is implemented with Google Task API and cloud services which requires a lot of exploration. Using external libraries also tests the skills of integrating external resources with current code base. +** *Credits:* Some of the implementation was inspired by the existing code base that has implemented numerous commands to manage a list of contacts in an address book. The utilization of Google Tasks API borrows my experience with Google Calendar API when doing an Android application in another project organized by NUS School of Computing called Orbital. + +* *Minor enhancement*: added multiple fields of person and updated all relevant commands. These fields are used in my teammates' enhancement. added a progress bar to give a graphic and more intuitive view of the level of completeness of tasks in the task list. + +* *Code contributed*: [https://github.com/CS2103JAN2018-T09-B3/main/blob/master/collated/functional/EdwardKSG.md[Functional code]] [https://github.com/CS2103JAN2018-T09-B3/main/blob/master/collated/test/EdwardKSG.md[Test code]] [https://github.com/CS2103JAN2018-T09-B3/main/blob/master/collated/unused/EdwardKSG-unused.md[Unused code]] +** Non-collatable: add field and tests: https://github.com/CS2103JAN2018-T09-B3/main/pull/121[#121], add field and tests: https://github.com/CS2103JAN2018-T09-B3/main/pull/20[#20], refactor: https://github.com/CS2103JAN2018-T09-B3/main/pull/57[#57] + +* *Other contributions*: did refactor of code, packages and documentations to fit our new project and hide legacy of the prototype project to make it an independent project. + +** Project management: +*** Created team repository +*** Managed issues and pull requests with a wide variety of labels and different milestones. +*** Checked completeness of milestone, closed milestone and made releases (v1.4, v1.5rc). +** Enhancements to existing features: +*** Added a new browser panel to allow showing two different pages side by side https://github.com/CS2103JAN2018-T09-B3/main/pull/181[#181] +*** Changed error message of *delete* command to give a clearer suggestion of what the users are doing wrong https://github.com/CS2103JAN2018-T09-B3/main/pull/213[#213] +** Documentation: +*** Wrote the first version of introduction in User and Developer Guide +*** Contributed to User Stories and Use Cases https://github.com/CS2103JAN2018-T09-B3/main/pull/111[#111] +** Community: +*** Pull requests reviewed (with non-trivial review comments): https://github.com/CS2103JAN2018-T09-B3/main/pull/212[#212] https://github.com/CS2103JAN2018-T09-B3/main/pull/209[#209] https://github.com/CS2103JAN2018-T09-B3/main/pull/26[#26] https://github.com/CS2103JAN2018-T09-B3/main/pull/8[#8] +*** Reported bugs and suggestions for other teams in the class https://github.com/CS2103JAN2018-F14-B4/main/issues/149[#149] https://github.com/CS2103JAN2018-F14-B4/main/issues/146[#146] +** Tools: +*** Integrated a https://developers.google.com/google-apps/tasks/[Google Tasks Java API] to the project https://github.com/CS2103JAN2018-T09-B3/main[ProgressChecker]. + +== Contributions to the User Guide + + +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== + +include::../UserGuide.adoc[tag=tasks] + +== Contributions to the Developer Guide + +|=== +|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ +|=== + +include::../DeveloperGuide.adoc[tag=tasks] diff --git a/docs/team/johndoe.adoc b/docs/team/johndoe.adoc deleted file mode 100644 index 0dfa757e454b..000000000000 --- a/docs/team/johndoe.adoc +++ /dev/null @@ -1,71 +0,0 @@ -= John Doe - Project Portfolio -:imagesDir: ../images -:stylesDir: ../stylesheets - -== PROJECT: AddressBook - Level 4 - ---- - -== Overview - -AddressBook - Level 4 is a desktop address book application used for teaching Software Engineering principles. The user interacts with it using a CLI, and it has a GUI created with JavaFX. It is written in Java, and has about 10 kLoC. - -== Summary of contributions - -* *Major enhancement*: added *the ability to undo/redo previous commands* -** What it does: allows the user to undo all previous commands one at a time. Preceding undo commands can be reversed by using the redo command. -** Justification: This feature improves the product significantly because a user can make mistakes in commands and the app should provide a convenient way to rectify them. -** Highlights: This enhancement affects existing commands and commands to be added in future. It required an in-depth analysis of design alternatives. The implementation too was challenging as it required changes to existing commands. -** Credits: _{mention here if you reused any code/ideas from elsewhere or if a third-party library is heavily used in the feature so that a reader can make a more accurate judgement of how much effort went into the feature}_ - -* *Minor enhancement*: added a history command that allows the user to navigate to previous commands using up/down keys. - -* *Code contributed*: [https://github.com[Functional code]] [https://github.com[Test code]] _{give links to collated code files}_ - -* *Other contributions*: - -** Project management: -*** Managed releases `v1.3` - `v1.5rc` (3 releases) on GitHub -** Enhancements to existing features: -*** Updated the GUI color scheme (Pull requests https://github.com[#33], https://github.com[#34]) -*** Wrote additional tests for existing features to increase coverage from 88% to 92% (Pull requests https://github.com[#36], https://github.com[#38]) -** Documentation: -*** Did cosmetic tweaks to existing contents of the User Guide: https://github.com[#14] -** Community: -*** PRs reviewed (with non-trivial review comments): https://github.com[#12], https://github.com[#32], https://github.com[#19], https://github.com[#42] -*** Contributed to forum discussions (examples: https://github.com[1], https://github.com[2], https://github.com[3], https://github.com[4]) -*** Reported bugs and suggestions for other teams in the class (examples: https://github.com[1], https://github.com[2], https://github.com[3]) -*** Some parts of the history feature I added was adopted by several other class mates (https://github.com[1], https://github.com[2]) -** Tools: -*** Integrated a third party library (Natty) to the project (https://github.com[#42]) -*** Integrated a new Github plugin (CircleCI) to the team repo - -_{you can add/remove categories in the list above}_ - -== Contributions to the User Guide - - -|=== -|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ -|=== - -include::../UserGuide.adoc[tag=undoredo] - -include::../UserGuide.adoc[tag=dataencryption] - -== Contributions to the Developer Guide - -|=== -|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ -|=== - -include::../DeveloperGuide.adoc[tag=undoredo] - -include::../DeveloperGuide.adoc[tag=dataencryption] - - -== PROJECT: PowerPointLabs - ---- - -_{Optionally, you may include other projects in your portfolio.}_ diff --git a/docs/team/liwen.adoc b/docs/team/liwen.adoc new file mode 100644 index 000000000000..e45d6a153ea1 --- /dev/null +++ b/docs/team/liwen.adoc @@ -0,0 +1,61 @@ += Lai Liwen - Project Portfolio +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: ProgressChecker + +--- + +== Overview + +ProgressChecker is *a desktop application for CS2103/T students to have a centralized hub that tracks their learning progress and project development* throughout the module. The current release is meant to be used alongside with CS2103/T module website. ProgressChecker has a JavaFX GUI, but users will interact with it via CLI for efficiency. + +== Summary of contributions + +* *Major enhancement*: added *the ability to upload profile photo* +** *What it does:* allows students to upload any photos as their own or teammates's profile photo. +** *Justification:* this feature solves the issue that students may have teammates with the same name. Profile photo can help students to recognize each other better. +** *Highlights:* This enhancement touches on all 4 components in the code base. It required an in-depth analysis of the existing architecture on how each components work with one another in order to implement it in an optimal way. +** *Credits:* The format and style of the codes are heavily inspired by the existing code, like `addCommand`. + +* *Minor enhancement*: add a theme command to change the theme of ProgressChecker + +* *Extra feature*: improve the UI from current version with another new theme + +* *Extra feature*: add a sort command to display teammates with their name in lexicological order + +* *Code contributed*: [https://github.com/CS2103JAN2018-T09-B3/main/blob/master/collated/functional/Livian1107.md[Functional code]] [https://github.com/CS2103JAN2018-T09-B3/main/blob/master/collated/test/Livian1107.md[Test code]] + +* *Other contributions*: + +** Project management: +*** Updated the documentations and assured them are in the consistent format +*** Helped to review and merge the pull requests on our team repo +** Enhancements to existing features: +*** Improved the UI https://github.com/CS2103JAN2018-T09-B3/main/pull/103[#226] +** Documentation: +*** Updated first draft for User Guide and Developer Guide https://github.com/CS2103JAN2018-T09-B3/main/pull/117[#115]https://github.com/CS2103JAN2018-T09-B3/main/pull/123[#116] +*** Updated documentations after the consultation https://github.com/CS2103JAN2018-T09-B3/main/pull/209[#227] https://github.com/CS2103JAN2018-T09-B3/main/pull/203[#227] +** Community: +*** Pull requests reviewed (with non-trivial review comments): https://github.com/CS2103JAN2018-T09-B3/main/pull/220[#220] + +== Contributions to the User Guide + + +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== + +include::../UserGuide.adoc[tag=upload] + +include::../UserGuide.adoc[tag=sort] + +include::../UserGuide.adoc[tag=theme] + +== Contributions to the Developer Guide + +|=== +|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ +|=== + +include::../DeveloperGuide.adoc[tag=upload] diff --git a/docs/team/yeeru.adoc b/docs/team/yeeru.adoc new file mode 100644 index 000000000000..e8fa50c24fd3 --- /dev/null +++ b/docs/team/yeeru.adoc @@ -0,0 +1,58 @@ += Koh Yee Ru - Project Portfolio +:imagesDir: ../images +:stylesDir: ../stylesheets + +== PROJECT: ProgressChecker + +--- + +== Overview + +ProgressChecker is *a desktop application for CS2103/T students to have a centralized hub that tracks their learning progress and project development* throughout the module. The current release is meant to be used alongside with CS2103/T module website. ProgressChecker has a JavaFX GUI, but users will interact with it via CLI for efficiency. + +== Summary of contributions + +* *Major enhancement*: added *the ability to view and answer weekly exercises* taken from CS2103/T module website +** *What it does:* allows students to view exercises by week and save their answers down for it. Answers are stored automatically and loaded the next time the software runs. +** *Justification:* students face significant issues in finding or saving their exercises answers down in the module website for revision purposes; now they can easily do those with this feature +** *Highlights:* This enhancement touches on all 4 components in the code base. It required an in-depth analysis of the existing architecture on how each components work with one another in order to implement it in an optimal way. +** *Credits:* Much of the implementation was heavily inspired by the existing code base that has originally implemented the listing, modifying and storing of persons. + +* *Minor enhancement*: added a view command that allows the user to navigate between different window tabs of information + +* *Code contributed*: [https://github.com/CS2103JAN2018-T09-B3/main/blob/master/collated/functional/iNekox3.md[Functional code]] [https://github.com/CS2103JAN2018-T09-B3/main/blob/master/collated/test/iNekox3.md[Test code]] + +* *Other contributions*: + +** Project management: +*** Managed listing of type.epic and type.stories issues on issue tracker to kickstart the team's type.task issues management +** Enhancements to existing features: +*** Updated tags in PersonCard to have differing colors https://github.com/CS2103JAN2018-T09-B3/main/pull/2[#2] +*** Changed GUI color scheme https://github.com/CS2103JAN2018-T09-B3/main/pull/243[#243] +** Documentation: +*** Added prefaces to User and Developer Guide for each section https://github.com/CS2103JAN2018-T09-B3/main/pull/124[#124] +*** Contributed to User Stories and Use Cases https://github.com/CS2103JAN2018-T09-B3/main/pull/262[#262] https://github.com/CS2103JAN2018-T09-B3/main/pull/76[#76] +** Community: +*** Pull requests reviewed (with non-trivial review comments): https://github.com/CS2103JAN2018-T09-B3/main/pull/209[#209] https://github.com/CS2103JAN2018-T09-B3/main/pull/100[#100] https://github.com/CS2103JAN2018-T09-B3/main/pull/17[#17] +*** Reported bugs and suggestions for other teams in the class https://github.com/CS2103JAN2018-W14-B1/main/issues/139[#139] https://github.com/CS2103JAN2018-W14-B1/main/issues/137[#137] + +== Contributions to the User Guide + + +|=== +|_Given below are sections I contributed to the User Guide. They showcase my ability to write documentation targeting end-users._ +|=== + +include::../UserGuide.adoc[tag=view] + +include::../UserGuide.adoc[tag=answer] + +== Contributions to the Developer Guide + +|=== +|_Given below are sections I contributed to the Developer Guide. They showcase my ability to write technical documentation and the technical depth of my contributions to the project._ +|=== + +include::../DeveloperGuide.adoc[tag=view] + +include::../DeveloperGuide.adoc[tag=answer] diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 4d04417b7daf..839056c1e6bb 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.12-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip diff --git a/src/main/java/seedu/address/commons/events/model/AddressBookChangedEvent.java b/src/main/java/seedu/address/commons/events/model/AddressBookChangedEvent.java deleted file mode 100644 index 7db9b5c48ed6..000000000000 --- a/src/main/java/seedu/address/commons/events/model/AddressBookChangedEvent.java +++ /dev/null @@ -1,19 +0,0 @@ -package seedu.address.commons.events.model; - -import seedu.address.commons.events.BaseEvent; -import seedu.address.model.ReadOnlyAddressBook; - -/** Indicates the AddressBook in the model has changed*/ -public class AddressBookChangedEvent extends BaseEvent { - - public final ReadOnlyAddressBook data; - - public AddressBookChangedEvent(ReadOnlyAddressBook data) { - this.data = data; - } - - @Override - public String toString() { - return "number of persons " + data.getPersonList().size() + ", number of tags " + data.getTagList().size(); - } -} diff --git a/src/main/java/seedu/address/commons/util/FileUtil.java b/src/main/java/seedu/address/commons/util/FileUtil.java deleted file mode 100644 index 73575030d7dd..000000000000 --- a/src/main/java/seedu/address/commons/util/FileUtil.java +++ /dev/null @@ -1,93 +0,0 @@ -package seedu.address.commons.util; - -import static seedu.address.commons.util.AppUtil.checkArgument; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; - -/** - * Writes and reads files - */ -public class FileUtil { - - private static final String CHARSET = "UTF-8"; - - public static boolean isFileExists(File file) { - return file.exists() && file.isFile(); - } - - /** - * Creates a file if it does not exist along with its missing parent directories. - * @throws IOException if the file or directory cannot be created. - */ - public static void createIfMissing(File file) throws IOException { - if (!isFileExists(file)) { - createFile(file); - } - } - - /** - * Creates a file if it does not exist along with its missing parent directories - * - * @return true if file is created, false if file already exists - */ - public static boolean createFile(File file) throws IOException { - if (file.exists()) { - return false; - } - - createParentDirsOfFile(file); - - return file.createNewFile(); - } - - /** - * Creates the given directory along with its parent directories - * - * @param dir the directory to be created; assumed not null - * @throws IOException if the directory or a parent directory cannot be created - */ - public static void createDirs(File dir) throws IOException { - if (!dir.exists() && !dir.mkdirs()) { - throw new IOException("Failed to make directories of " + dir.getName()); - } - } - - /** - * Creates parent directories of file if it has a parent directory - */ - public static void createParentDirsOfFile(File file) throws IOException { - File parentDir = file.getParentFile(); - - if (parentDir != null) { - createDirs(parentDir); - } - } - - /** - * Assumes file exists - */ - public static String readFromFile(File file) throws IOException { - return new String(Files.readAllBytes(file.toPath()), CHARSET); - } - - /** - * Writes given string to a file. - * Will create the file if it does not exist yet. - */ - public static void writeToFile(File file, String content) throws IOException { - Files.write(file.toPath(), content.getBytes(CHARSET)); - } - - /** - * Converts a string to a platform-specific file path - * @param pathWithForwardSlash A String representing a file path but using '/' as the separator - * @return {@code pathWithForwardSlash} but '/' replaced with {@code File.separator} - */ - public static String getPath(String pathWithForwardSlash) { - checkArgument(pathWithForwardSlash.contains("/")); - return pathWithForwardSlash.replace("/", File.separator); - } - -} diff --git a/src/main/java/seedu/address/logic/LogicManager.java b/src/main/java/seedu/address/logic/LogicManager.java deleted file mode 100644 index 9f6846bdfc74..000000000000 --- a/src/main/java/seedu/address/logic/LogicManager.java +++ /dev/null @@ -1,57 +0,0 @@ -package seedu.address.logic; - -import java.util.logging.Logger; - -import javafx.collections.ObservableList; -import seedu.address.commons.core.ComponentManager; -import seedu.address.commons.core.LogsCenter; -import seedu.address.logic.commands.Command; -import seedu.address.logic.commands.CommandResult; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.logic.parser.AddressBookParser; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.Model; -import seedu.address.model.person.Person; - -/** - * The main LogicManager of the app. - */ -public class LogicManager extends ComponentManager implements Logic { - private final Logger logger = LogsCenter.getLogger(LogicManager.class); - - private final Model model; - private final CommandHistory history; - private final AddressBookParser addressBookParser; - private final UndoRedoStack undoRedoStack; - - public LogicManager(Model model) { - this.model = model; - history = new CommandHistory(); - addressBookParser = new AddressBookParser(); - undoRedoStack = new UndoRedoStack(); - } - - @Override - public CommandResult execute(String commandText) throws CommandException, ParseException { - logger.info("----------------[USER COMMAND][" + commandText + "]"); - try { - Command command = addressBookParser.parseCommand(commandText); - command.setData(model, history, undoRedoStack); - CommandResult result = command.execute(); - undoRedoStack.push(command); - return result; - } finally { - history.add(commandText); - } - } - - @Override - public ObservableList<Person> getFilteredPersonList() { - return model.getFilteredPersonList(); - } - - @Override - public ListElementPointer getHistorySnapshot() { - return new ListElementPointer(history.getHistory()); - } -} diff --git a/src/main/java/seedu/address/logic/parser/AddCommandParser.java b/src/main/java/seedu/address/logic/parser/AddCommandParser.java deleted file mode 100644 index 3c729b388554..000000000000 --- a/src/main/java/seedu/address/logic/parser/AddCommandParser.java +++ /dev/null @@ -1,65 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; - -import java.util.Set; -import java.util.stream.Stream; - -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.logic.commands.AddCommand; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * Parses input arguments and creates a new AddCommand object - */ -public class AddCommandParser implements Parser<AddCommand> { - - /** - * Parses the given {@code String} of arguments in the context of the AddCommand - * and returns an AddCommand object for execution. - * @throws ParseException if the user input does not conform the expected format - */ - public AddCommand parse(String args) throws ParseException { - ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); - - if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_ADDRESS, PREFIX_PHONE, PREFIX_EMAIL) - || !argMultimap.getPreamble().isEmpty()) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); - } - - try { - Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME)).get(); - Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE)).get(); - Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL)).get(); - Address address = ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS)).get(); - Set<Tag> tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); - - Person person = new Person(name, phone, email, address, tagList); - - return new AddCommand(person); - } catch (IllegalValueException ive) { - throw new ParseException(ive.getMessage(), ive); - } - } - - /** - * Returns true if none of the prefixes contains empty {@code Optional} values in the given - * {@code ArgumentMultimap}. - */ - private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { - return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); - } - -} diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java deleted file mode 100644 index b7d57f5db86a..000000000000 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ /dev/null @@ -1,92 +0,0 @@ -package seedu.address.logic.parser; - -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import seedu.address.logic.commands.AddCommand; -import seedu.address.logic.commands.ClearCommand; -import seedu.address.logic.commands.Command; -import seedu.address.logic.commands.DeleteCommand; -import seedu.address.logic.commands.EditCommand; -import seedu.address.logic.commands.ExitCommand; -import seedu.address.logic.commands.FindCommand; -import seedu.address.logic.commands.HelpCommand; -import seedu.address.logic.commands.HistoryCommand; -import seedu.address.logic.commands.ListCommand; -import seedu.address.logic.commands.RedoCommand; -import seedu.address.logic.commands.SelectCommand; -import seedu.address.logic.commands.UndoCommand; -import seedu.address.logic.parser.exceptions.ParseException; - -/** - * Parses user input. - */ -public class AddressBookParser { - - /** - * Used for initial separation of command word and args. - */ - private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?<commandWord>\\S+)(?<arguments>.*)"); - - /** - * Parses user input into command for execution. - * - * @param userInput full user input string - * @return the command based on the user input - * @throws ParseException if the user input does not conform the expected format - */ - public Command parseCommand(String userInput) throws ParseException { - final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim()); - if (!matcher.matches()) { - throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); - } - - final String commandWord = matcher.group("commandWord"); - final String arguments = matcher.group("arguments"); - switch (commandWord) { - - case AddCommand.COMMAND_WORD: - return new AddCommandParser().parse(arguments); - - case EditCommand.COMMAND_WORD: - return new EditCommandParser().parse(arguments); - - case SelectCommand.COMMAND_WORD: - return new SelectCommandParser().parse(arguments); - - case DeleteCommand.COMMAND_WORD: - return new DeleteCommandParser().parse(arguments); - - case ClearCommand.COMMAND_WORD: - return new ClearCommand(); - - case FindCommand.COMMAND_WORD: - return new FindCommandParser().parse(arguments); - - case ListCommand.COMMAND_WORD: - return new ListCommand(); - - case HistoryCommand.COMMAND_WORD: - return new HistoryCommand(); - - case ExitCommand.COMMAND_WORD: - return new ExitCommand(); - - case HelpCommand.COMMAND_WORD: - return new HelpCommand(); - - case UndoCommand.COMMAND_WORD: - return new UndoCommand(); - - case RedoCommand.COMMAND_WORD: - return new RedoCommand(); - - default: - throw new ParseException(MESSAGE_UNKNOWN_COMMAND); - } - } - -} diff --git a/src/main/java/seedu/address/logic/parser/CliSyntax.java b/src/main/java/seedu/address/logic/parser/CliSyntax.java deleted file mode 100644 index 75b1a9bf1190..000000000000 --- a/src/main/java/seedu/address/logic/parser/CliSyntax.java +++ /dev/null @@ -1,15 +0,0 @@ -package seedu.address.logic.parser; - -/** - * Contains Command Line Interface (CLI) syntax definitions common to multiple commands - */ -public class CliSyntax { - - /* Prefix definitions */ - public static final Prefix PREFIX_NAME = new Prefix("n/"); - public static final Prefix PREFIX_PHONE = new Prefix("p/"); - public static final Prefix PREFIX_EMAIL = new Prefix("e/"); - public static final Prefix PREFIX_ADDRESS = new Prefix("a/"); - public static final Prefix PREFIX_TAG = new Prefix("t/"); - -} diff --git a/src/main/java/seedu/address/logic/parser/ParserUtil.java b/src/main/java/seedu/address/logic/parser/ParserUtil.java deleted file mode 100644 index 5d6d4ae3f7b1..000000000000 --- a/src/main/java/seedu/address/logic/parser/ParserUtil.java +++ /dev/null @@ -1,168 +0,0 @@ -package seedu.address.logic.parser; - -import static java.util.Objects.requireNonNull; - -import java.util.Collection; -import java.util.HashSet; -import java.util.Optional; -import java.util.Set; - -import seedu.address.commons.core.index.Index; -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.commons.util.StringUtil; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; - -/** - * Contains utility methods used for parsing strings in the various *Parser classes. - * {@code ParserUtil} contains methods that take in {@code Optional} as parameters. However, it goes against Java's - * convention (see https://stackoverflow.com/a/39005452) as {@code Optional} should only be used a return type. - * Justification: The methods in concern receive {@code Optional} return values from other methods as parameters and - * return {@code Optional} values based on whether the parameters were present. Therefore, it is redundant to unwrap the - * initial {@code Optional} before passing to {@code ParserUtil} as a parameter and then re-wrap it into an - * {@code Optional} return value inside {@code ParserUtil} methods. - */ -public class ParserUtil { - - public static final String MESSAGE_INVALID_INDEX = "Index is not a non-zero unsigned integer."; - public static final String MESSAGE_INSUFFICIENT_PARTS = "Number of parts must be more than 1."; - - /** - * Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be - * trimmed. - * @throws IllegalValueException if the specified index is invalid (not non-zero unsigned integer). - */ - public static Index parseIndex(String oneBasedIndex) throws IllegalValueException { - String trimmedIndex = oneBasedIndex.trim(); - if (!StringUtil.isNonZeroUnsignedInteger(trimmedIndex)) { - throw new IllegalValueException(MESSAGE_INVALID_INDEX); - } - return Index.fromOneBased(Integer.parseInt(trimmedIndex)); - } - - /** - * Parses a {@code String name} into a {@code Name}. - * Leading and trailing whitespaces will be trimmed. - * - * @throws IllegalValueException if the given {@code name} is invalid. - */ - public static Name parseName(String name) throws IllegalValueException { - requireNonNull(name); - String trimmedName = name.trim(); - if (!Name.isValidName(trimmedName)) { - throw new IllegalValueException(Name.MESSAGE_NAME_CONSTRAINTS); - } - return new Name(trimmedName); - } - - /** - * Parses a {@code Optional<String> name} into an {@code Optional<Name>} if {@code name} is present. - * See header comment of this class regarding the use of {@code Optional} parameters. - */ - public static Optional<Name> parseName(Optional<String> name) throws IllegalValueException { - requireNonNull(name); - return name.isPresent() ? Optional.of(parseName(name.get())) : Optional.empty(); - } - - /** - * Parses a {@code String phone} into a {@code Phone}. - * Leading and trailing whitespaces will be trimmed. - * - * @throws IllegalValueException if the given {@code phone} is invalid. - */ - public static Phone parsePhone(String phone) throws IllegalValueException { - requireNonNull(phone); - String trimmedPhone = phone.trim(); - if (!Phone.isValidPhone(trimmedPhone)) { - throw new IllegalValueException(Phone.MESSAGE_PHONE_CONSTRAINTS); - } - return new Phone(trimmedPhone); - } - - /** - * Parses a {@code Optional<String> phone} into an {@code Optional<Phone>} if {@code phone} is present. - * See header comment of this class regarding the use of {@code Optional} parameters. - */ - public static Optional<Phone> parsePhone(Optional<String> phone) throws IllegalValueException { - requireNonNull(phone); - return phone.isPresent() ? Optional.of(parsePhone(phone.get())) : Optional.empty(); - } - - /** - * Parses a {@code String address} into an {@code Address}. - * Leading and trailing whitespaces will be trimmed. - * - * @throws IllegalValueException if the given {@code address} is invalid. - */ - public static Address parseAddress(String address) throws IllegalValueException { - requireNonNull(address); - String trimmedAddress = address.trim(); - if (!Address.isValidAddress(trimmedAddress)) { - throw new IllegalValueException(Address.MESSAGE_ADDRESS_CONSTRAINTS); - } - return new Address(trimmedAddress); - } - - /** - * Parses a {@code Optional<String> address} into an {@code Optional<Address>} if {@code address} is present. - * See header comment of this class regarding the use of {@code Optional} parameters. - */ - public static Optional<Address> parseAddress(Optional<String> address) throws IllegalValueException { - requireNonNull(address); - return address.isPresent() ? Optional.of(parseAddress(address.get())) : Optional.empty(); - } - - /** - * Parses a {@code String email} into an {@code Email}. - * Leading and trailing whitespaces will be trimmed. - * - * @throws IllegalValueException if the given {@code email} is invalid. - */ - public static Email parseEmail(String email) throws IllegalValueException { - requireNonNull(email); - String trimmedEmail = email.trim(); - if (!Email.isValidEmail(trimmedEmail)) { - throw new IllegalValueException(Email.MESSAGE_EMAIL_CONSTRAINTS); - } - return new Email(trimmedEmail); - } - - /** - * Parses a {@code Optional<String> email} into an {@code Optional<Email>} if {@code email} is present. - * See header comment of this class regarding the use of {@code Optional} parameters. - */ - public static Optional<Email> parseEmail(Optional<String> email) throws IllegalValueException { - requireNonNull(email); - return email.isPresent() ? Optional.of(parseEmail(email.get())) : Optional.empty(); - } - - /** - * Parses a {@code String tag} into a {@code Tag}. - * Leading and trailing whitespaces will be trimmed. - * - * @throws IllegalValueException if the given {@code tag} is invalid. - */ - public static Tag parseTag(String tag) throws IllegalValueException { - requireNonNull(tag); - String trimmedTag = tag.trim(); - if (!Tag.isValidTagName(trimmedTag)) { - throw new IllegalValueException(Tag.MESSAGE_TAG_CONSTRAINTS); - } - return new Tag(trimmedTag); - } - - /** - * Parses {@code Collection<String> tags} into a {@code Set<Tag>}. - */ - public static Set<Tag> parseTags(Collection<String> tags) throws IllegalValueException { - requireNonNull(tags); - final Set<Tag> tagSet = new HashSet<>(); - for (String tagName : tags) { - tagSet.add(parseTag(tagName)); - } - return tagSet; - } -} diff --git a/src/main/java/seedu/address/model/AddressBook.java b/src/main/java/seedu/address/model/AddressBook.java deleted file mode 100644 index f8d0260de159..000000000000 --- a/src/main/java/seedu/address/model/AddressBook.java +++ /dev/null @@ -1,187 +0,0 @@ -package seedu.address.model; - -import static java.util.Objects.requireNonNull; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.stream.Collectors; - -import javafx.collections.ObservableList; -import seedu.address.model.person.Person; -import seedu.address.model.person.UniquePersonList; -import seedu.address.model.person.exceptions.DuplicatePersonException; -import seedu.address.model.person.exceptions.PersonNotFoundException; -import seedu.address.model.tag.Tag; -import seedu.address.model.tag.UniqueTagList; - -/** - * Wraps all data at the address-book level - * Duplicates are not allowed (by .equals comparison) - */ -public class AddressBook implements ReadOnlyAddressBook { - - private final UniquePersonList persons; - private final UniqueTagList tags; - - /* - * The 'unusual' code block below is an non-static initialization block, sometimes used to avoid duplication - * between constructors. See https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html - * - * Note that non-static init blocks are not recommended to use. There are other ways to avoid duplication - * among constructors. - */ - { - persons = new UniquePersonList(); - tags = new UniqueTagList(); - } - - public AddressBook() {} - - /** - * Creates an AddressBook using the Persons and Tags in the {@code toBeCopied} - */ - public AddressBook(ReadOnlyAddressBook toBeCopied) { - this(); - resetData(toBeCopied); - } - - //// list overwrite operations - - public void setPersons(List<Person> persons) throws DuplicatePersonException { - this.persons.setPersons(persons); - } - - public void setTags(Set<Tag> tags) { - this.tags.setTags(tags); - } - - /** - * Resets the existing data of this {@code AddressBook} with {@code newData}. - */ - public void resetData(ReadOnlyAddressBook newData) { - requireNonNull(newData); - setTags(new HashSet<>(newData.getTagList())); - List<Person> syncedPersonList = newData.getPersonList().stream() - .map(this::syncWithMasterTagList) - .collect(Collectors.toList()); - - try { - setPersons(syncedPersonList); - } catch (DuplicatePersonException e) { - throw new AssertionError("AddressBooks should not have duplicate persons"); - } - } - - //// person-level operations - - /** - * Adds a person to the address book. - * Also checks the new person's tags and updates {@link #tags} with any new tags found, - * and updates the Tag objects in the person to point to those in {@link #tags}. - * - * @throws DuplicatePersonException if an equivalent person already exists. - */ - public void addPerson(Person p) throws DuplicatePersonException { - Person person = syncWithMasterTagList(p); - // TODO: the tags master list will be updated even though the below line fails. - // This can cause the tags master list to have additional tags that are not tagged to any person - // in the person list. - persons.add(person); - } - - /** - * Replaces the given person {@code target} in the list with {@code editedPerson}. - * {@code AddressBook}'s tag list will be updated with the tags of {@code editedPerson}. - * - * @throws DuplicatePersonException if updating the person's details causes the person to be equivalent to - * another existing person in the list. - * @throws PersonNotFoundException if {@code target} could not be found in the list. - * - * @see #syncWithMasterTagList(Person) - */ - public void updatePerson(Person target, Person editedPerson) - throws DuplicatePersonException, PersonNotFoundException { - requireNonNull(editedPerson); - - Person syncedEditedPerson = syncWithMasterTagList(editedPerson); - // TODO: the tags master list will be updated even though the below line fails. - // This can cause the tags master list to have additional tags that are not tagged to any person - // in the person list. - persons.setPerson(target, syncedEditedPerson); - } - - /** - * Updates the master tag list to include tags in {@code person} that are not in the list. - * @return a copy of this {@code person} such that every tag in this person points to a Tag object in the master - * list. - */ - private Person syncWithMasterTagList(Person person) { - final UniqueTagList personTags = new UniqueTagList(person.getTags()); - tags.mergeFrom(personTags); - - // Create map with values = tag object references in the master list - // used for checking person tag references - final Map<Tag, Tag> masterTagObjects = new HashMap<>(); - tags.forEach(tag -> masterTagObjects.put(tag, tag)); - - // Rebuild the list of person tags to point to the relevant tags in the master tag list. - final Set<Tag> correctTagReferences = new HashSet<>(); - personTags.forEach(tag -> correctTagReferences.add(masterTagObjects.get(tag))); - return new Person( - person.getName(), person.getPhone(), person.getEmail(), person.getAddress(), correctTagReferences); - } - - /** - * Removes {@code key} from this {@code AddressBook}. - * @throws PersonNotFoundException if the {@code key} is not in this {@code AddressBook}. - */ - public boolean removePerson(Person key) throws PersonNotFoundException { - if (persons.remove(key)) { - return true; - } else { - throw new PersonNotFoundException(); - } - } - - //// tag-level operations - - public void addTag(Tag t) throws UniqueTagList.DuplicateTagException { - tags.add(t); - } - - //// util methods - - @Override - public String toString() { - return persons.asObservableList().size() + " persons, " + tags.asObservableList().size() + " tags"; - // TODO: refine later - } - - @Override - public ObservableList<Person> getPersonList() { - return persons.asObservableList(); - } - - @Override - public ObservableList<Tag> getTagList() { - return tags.asObservableList(); - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof AddressBook // instanceof handles nulls - && this.persons.equals(((AddressBook) other).persons) - && this.tags.equalsOrderInsensitive(((AddressBook) other).tags)); - } - - @Override - public int hashCode() { - // use this method for custom fields hashing instead of implementing your own - return Objects.hash(persons, tags); - } -} diff --git a/src/main/java/seedu/address/model/Model.java b/src/main/java/seedu/address/model/Model.java deleted file mode 100644 index 4a6079ce0199..000000000000 --- a/src/main/java/seedu/address/model/Model.java +++ /dev/null @@ -1,48 +0,0 @@ -package seedu.address.model; - -import java.util.function.Predicate; - -import javafx.collections.ObservableList; -import seedu.address.model.person.Person; -import seedu.address.model.person.exceptions.DuplicatePersonException; -import seedu.address.model.person.exceptions.PersonNotFoundException; - -/** - * The API of the Model component. - */ -public interface Model { - /** {@code Predicate} that always evaluate to true */ - Predicate<Person> PREDICATE_SHOW_ALL_PERSONS = unused -> true; - - /** Clears existing backing model and replaces with the provided new data. */ - void resetData(ReadOnlyAddressBook newData); - - /** Returns the AddressBook */ - ReadOnlyAddressBook getAddressBook(); - - /** Deletes the given person. */ - void deletePerson(Person target) throws PersonNotFoundException; - - /** Adds the given person */ - void addPerson(Person person) throws DuplicatePersonException; - - /** - * Replaces the given person {@code target} with {@code editedPerson}. - * - * @throws DuplicatePersonException if updating the person's details causes the person to be equivalent to - * another existing person in the list. - * @throws PersonNotFoundException if {@code target} could not be found in the list. - */ - void updatePerson(Person target, Person editedPerson) - throws DuplicatePersonException, PersonNotFoundException; - - /** Returns an unmodifiable view of the filtered person list */ - ObservableList<Person> getFilteredPersonList(); - - /** - * Updates the filter of the filtered person list to filter by the given {@code predicate}. - * @throws NullPointerException if {@code predicate} is null. - */ - void updateFilteredPersonList(Predicate<Person> predicate); - -} diff --git a/src/main/java/seedu/address/model/ModelManager.java b/src/main/java/seedu/address/model/ModelManager.java deleted file mode 100644 index 22a7d0eb3f4d..000000000000 --- a/src/main/java/seedu/address/model/ModelManager.java +++ /dev/null @@ -1,119 +0,0 @@ -package seedu.address.model; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; - -import java.util.function.Predicate; -import java.util.logging.Logger; - -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import javafx.collections.transformation.FilteredList; -import seedu.address.commons.core.ComponentManager; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.events.model.AddressBookChangedEvent; -import seedu.address.model.person.Person; -import seedu.address.model.person.exceptions.DuplicatePersonException; -import seedu.address.model.person.exceptions.PersonNotFoundException; - -/** - * Represents the in-memory model of the address book data. - * All changes to any model should be synchronized. - */ -public class ModelManager extends ComponentManager implements Model { - private static final Logger logger = LogsCenter.getLogger(ModelManager.class); - - private final AddressBook addressBook; - private final FilteredList<Person> filteredPersons; - - /** - * Initializes a ModelManager with the given addressBook and userPrefs. - */ - public ModelManager(ReadOnlyAddressBook addressBook, UserPrefs userPrefs) { - super(); - requireAllNonNull(addressBook, userPrefs); - - logger.fine("Initializing with address book: " + addressBook + " and user prefs " + userPrefs); - - this.addressBook = new AddressBook(addressBook); - filteredPersons = new FilteredList<>(this.addressBook.getPersonList()); - } - - public ModelManager() { - this(new AddressBook(), new UserPrefs()); - } - - @Override - public void resetData(ReadOnlyAddressBook newData) { - addressBook.resetData(newData); - indicateAddressBookChanged(); - } - - @Override - public ReadOnlyAddressBook getAddressBook() { - return addressBook; - } - - /** Raises an event to indicate the model has changed */ - private void indicateAddressBookChanged() { - raise(new AddressBookChangedEvent(addressBook)); - } - - @Override - public synchronized void deletePerson(Person target) throws PersonNotFoundException { - addressBook.removePerson(target); - indicateAddressBookChanged(); - } - - @Override - public synchronized void addPerson(Person person) throws DuplicatePersonException { - addressBook.addPerson(person); - updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); - indicateAddressBookChanged(); - } - - @Override - public void updatePerson(Person target, Person editedPerson) - throws DuplicatePersonException, PersonNotFoundException { - requireAllNonNull(target, editedPerson); - - addressBook.updatePerson(target, editedPerson); - indicateAddressBookChanged(); - } - - //=========== Filtered Person List Accessors ============================================================= - - /** - * Returns an unmodifiable view of the list of {@code Person} backed by the internal list of - * {@code addressBook} - */ - @Override - public ObservableList<Person> getFilteredPersonList() { - return FXCollections.unmodifiableObservableList(filteredPersons); - } - - @Override - public void updateFilteredPersonList(Predicate<Person> predicate) { - requireNonNull(predicate); - filteredPersons.setPredicate(predicate); - } - - @Override - public boolean equals(Object obj) { - // short circuit if same object - if (obj == this) { - return true; - } - - // instanceof handles nulls - if (!(obj instanceof ModelManager)) { - return false; - } - - // state check - ModelManager other = (ModelManager) obj; - return addressBook.equals(other.addressBook) - && filteredPersons.equals(other.filteredPersons); - } - -} diff --git a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java b/src/main/java/seedu/address/model/ReadOnlyAddressBook.java deleted file mode 100644 index 1f4e49a37d67..000000000000 --- a/src/main/java/seedu/address/model/ReadOnlyAddressBook.java +++ /dev/null @@ -1,24 +0,0 @@ -package seedu.address.model; - -import javafx.collections.ObservableList; -import seedu.address.model.person.Person; -import seedu.address.model.tag.Tag; - -/** - * Unmodifiable view of an address book - */ -public interface ReadOnlyAddressBook { - - /** - * Returns an unmodifiable view of the persons list. - * This list will not contain any duplicate persons. - */ - ObservableList<Person> getPersonList(); - - /** - * Returns an unmodifiable view of the tags list. - * This list will not contain any duplicate tags. - */ - ObservableList<Tag> getTagList(); - -} diff --git a/src/main/java/seedu/address/model/person/Address.java b/src/main/java/seedu/address/model/person/Address.java deleted file mode 100644 index 5e981f07790a..000000000000 --- a/src/main/java/seedu/address/model/person/Address.java +++ /dev/null @@ -1,58 +0,0 @@ -package seedu.address.model.person; - -import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; - -/** - * Represents a Person's address in the address book. - * Guarantees: immutable; is valid as declared in {@link #isValidAddress(String)} - */ -public class Address { - - public static final String MESSAGE_ADDRESS_CONSTRAINTS = - "Person addresses can take any values, and it should not be blank"; - - /* - * The first character of the address must not be a whitespace, - * otherwise " " (a blank string) becomes a valid input. - */ - public static final String ADDRESS_VALIDATION_REGEX = "[^\\s].*"; - - public final String value; - - /** - * Constructs an {@code Address}. - * - * @param address A valid address. - */ - public Address(String address) { - requireNonNull(address); - checkArgument(isValidAddress(address), MESSAGE_ADDRESS_CONSTRAINTS); - this.value = address; - } - - /** - * Returns true if a given string is a valid person email. - */ - public static boolean isValidAddress(String test) { - return test.matches(ADDRESS_VALIDATION_REGEX); - } - - @Override - public String toString() { - return value; - } - - @Override - public boolean equals(Object other) { - return other == this // short circuit if same object - || (other instanceof Address // instanceof handles nulls - && this.value.equals(((Address) other).value)); // state check - } - - @Override - public int hashCode() { - return value.hashCode(); - } - -} diff --git a/src/main/java/seedu/address/model/util/SampleDataUtil.java b/src/main/java/seedu/address/model/util/SampleDataUtil.java deleted file mode 100644 index aea96bfb31f3..000000000000 --- a/src/main/java/seedu/address/model/util/SampleDataUtil.java +++ /dev/null @@ -1,67 +0,0 @@ -package seedu.address.model.util; - -import java.util.HashSet; -import java.util.Set; - -import seedu.address.model.AddressBook; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.person.exceptions.DuplicatePersonException; -import seedu.address.model.tag.Tag; - -/** - * Contains utility methods for populating {@code AddressBook} with sample data. - */ -public class SampleDataUtil { - public static Person[] getSamplePersons() { - return new Person[] { - new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@example.com"), - new Address("Blk 30 Geylang Street 29, #06-40"), - getTagSet("friends")), - new Person(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@example.com"), - new Address("Blk 30 Lorong 3 Serangoon Gardens, #07-18"), - getTagSet("colleagues", "friends")), - new Person(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@example.com"), - new Address("Blk 11 Ang Mo Kio Street 74, #11-04"), - getTagSet("neighbours")), - new Person(new Name("David Li"), new Phone("91031282"), new Email("lidavid@example.com"), - new Address("Blk 436 Serangoon Gardens Street 26, #16-43"), - getTagSet("family")), - new Person(new Name("Irfan Ibrahim"), new Phone("92492021"), new Email("irfan@example.com"), - new Address("Blk 47 Tampines Street 20, #17-35"), - getTagSet("classmates")), - new Person(new Name("Roy Balakrishnan"), new Phone("92624417"), new Email("royb@example.com"), - new Address("Blk 45 Aljunied Street 85, #11-31"), - getTagSet("colleagues")) - }; - } - - public static ReadOnlyAddressBook getSampleAddressBook() { - try { - AddressBook sampleAb = new AddressBook(); - for (Person samplePerson : getSamplePersons()) { - sampleAb.addPerson(samplePerson); - } - return sampleAb; - } catch (DuplicatePersonException e) { - throw new AssertionError("sample data cannot contain duplicate persons", e); - } - } - - /** - * Returns a tag set containing the list of strings given. - */ - public static Set<Tag> getTagSet(String... strings) { - HashSet<Tag> tags = new HashSet<>(); - for (String s : strings) { - tags.add(new Tag(s)); - } - - return tags; - } - -} diff --git a/src/main/java/seedu/address/storage/AddressBookStorage.java b/src/main/java/seedu/address/storage/AddressBookStorage.java deleted file mode 100644 index cf5b527c063a..000000000000 --- a/src/main/java/seedu/address/storage/AddressBookStorage.java +++ /dev/null @@ -1,44 +0,0 @@ -package seedu.address.storage; - -import java.io.IOException; -import java.util.Optional; - -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; - -/** - * Represents a storage for {@link seedu.address.model.AddressBook}. - */ -public interface AddressBookStorage { - - /** - * Returns the file path of the data file. - */ - String getAddressBookFilePath(); - - /** - * Returns AddressBook data as a {@link ReadOnlyAddressBook}. - * Returns {@code Optional.empty()} if storage file is not found. - * @throws DataConversionException if the data in storage is not in the expected format. - * @throws IOException if there was any problem when reading from the storage. - */ - Optional<ReadOnlyAddressBook> readAddressBook() throws DataConversionException, IOException; - - /** - * @see #getAddressBookFilePath() - */ - Optional<ReadOnlyAddressBook> readAddressBook(String filePath) throws DataConversionException, IOException; - - /** - * Saves the given {@link ReadOnlyAddressBook} to the storage. - * @param addressBook cannot be null. - * @throws IOException if there was any problem writing to the file. - */ - void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException; - - /** - * @see #saveAddressBook(ReadOnlyAddressBook) - */ - void saveAddressBook(ReadOnlyAddressBook addressBook, String filePath) throws IOException; - -} diff --git a/src/main/java/seedu/address/storage/Storage.java b/src/main/java/seedu/address/storage/Storage.java deleted file mode 100644 index c0881a5a6483..000000000000 --- a/src/main/java/seedu/address/storage/Storage.java +++ /dev/null @@ -1,38 +0,0 @@ -package seedu.address.storage; - -import java.io.IOException; -import java.util.Optional; - -import seedu.address.commons.events.model.AddressBookChangedEvent; -import seedu.address.commons.events.storage.DataSavingExceptionEvent; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.UserPrefs; - -/** - * API of the Storage component - */ -public interface Storage extends AddressBookStorage, UserPrefsStorage { - - @Override - Optional<UserPrefs> readUserPrefs() throws DataConversionException, IOException; - - @Override - void saveUserPrefs(UserPrefs userPrefs) throws IOException; - - @Override - String getAddressBookFilePath(); - - @Override - Optional<ReadOnlyAddressBook> readAddressBook() throws DataConversionException, IOException; - - @Override - void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException; - - /** - * Saves the current version of the Address Book to the hard disk. - * Creates the data file if it is missing. - * Raises {@link DataSavingExceptionEvent} if there was an error during saving. - */ - void handleAddressBookChangedEvent(AddressBookChangedEvent abce); -} diff --git a/src/main/java/seedu/address/storage/StorageManager.java b/src/main/java/seedu/address/storage/StorageManager.java deleted file mode 100644 index 53967b391a5a..000000000000 --- a/src/main/java/seedu/address/storage/StorageManager.java +++ /dev/null @@ -1,92 +0,0 @@ -package seedu.address.storage; - -import java.io.IOException; -import java.util.Optional; -import java.util.logging.Logger; - -import com.google.common.eventbus.Subscribe; - -import seedu.address.commons.core.ComponentManager; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.events.model.AddressBookChangedEvent; -import seedu.address.commons.events.storage.DataSavingExceptionEvent; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.UserPrefs; - -/** - * Manages storage of AddressBook data in local storage. - */ -public class StorageManager extends ComponentManager implements Storage { - - private static final Logger logger = LogsCenter.getLogger(StorageManager.class); - private AddressBookStorage addressBookStorage; - private UserPrefsStorage userPrefsStorage; - - - public StorageManager(AddressBookStorage addressBookStorage, UserPrefsStorage userPrefsStorage) { - super(); - this.addressBookStorage = addressBookStorage; - this.userPrefsStorage = userPrefsStorage; - } - - // ================ UserPrefs methods ============================== - - @Override - public String getUserPrefsFilePath() { - return userPrefsStorage.getUserPrefsFilePath(); - } - - @Override - public Optional<UserPrefs> readUserPrefs() throws DataConversionException, IOException { - return userPrefsStorage.readUserPrefs(); - } - - @Override - public void saveUserPrefs(UserPrefs userPrefs) throws IOException { - userPrefsStorage.saveUserPrefs(userPrefs); - } - - - // ================ AddressBook methods ============================== - - @Override - public String getAddressBookFilePath() { - return addressBookStorage.getAddressBookFilePath(); - } - - @Override - public Optional<ReadOnlyAddressBook> readAddressBook() throws DataConversionException, IOException { - return readAddressBook(addressBookStorage.getAddressBookFilePath()); - } - - @Override - public Optional<ReadOnlyAddressBook> readAddressBook(String filePath) throws DataConversionException, IOException { - logger.fine("Attempting to read data from file: " + filePath); - return addressBookStorage.readAddressBook(filePath); - } - - @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException { - saveAddressBook(addressBook, addressBookStorage.getAddressBookFilePath()); - } - - @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook, String filePath) throws IOException { - logger.fine("Attempting to write to data file: " + filePath); - addressBookStorage.saveAddressBook(addressBook, filePath); - } - - - @Override - @Subscribe - public void handleAddressBookChangedEvent(AddressBookChangedEvent event) { - logger.info(LogsCenter.getEventHandlingLogMessage(event, "Local data changed, saving to file")); - try { - saveAddressBook(event.data); - } catch (IOException e) { - raise(new DataSavingExceptionEvent(e)); - } - } - -} diff --git a/src/main/java/seedu/address/storage/XmlAddressBookStorage.java b/src/main/java/seedu/address/storage/XmlAddressBookStorage.java deleted file mode 100644 index c77ebe67435c..000000000000 --- a/src/main/java/seedu/address/storage/XmlAddressBookStorage.java +++ /dev/null @@ -1,82 +0,0 @@ -package seedu.address.storage; - -import static java.util.Objects.requireNonNull; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.Optional; -import java.util.logging.Logger; - -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.commons.util.FileUtil; -import seedu.address.model.ReadOnlyAddressBook; - -/** - * A class to access AddressBook data stored as an xml file on the hard disk. - */ -public class XmlAddressBookStorage implements AddressBookStorage { - - private static final Logger logger = LogsCenter.getLogger(XmlAddressBookStorage.class); - - private String filePath; - - public XmlAddressBookStorage(String filePath) { - this.filePath = filePath; - } - - public String getAddressBookFilePath() { - return filePath; - } - - @Override - public Optional<ReadOnlyAddressBook> readAddressBook() throws DataConversionException, IOException { - return readAddressBook(filePath); - } - - /** - * Similar to {@link #readAddressBook()} - * @param filePath location of the data. Cannot be null - * @throws DataConversionException if the file is not in the correct format. - */ - public Optional<ReadOnlyAddressBook> readAddressBook(String filePath) throws DataConversionException, - FileNotFoundException { - requireNonNull(filePath); - - File addressBookFile = new File(filePath); - - if (!addressBookFile.exists()) { - logger.info("AddressBook file " + addressBookFile + " not found"); - return Optional.empty(); - } - - XmlSerializableAddressBook xmlAddressBook = XmlFileStorage.loadDataFromSaveFile(new File(filePath)); - try { - return Optional.of(xmlAddressBook.toModelType()); - } catch (IllegalValueException ive) { - logger.info("Illegal values found in " + addressBookFile + ": " + ive.getMessage()); - throw new DataConversionException(ive); - } - } - - @Override - public void saveAddressBook(ReadOnlyAddressBook addressBook) throws IOException { - saveAddressBook(addressBook, filePath); - } - - /** - * Similar to {@link #saveAddressBook(ReadOnlyAddressBook)} - * @param filePath location of the data. Cannot be null - */ - public void saveAddressBook(ReadOnlyAddressBook addressBook, String filePath) throws IOException { - requireNonNull(addressBook); - requireNonNull(filePath); - - File file = new File(filePath); - FileUtil.createIfMissing(file); - XmlFileStorage.saveDataToFile(file, new XmlSerializableAddressBook(addressBook)); - } - -} diff --git a/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java b/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java deleted file mode 100644 index dc820896c312..000000000000 --- a/src/main/java/seedu/address/storage/XmlSerializableAddressBook.java +++ /dev/null @@ -1,73 +0,0 @@ -package seedu.address.storage; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; - -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.AddressBook; -import seedu.address.model.ReadOnlyAddressBook; - -/** - * An Immutable AddressBook that is serializable to XML format - */ -@XmlRootElement(name = "addressbook") -public class XmlSerializableAddressBook { - - @XmlElement - private List<XmlAdaptedPerson> persons; - @XmlElement - private List<XmlAdaptedTag> tags; - - /** - * Creates an empty XmlSerializableAddressBook. - * This empty constructor is required for marshalling. - */ - public XmlSerializableAddressBook() { - persons = new ArrayList<>(); - tags = new ArrayList<>(); - } - - /** - * Conversion - */ - public XmlSerializableAddressBook(ReadOnlyAddressBook src) { - this(); - persons.addAll(src.getPersonList().stream().map(XmlAdaptedPerson::new).collect(Collectors.toList())); - tags.addAll(src.getTagList().stream().map(XmlAdaptedTag::new).collect(Collectors.toList())); - } - - /** - * Converts this addressbook into the model's {@code AddressBook} object. - * - * @throws IllegalValueException if there were any data constraints violated or duplicates in the - * {@code XmlAdaptedPerson} or {@code XmlAdaptedTag}. - */ - public AddressBook toModelType() throws IllegalValueException { - AddressBook addressBook = new AddressBook(); - for (XmlAdaptedTag t : tags) { - addressBook.addTag(t.toModelType()); - } - for (XmlAdaptedPerson p : persons) { - addressBook.addPerson(p.toModelType()); - } - return addressBook; - } - - @Override - public boolean equals(Object other) { - if (other == this) { - return true; - } - - if (!(other instanceof XmlSerializableAddressBook)) { - return false; - } - - XmlSerializableAddressBook otherAb = (XmlSerializableAddressBook) other; - return persons.equals(otherAb.persons) && tags.equals(otherAb.tags); - } -} diff --git a/src/main/java/seedu/address/MainApp.java b/src/main/java/seedu/progresschecker/MainApp.java similarity index 67% rename from src/main/java/seedu/address/MainApp.java rename to src/main/java/seedu/progresschecker/MainApp.java index fa0800d55cb9..e6375d003283 100644 --- a/src/main/java/seedu/address/MainApp.java +++ b/src/main/java/seedu/progresschecker/MainApp.java @@ -1,4 +1,4 @@ -package seedu.address; +package seedu.progresschecker; import java.io.IOException; import java.util.Map; @@ -10,30 +10,30 @@ import javafx.application.Application; import javafx.application.Platform; import javafx.stage.Stage; -import seedu.address.commons.core.Config; -import seedu.address.commons.core.EventsCenter; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.core.Version; -import seedu.address.commons.events.ui.ExitAppRequestEvent; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.commons.util.ConfigUtil; -import seedu.address.commons.util.StringUtil; -import seedu.address.logic.Logic; -import seedu.address.logic.LogicManager; -import seedu.address.model.AddressBook; -import seedu.address.model.Model; -import seedu.address.model.ModelManager; -import seedu.address.model.ReadOnlyAddressBook; -import seedu.address.model.UserPrefs; -import seedu.address.model.util.SampleDataUtil; -import seedu.address.storage.AddressBookStorage; -import seedu.address.storage.JsonUserPrefsStorage; -import seedu.address.storage.Storage; -import seedu.address.storage.StorageManager; -import seedu.address.storage.UserPrefsStorage; -import seedu.address.storage.XmlAddressBookStorage; -import seedu.address.ui.Ui; -import seedu.address.ui.UiManager; +import seedu.progresschecker.commons.core.Config; +import seedu.progresschecker.commons.core.EventsCenter; +import seedu.progresschecker.commons.core.LogsCenter; +import seedu.progresschecker.commons.core.Version; +import seedu.progresschecker.commons.events.ui.ExitAppRequestEvent; +import seedu.progresschecker.commons.exceptions.DataConversionException; +import seedu.progresschecker.commons.util.ConfigUtil; +import seedu.progresschecker.commons.util.StringUtil; +import seedu.progresschecker.logic.Logic; +import seedu.progresschecker.logic.LogicManager; +import seedu.progresschecker.model.Model; +import seedu.progresschecker.model.ModelManager; +import seedu.progresschecker.model.ProgressChecker; +import seedu.progresschecker.model.ReadOnlyProgressChecker; +import seedu.progresschecker.model.UserPrefs; +import seedu.progresschecker.model.util.SampleDataUtil; +import seedu.progresschecker.storage.JsonUserPrefsStorage; +import seedu.progresschecker.storage.ProgressCheckerStorage; +import seedu.progresschecker.storage.Storage; +import seedu.progresschecker.storage.StorageManager; +import seedu.progresschecker.storage.UserPrefsStorage; +import seedu.progresschecker.storage.XmlProgressCheckerStorage; +import seedu.progresschecker.ui.Ui; +import seedu.progresschecker.ui.UiManager; /** * The main entry point to the application. @@ -54,15 +54,16 @@ public class MainApp extends Application { @Override public void init() throws Exception { - logger.info("=============================[ Initializing AddressBook ]==========================="); + logger.info("=============================[ Initializing ProgressChecker ]==========================="); super.init(); config = initConfig(getApplicationParameter("config")); UserPrefsStorage userPrefsStorage = new JsonUserPrefsStorage(config.getUserPrefsFilePath()); userPrefs = initPrefs(userPrefsStorage); - AddressBookStorage addressBookStorage = new XmlAddressBookStorage(userPrefs.getAddressBookFilePath()); - storage = new StorageManager(addressBookStorage, userPrefsStorage); + ProgressCheckerStorage progressCheckerStorage = new XmlProgressCheckerStorage( + userPrefs.getProgressCheckerFilePath()); + storage = new StorageManager(progressCheckerStorage, userPrefsStorage); initLogging(config); @@ -81,25 +82,25 @@ private String getApplicationParameter(String parameterName) { } /** - * Returns a {@code ModelManager} with the data from {@code storage}'s address book and {@code userPrefs}. <br> - * The data from the sample address book will be used instead if {@code storage}'s address book is not found, - * or an empty address book will be used instead if errors occur when reading {@code storage}'s address book. + * Returns a {@code ModelManager} with the data from {@code storage}'s ProgressChecker and {@code userPrefs}. <br> + * The data from the sample ProgressChecker will be used instead if {@code storage}'s ProgressChecker is not found, + * or an empty ProgressChecker will be used instead if errors occur when reading {@code storage}'s ProgressChecker. */ private Model initModelManager(Storage storage, UserPrefs userPrefs) { - Optional<ReadOnlyAddressBook> addressBookOptional; - ReadOnlyAddressBook initialData; + Optional<ReadOnlyProgressChecker> progressCheckerOptional; + ReadOnlyProgressChecker initialData; try { - addressBookOptional = storage.readAddressBook(); - if (!addressBookOptional.isPresent()) { - logger.info("Data file not found. Will be starting with a sample AddressBook"); + progressCheckerOptional = storage.readProgressChecker(); + if (!progressCheckerOptional.isPresent()) { + logger.info("Data file not found. Will be starting with a sample ProgressChecker"); } - initialData = addressBookOptional.orElseGet(SampleDataUtil::getSampleAddressBook); + initialData = progressCheckerOptional.orElseGet(SampleDataUtil::getSampleProgressChecker); } catch (DataConversionException e) { - logger.warning("Data file not in the correct format. Will be starting with an empty AddressBook"); - initialData = new AddressBook(); + logger.warning("Data file not in the correct format. Will be starting with an empty ProgressChecker"); + initialData = new ProgressChecker(); } catch (IOException e) { - logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook"); - initialData = new AddressBook(); + logger.warning("Problem while reading from the file. Will be starting with an empty ProgressChecker"); + initialData = new ProgressChecker(); } return new ModelManager(initialData, userPrefs); @@ -163,7 +164,7 @@ protected UserPrefs initPrefs(UserPrefsStorage storage) { + "Using default user prefs"); initializedPrefs = new UserPrefs(); } catch (IOException e) { - logger.warning("Problem while reading from the file. Will be starting with an empty AddressBook"); + logger.warning("Problem while reading from the file. Will be starting with an empty ProgressChecker"); initializedPrefs = new UserPrefs(); } @@ -183,13 +184,13 @@ private void initEventsCenter() { @Override public void start(Stage primaryStage) { - logger.info("Starting AddressBook " + MainApp.VERSION); + logger.info("Starting ProgressChecker " + MainApp.VERSION); ui.start(primaryStage); } @Override public void stop() { - logger.info("============================ [ Stopping Address Book ] ============================="); + logger.info("============================ [ Stopping Progress Checker ] ============================="); ui.stop(); try { storage.saveUserPrefs(userPrefs); diff --git a/src/main/java/seedu/address/commons/core/ComponentManager.java b/src/main/java/seedu/progresschecker/commons/core/ComponentManager.java similarity index 84% rename from src/main/java/seedu/address/commons/core/ComponentManager.java rename to src/main/java/seedu/progresschecker/commons/core/ComponentManager.java index 05a400773ae8..38121c1f2ca4 100644 --- a/src/main/java/seedu/address/commons/core/ComponentManager.java +++ b/src/main/java/seedu/progresschecker/commons/core/ComponentManager.java @@ -1,6 +1,6 @@ -package seedu.address.commons.core; +package seedu.progresschecker.commons.core; -import seedu.address.commons.events.BaseEvent; +import seedu.progresschecker.commons.events.BaseEvent; /** * Base class for *Manager classes diff --git a/src/main/java/seedu/address/commons/core/Config.java b/src/main/java/seedu/progresschecker/commons/core/Config.java similarity index 94% rename from src/main/java/seedu/address/commons/core/Config.java rename to src/main/java/seedu/progresschecker/commons/core/Config.java index 8f4d737d0e24..452f8f639cf0 100644 --- a/src/main/java/seedu/address/commons/core/Config.java +++ b/src/main/java/seedu/progresschecker/commons/core/Config.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package seedu.progresschecker.commons.core; import java.util.Objects; import java.util.logging.Level; @@ -11,7 +11,7 @@ public class Config { public static final String DEFAULT_CONFIG_FILE = "config.json"; // Config values customizable through config file - private String appTitle = "Address App"; + private String appTitle = "ProgressChecker"; private Level logLevel = Level.INFO; private String userPrefsFilePath = "preferences.json"; diff --git a/src/main/java/seedu/address/commons/core/EventsCenter.java b/src/main/java/seedu/progresschecker/commons/core/EventsCenter.java similarity index 91% rename from src/main/java/seedu/address/commons/core/EventsCenter.java rename to src/main/java/seedu/progresschecker/commons/core/EventsCenter.java index 799b976f7eb7..99e7cdd2f1eb 100644 --- a/src/main/java/seedu/address/commons/core/EventsCenter.java +++ b/src/main/java/seedu/progresschecker/commons/core/EventsCenter.java @@ -1,10 +1,10 @@ -package seedu.address.commons.core; +package seedu.progresschecker.commons.core; import java.util.logging.Logger; import com.google.common.eventbus.EventBus; -import seedu.address.commons.events.BaseEvent; +import seedu.progresschecker.commons.events.BaseEvent; /** * Manages the event dispatching of the app. diff --git a/src/main/java/seedu/address/commons/core/GuiSettings.java b/src/main/java/seedu/progresschecker/commons/core/GuiSettings.java similarity index 97% rename from src/main/java/seedu/address/commons/core/GuiSettings.java rename to src/main/java/seedu/progresschecker/commons/core/GuiSettings.java index 846d714375e4..064592053e9b 100644 --- a/src/main/java/seedu/address/commons/core/GuiSettings.java +++ b/src/main/java/seedu/progresschecker/commons/core/GuiSettings.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package seedu.progresschecker.commons.core; import java.awt.Point; import java.io.Serializable; diff --git a/src/main/java/seedu/address/commons/core/LogsCenter.java b/src/main/java/seedu/progresschecker/commons/core/LogsCenter.java similarity index 96% rename from src/main/java/seedu/address/commons/core/LogsCenter.java rename to src/main/java/seedu/progresschecker/commons/core/LogsCenter.java index 46e4c3aac468..0d25c7acd2b2 100644 --- a/src/main/java/seedu/address/commons/core/LogsCenter.java +++ b/src/main/java/seedu/progresschecker/commons/core/LogsCenter.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package seedu.progresschecker.commons.core; import java.io.IOException; import java.util.logging.ConsoleHandler; @@ -8,7 +8,7 @@ import java.util.logging.Logger; import java.util.logging.SimpleFormatter; -import seedu.address.commons.events.BaseEvent; +import seedu.progresschecker.commons.events.BaseEvent; /** * Configures and manages loggers and handlers, including their logging level @@ -20,7 +20,7 @@ public class LogsCenter { private static final int MAX_FILE_COUNT = 5; private static final int MAX_FILE_SIZE_IN_BYTES = (int) (Math.pow(2, 20) * 5); // 5MB - private static final String LOG_FILE = "addressbook.log"; + private static final String LOG_FILE = "progresschecker.log"; private static Level currentLogLevel = Level.INFO; private static final Logger logger = LogsCenter.getLogger(LogsCenter.class); private static FileHandler fileHandler; diff --git a/src/main/java/seedu/address/commons/core/Messages.java b/src/main/java/seedu/progresschecker/commons/core/Messages.java similarity index 62% rename from src/main/java/seedu/address/commons/core/Messages.java rename to src/main/java/seedu/progresschecker/commons/core/Messages.java index 1deb3a1e4695..1a3a2fc15339 100644 --- a/src/main/java/seedu/address/commons/core/Messages.java +++ b/src/main/java/seedu/progresschecker/commons/core/Messages.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package seedu.progresschecker.commons.core; /** * Container for user visible messages. @@ -9,5 +9,7 @@ public class Messages { public static final String MESSAGE_INVALID_COMMAND_FORMAT = "Invalid command format! \n%1$s"; public static final String MESSAGE_INVALID_PERSON_DISPLAYED_INDEX = "The person index provided is invalid"; public static final String MESSAGE_PERSONS_LISTED_OVERVIEW = "%1$d persons listed!"; + public static final String MESSAGE_INVALID_EXERCISE_INDEX = "Exercise %1$s does not exist in the current week"; + public static final String MESSAGE_INVALID_ISSUE_DISPLAYED_INDEX = "The issue index provided is invalid"; } diff --git a/src/main/java/seedu/address/commons/core/Version.java b/src/main/java/seedu/progresschecker/commons/core/Version.java similarity index 98% rename from src/main/java/seedu/address/commons/core/Version.java rename to src/main/java/seedu/progresschecker/commons/core/Version.java index e8fe0d3e6299..5259a38de137 100644 --- a/src/main/java/seedu/address/commons/core/Version.java +++ b/src/main/java/seedu/progresschecker/commons/core/Version.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core; +package seedu.progresschecker.commons.core; import java.util.regex.Matcher; import java.util.regex.Pattern; diff --git a/src/main/java/seedu/address/commons/core/index/Index.java b/src/main/java/seedu/progresschecker/commons/core/index/Index.java similarity index 97% rename from src/main/java/seedu/address/commons/core/index/Index.java rename to src/main/java/seedu/progresschecker/commons/core/index/Index.java index fd119bc926fd..06cd1ea6bdd3 100644 --- a/src/main/java/seedu/address/commons/core/index/Index.java +++ b/src/main/java/seedu/progresschecker/commons/core/index/Index.java @@ -1,4 +1,4 @@ -package seedu.address.commons.core.index; +package seedu.progresschecker.commons.core.index; /** * Represents a zero-based or one-based index. diff --git a/src/main/java/seedu/address/commons/events/BaseEvent.java b/src/main/java/seedu/progresschecker/commons/events/BaseEvent.java similarity index 89% rename from src/main/java/seedu/address/commons/events/BaseEvent.java rename to src/main/java/seedu/progresschecker/commons/events/BaseEvent.java index 85e71cbb6b62..94df3be20678 100644 --- a/src/main/java/seedu/address/commons/events/BaseEvent.java +++ b/src/main/java/seedu/progresschecker/commons/events/BaseEvent.java @@ -1,4 +1,4 @@ -package seedu.address.commons.events; +package seedu.progresschecker.commons.events; /** * The base class for all event classes. diff --git a/src/main/java/seedu/progresschecker/commons/events/model/ProgressCheckerChangedEvent.java b/src/main/java/seedu/progresschecker/commons/events/model/ProgressCheckerChangedEvent.java new file mode 100644 index 000000000000..339936c35bc0 --- /dev/null +++ b/src/main/java/seedu/progresschecker/commons/events/model/ProgressCheckerChangedEvent.java @@ -0,0 +1,19 @@ +package seedu.progresschecker.commons.events.model; + +import seedu.progresschecker.commons.events.BaseEvent; +import seedu.progresschecker.model.ReadOnlyProgressChecker; + +/** Indicates the ProgressChecker in the model has changed*/ +public class ProgressCheckerChangedEvent extends BaseEvent { + + public final ReadOnlyProgressChecker data; + + public ProgressCheckerChangedEvent(ReadOnlyProgressChecker data) { + this.data = data; + } + + @Override + public String toString() { + return "number of persons " + data.getPersonList().size() + ", number of tags " + data.getTagList().size(); + } +} diff --git a/src/main/java/seedu/address/commons/events/storage/DataSavingExceptionEvent.java b/src/main/java/seedu/progresschecker/commons/events/storage/DataSavingExceptionEvent.java similarity index 76% rename from src/main/java/seedu/address/commons/events/storage/DataSavingExceptionEvent.java rename to src/main/java/seedu/progresschecker/commons/events/storage/DataSavingExceptionEvent.java index 7096107d8adf..514f7fd30b14 100644 --- a/src/main/java/seedu/address/commons/events/storage/DataSavingExceptionEvent.java +++ b/src/main/java/seedu/progresschecker/commons/events/storage/DataSavingExceptionEvent.java @@ -1,6 +1,6 @@ -package seedu.address.commons.events.storage; +package seedu.progresschecker.commons.events.storage; -import seedu.address.commons.events.BaseEvent; +import seedu.progresschecker.commons.events.BaseEvent; /** * Indicates an exception during a file saving diff --git a/src/main/java/seedu/progresschecker/commons/events/ui/ChangeThemeEvent.java b/src/main/java/seedu/progresschecker/commons/events/ui/ChangeThemeEvent.java new file mode 100644 index 000000000000..bb71ecaec47d --- /dev/null +++ b/src/main/java/seedu/progresschecker/commons/events/ui/ChangeThemeEvent.java @@ -0,0 +1,24 @@ +package seedu.progresschecker.commons.events.ui; + +import seedu.progresschecker.commons.events.BaseEvent; + +//@@author Livian1107 +/** + * Represents the change of the theme of ProgressChecker. + */ +public class ChangeThemeEvent extends BaseEvent { + public final String theme; + + public ChangeThemeEvent(String theme) { + this.theme = theme; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + + public String getTheme() { + return theme; + } +} diff --git a/src/main/java/seedu/address/commons/events/ui/ExitAppRequestEvent.java b/src/main/java/seedu/progresschecker/commons/events/ui/ExitAppRequestEvent.java similarity index 66% rename from src/main/java/seedu/address/commons/events/ui/ExitAppRequestEvent.java rename to src/main/java/seedu/progresschecker/commons/events/ui/ExitAppRequestEvent.java index 9af6194543a3..0f1f5c16291c 100644 --- a/src/main/java/seedu/address/commons/events/ui/ExitAppRequestEvent.java +++ b/src/main/java/seedu/progresschecker/commons/events/ui/ExitAppRequestEvent.java @@ -1,6 +1,6 @@ -package seedu.address.commons.events.ui; +package seedu.progresschecker.commons.events.ui; -import seedu.address.commons.events.BaseEvent; +import seedu.progresschecker.commons.events.BaseEvent; /** * Indicates a request for App termination diff --git a/src/main/java/seedu/address/commons/events/ui/JumpToListRequestEvent.java b/src/main/java/seedu/progresschecker/commons/events/ui/JumpToListRequestEvent.java similarity index 70% rename from src/main/java/seedu/address/commons/events/ui/JumpToListRequestEvent.java rename to src/main/java/seedu/progresschecker/commons/events/ui/JumpToListRequestEvent.java index 4fc32183f074..a724af8e57e9 100644 --- a/src/main/java/seedu/address/commons/events/ui/JumpToListRequestEvent.java +++ b/src/main/java/seedu/progresschecker/commons/events/ui/JumpToListRequestEvent.java @@ -1,7 +1,7 @@ -package seedu.address.commons.events.ui; +package seedu.progresschecker.commons.events.ui; -import seedu.address.commons.core.index.Index; -import seedu.address.commons.events.BaseEvent; +import seedu.progresschecker.commons.core.index.Index; +import seedu.progresschecker.commons.events.BaseEvent; /** * Indicates a request to jump to the list of persons diff --git a/src/main/java/seedu/progresschecker/commons/events/ui/LoadBarEvent.java b/src/main/java/seedu/progresschecker/commons/events/ui/LoadBarEvent.java new file mode 100644 index 000000000000..101444267694 --- /dev/null +++ b/src/main/java/seedu/progresschecker/commons/events/ui/LoadBarEvent.java @@ -0,0 +1,28 @@ +package seedu.progresschecker.commons.events.ui; + +import seedu.progresschecker.commons.events.BaseEvent; + +//@@author EdwardKSG +/** + * Represents a page change in the second Browser Panel which shows the progress bar. + */ +public class LoadBarEvent extends BaseEvent { + + + public final String content; + + public LoadBarEvent(String content) { + this.content = content; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + + public String getContent() { + return content; + } + +} +//@@author diff --git a/src/main/java/seedu/progresschecker/commons/events/ui/LoadTaskEvent.java b/src/main/java/seedu/progresschecker/commons/events/ui/LoadTaskEvent.java new file mode 100644 index 000000000000..c24aede72d31 --- /dev/null +++ b/src/main/java/seedu/progresschecker/commons/events/ui/LoadTaskEvent.java @@ -0,0 +1,28 @@ +package seedu.progresschecker.commons.events.ui; + +import seedu.progresschecker.commons.events.BaseEvent; + +//@@author EdwardKSG +/** + * Represents a page change in the Browser Panel + */ +public class LoadTaskEvent extends BaseEvent { + + + public final String content; + + public LoadTaskEvent(String content) { + this.content = content; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + + public String getContent() { + return content; + } + +} +//@@author diff --git a/src/main/java/seedu/progresschecker/commons/events/ui/LoadUrlEvent.java b/src/main/java/seedu/progresschecker/commons/events/ui/LoadUrlEvent.java new file mode 100644 index 000000000000..153366ee4bd8 --- /dev/null +++ b/src/main/java/seedu/progresschecker/commons/events/ui/LoadUrlEvent.java @@ -0,0 +1,28 @@ +package seedu.progresschecker.commons.events.ui; + +import seedu.progresschecker.commons.events.BaseEvent; + +//@@author EdwardKSG +/** + * Represents a page change in the Browser Panel + */ +public class LoadUrlEvent extends BaseEvent { + + + public final String url; + + public LoadUrlEvent(String url) { + this.url = url; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + + public String getUrl() { + return url; + } + +} +//@@author diff --git a/src/main/java/seedu/address/commons/events/ui/NewResultAvailableEvent.java b/src/main/java/seedu/progresschecker/commons/events/ui/NewResultAvailableEvent.java similarity index 76% rename from src/main/java/seedu/address/commons/events/ui/NewResultAvailableEvent.java rename to src/main/java/seedu/progresschecker/commons/events/ui/NewResultAvailableEvent.java index a6d5274a97e0..6e9f0fa2b7d5 100644 --- a/src/main/java/seedu/address/commons/events/ui/NewResultAvailableEvent.java +++ b/src/main/java/seedu/progresschecker/commons/events/ui/NewResultAvailableEvent.java @@ -1,6 +1,6 @@ -package seedu.address.commons.events.ui; +package seedu.progresschecker.commons.events.ui; -import seedu.address.commons.events.BaseEvent; +import seedu.progresschecker.commons.events.BaseEvent; /** * Indicates that a new result is available. diff --git a/src/main/java/seedu/address/commons/events/ui/PersonPanelSelectionChangedEvent.java b/src/main/java/seedu/progresschecker/commons/events/ui/PersonPanelSelectionChangedEvent.java similarity index 76% rename from src/main/java/seedu/address/commons/events/ui/PersonPanelSelectionChangedEvent.java rename to src/main/java/seedu/progresschecker/commons/events/ui/PersonPanelSelectionChangedEvent.java index 56c1c9d987f1..0362a9f83b60 100644 --- a/src/main/java/seedu/address/commons/events/ui/PersonPanelSelectionChangedEvent.java +++ b/src/main/java/seedu/progresschecker/commons/events/ui/PersonPanelSelectionChangedEvent.java @@ -1,7 +1,7 @@ -package seedu.address.commons.events.ui; +package seedu.progresschecker.commons.events.ui; -import seedu.address.commons.events.BaseEvent; -import seedu.address.ui.PersonCard; +import seedu.progresschecker.commons.events.BaseEvent; +import seedu.progresschecker.ui.PersonCard; /** * Represents a selection change in the Person List Panel diff --git a/src/main/java/seedu/address/commons/events/ui/ShowHelpRequestEvent.java b/src/main/java/seedu/progresschecker/commons/events/ui/ShowHelpRequestEvent.java similarity index 67% rename from src/main/java/seedu/address/commons/events/ui/ShowHelpRequestEvent.java rename to src/main/java/seedu/progresschecker/commons/events/ui/ShowHelpRequestEvent.java index a7e40940b2c7..94ec9d71e543 100644 --- a/src/main/java/seedu/address/commons/events/ui/ShowHelpRequestEvent.java +++ b/src/main/java/seedu/progresschecker/commons/events/ui/ShowHelpRequestEvent.java @@ -1,6 +1,6 @@ -package seedu.address.commons.events.ui; +package seedu.progresschecker.commons.events.ui; -import seedu.address.commons.events.BaseEvent; +import seedu.progresschecker.commons.events.BaseEvent; /** * An event requesting to view the help page. diff --git a/src/main/java/seedu/progresschecker/commons/events/ui/TabLoadChangedEvent.java b/src/main/java/seedu/progresschecker/commons/events/ui/TabLoadChangedEvent.java new file mode 100644 index 000000000000..a4e5dc229695 --- /dev/null +++ b/src/main/java/seedu/progresschecker/commons/events/ui/TabLoadChangedEvent.java @@ -0,0 +1,25 @@ +package seedu.progresschecker.commons.events.ui; + +import seedu.progresschecker.commons.events.BaseEvent; + +//@@author iNekox3 +/** + * Represents a tab change in Main Window. + */ +public class TabLoadChangedEvent extends BaseEvent { + + public final String type; + + public TabLoadChangedEvent(String type) { + this.type = type; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + + public String getTabName() { + return type; + } +} diff --git a/src/main/java/seedu/address/commons/exceptions/DataConversionException.java b/src/main/java/seedu/progresschecker/commons/exceptions/DataConversionException.java similarity index 81% rename from src/main/java/seedu/address/commons/exceptions/DataConversionException.java rename to src/main/java/seedu/progresschecker/commons/exceptions/DataConversionException.java index 1f689bd8e3f9..727ba00875e1 100644 --- a/src/main/java/seedu/address/commons/exceptions/DataConversionException.java +++ b/src/main/java/seedu/progresschecker/commons/exceptions/DataConversionException.java @@ -1,4 +1,4 @@ -package seedu.address.commons.exceptions; +package seedu.progresschecker.commons.exceptions; /** * Represents an error during conversion of data from one format to another diff --git a/src/main/java/seedu/address/commons/exceptions/DuplicateDataException.java b/src/main/java/seedu/progresschecker/commons/exceptions/DuplicateDataException.java similarity index 82% rename from src/main/java/seedu/address/commons/exceptions/DuplicateDataException.java rename to src/main/java/seedu/progresschecker/commons/exceptions/DuplicateDataException.java index 17aa63d5020c..aaa5789991ad 100644 --- a/src/main/java/seedu/address/commons/exceptions/DuplicateDataException.java +++ b/src/main/java/seedu/progresschecker/commons/exceptions/DuplicateDataException.java @@ -1,4 +1,4 @@ -package seedu.address.commons.exceptions; +package seedu.progresschecker.commons.exceptions; /** * Signals an error caused by duplicate data where there should be none. diff --git a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java b/src/main/java/seedu/progresschecker/commons/exceptions/IllegalValueException.java similarity index 91% rename from src/main/java/seedu/address/commons/exceptions/IllegalValueException.java rename to src/main/java/seedu/progresschecker/commons/exceptions/IllegalValueException.java index 19124db485c9..04225288ac95 100644 --- a/src/main/java/seedu/address/commons/exceptions/IllegalValueException.java +++ b/src/main/java/seedu/progresschecker/commons/exceptions/IllegalValueException.java @@ -1,4 +1,4 @@ -package seedu.address.commons.exceptions; +package seedu.progresschecker.commons.exceptions; /** * Signals that some given data does not fulfill some constraints. diff --git a/src/main/java/seedu/address/commons/util/AppUtil.java b/src/main/java/seedu/progresschecker/commons/util/AppUtil.java similarity index 92% rename from src/main/java/seedu/address/commons/util/AppUtil.java rename to src/main/java/seedu/progresschecker/commons/util/AppUtil.java index da90201dfd64..6eb56be4fed8 100644 --- a/src/main/java/seedu/address/commons/util/AppUtil.java +++ b/src/main/java/seedu/progresschecker/commons/util/AppUtil.java @@ -1,9 +1,9 @@ -package seedu.address.commons.util; +package seedu.progresschecker.commons.util; import static java.util.Objects.requireNonNull; import javafx.scene.image.Image; -import seedu.address.MainApp; +import seedu.progresschecker.MainApp; /** * A container for App specific utility functions diff --git a/src/main/java/seedu/address/commons/util/CollectionUtil.java b/src/main/java/seedu/progresschecker/commons/util/CollectionUtil.java similarity index 97% rename from src/main/java/seedu/address/commons/util/CollectionUtil.java rename to src/main/java/seedu/progresschecker/commons/util/CollectionUtil.java index 52d209e778dd..9fc64fa13da2 100644 --- a/src/main/java/seedu/address/commons/util/CollectionUtil.java +++ b/src/main/java/seedu/progresschecker/commons/util/CollectionUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package seedu.progresschecker.commons.util; import static java.util.Objects.requireNonNull; diff --git a/src/main/java/seedu/address/commons/util/ConfigUtil.java b/src/main/java/seedu/progresschecker/commons/util/ConfigUtil.java similarity index 73% rename from src/main/java/seedu/address/commons/util/ConfigUtil.java rename to src/main/java/seedu/progresschecker/commons/util/ConfigUtil.java index 7eb770dba3e3..c4f7b9815ee9 100644 --- a/src/main/java/seedu/address/commons/util/ConfigUtil.java +++ b/src/main/java/seedu/progresschecker/commons/util/ConfigUtil.java @@ -1,10 +1,10 @@ -package seedu.address.commons.util; +package seedu.progresschecker.commons.util; import java.io.IOException; import java.util.Optional; -import seedu.address.commons.core.Config; -import seedu.address.commons.exceptions.DataConversionException; +import seedu.progresschecker.commons.core.Config; +import seedu.progresschecker.commons.exceptions.DataConversionException; /** * A class for accessing the Config File. diff --git a/src/main/java/seedu/progresschecker/commons/util/FileUtil.java b/src/main/java/seedu/progresschecker/commons/util/FileUtil.java new file mode 100644 index 000000000000..0c455e53aead --- /dev/null +++ b/src/main/java/seedu/progresschecker/commons/util/FileUtil.java @@ -0,0 +1,173 @@ +package seedu.progresschecker.commons.util; + +import static seedu.progresschecker.commons.util.AppUtil.checkArgument; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; + +/** + * Writes and reads files + */ +public class FileUtil { + + public static final String REGEX_VALID_IMAGE = "([^\\s]+(\\.(?i)(jpg|jpeg|png))$)"; + private static final String CHARSET = "UTF-8"; + + public static boolean isFileExists(File file) { + return file.exists() && file.isFile(); + } + + /** + * Creates a file if it does not exist along with its missing parent directories. + * @throws IOException if the file or directory cannot be created. + */ + public static void createIfMissing(File file) throws IOException { + if (!isFileExists(file)) { + createFile(file); + } + } + + /** + * Creates a file if it does not exist along with its missing parent directories + * + * @return true if file is created, false if file already exists + */ + public static boolean createFile(File file) throws IOException { + if (file.exists()) { + return false; + } + + createParentDirsOfFile(file); + + return file.createNewFile(); + } + + /** + * Creates the given directory along with its parent directories + * + * @param dir the directory to be created; assumed not null + * @throws IOException if the directory or a parent directory cannot be created + */ + public static void createDirs(File dir) throws IOException { + if (!dir.exists() && !dir.mkdirs()) { + throw new IOException("Failed to make directories of " + dir.getName()); + } + } + + /** + * Creates parent directories of file if it has a parent directory + */ + public static void createParentDirsOfFile(File file) throws IOException { + File parentDir = file.getParentFile(); + + if (parentDir != null) { + createDirs(parentDir); + } + } + + /** + * Assumes file exists + */ + public static String readFromFile(File file) throws IOException { + return new String(Files.readAllBytes(file.toPath()), CHARSET); + } + + /** + * Writes given string to a file. + * Will create the file if it does not exist yet. + */ + public static void writeToFile(File file, String content) throws IOException { + Files.write(file.toPath(), content.getBytes(CHARSET)); + } + + //@@author Livian1107 + /** + * Copies all the contents from the file in original path to the one in destination path. + * @param oriPath of the file to be copied + * @param destPath of the file to be pasted + * @return true if the file is successfully copied to the specified place. + */ + public static boolean copyFile(String oriPath, String destPath) throws IOException { + + //create a buffer to store content + byte[] buffer = new byte[3072]; + + //bufferedInputStream + FileInputStream fis = new FileInputStream(oriPath); + BufferedInputStream bis = new BufferedInputStream(fis); + + //bufferedOutputStream + FileOutputStream fos = new FileOutputStream(destPath); + BufferedOutputStream bos = new BufferedOutputStream(fos); + + int numBytes = bis.read(buffer); + while (numBytes > 0) { + bos.write(buffer, 0, numBytes); + numBytes = bis.read(buffer); + } + + //close input,output stream + bis.close(); + bos.close(); + + return true; + } + //@@author + + /** + * Converts a string to a platform-specific file path + * @param pathWithForwardSlash A String representing a file path but using '/' as the separator + * @return {@code pathWithForwardSlash} but '/' replaced with {@code File.separator} + */ + public static String getPath(String pathWithForwardSlash) { + checkArgument(pathWithForwardSlash.contains("/")); + return pathWithForwardSlash.replace("/", File.separator); + } + + //@@author Livian1107 + /** + * Returns the extension information from the file path + * @param filePath + * @return extension String + */ + public static String getFileExtension(String filePath) { + return "." + filePath.split("\\.")[1]; + } + + /** + * Creates a new file if it does not exist + * @param file to created + * @throws IOException if the file cannot be created + */ + public static void createMissing(File file) throws IOException { + if (!file.exists()) { + createFile(file); + } + } + + /** + * Returns whether the uploaded file is a valid image file + * Valid image file should have extension: '.jpg', '.jepg' or 'png'. + * @param path of the uploaded image file + * @return true if the uploaded file has valid extension + */ + public static boolean isValidImageFile(String path) { + return path.matches(REGEX_VALID_IMAGE); + } + + /** + * Returns whether the path of uploaded file is under the specific folder + * @param path of the uploaded file + * @param parentFolder of the specific folder + * @return true if the file is under this specific folder + */ + public static boolean isUnderFolder(String path, String parentFolder) { + return path.startsWith(parentFolder); + } + //@@author +} diff --git a/src/main/java/seedu/address/commons/util/JsonUtil.java b/src/main/java/seedu/progresschecker/commons/util/JsonUtil.java similarity index 96% rename from src/main/java/seedu/address/commons/util/JsonUtil.java rename to src/main/java/seedu/progresschecker/commons/util/JsonUtil.java index 1c629b0d4a16..7c0b3a652320 100644 --- a/src/main/java/seedu/address/commons/util/JsonUtil.java +++ b/src/main/java/seedu/progresschecker/commons/util/JsonUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package seedu.progresschecker.commons.util; import static java.util.Objects.requireNonNull; @@ -19,8 +19,8 @@ import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.exceptions.DataConversionException; +import seedu.progresschecker.commons.core.LogsCenter; +import seedu.progresschecker.commons.exceptions.DataConversionException; /** * Converts a Java object instance to JSON and vice versa diff --git a/src/main/java/seedu/address/commons/util/StringUtil.java b/src/main/java/seedu/progresschecker/commons/util/StringUtil.java similarity index 65% rename from src/main/java/seedu/address/commons/util/StringUtil.java rename to src/main/java/seedu/progresschecker/commons/util/StringUtil.java index 6e403c17c96e..32586c08e8d0 100644 --- a/src/main/java/seedu/address/commons/util/StringUtil.java +++ b/src/main/java/seedu/progresschecker/commons/util/StringUtil.java @@ -1,11 +1,13 @@ -package seedu.address.commons.util; +package seedu.progresschecker.commons.util; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; +import static seedu.progresschecker.commons.util.AppUtil.checkArgument; import java.io.PrintWriter; import java.io.StringWriter; +import seedu.progresschecker.logic.commands.ViewCommand; + /** * Helper functions for handling strings. */ @@ -17,7 +19,7 @@ public class StringUtil { * <br>examples:<pre> * containsWordIgnoreCase("ABc def", "abc") == true * containsWordIgnoreCase("ABc def", "DEF") == true - * containsWordIgnoreCase("ABc def", "AB") == false //not a full word match + * containsWordIgnoreCase("ABc def", "AB") == true //for a word that contains the substring * </pre> * @param sentence cannot be null * @param word cannot be null, cannot be empty, must be a single word @@ -34,8 +36,10 @@ public static boolean containsWordIgnoreCase(String sentence, String word) { String[] wordsInPreppedSentence = preppedSentence.split("\\s+"); for (String wordInSentence: wordsInPreppedSentence) { - if (wordInSentence.equalsIgnoreCase(preppedWord)) { - return true; + for (int i = 0; i <= wordInSentence.length() - preppedWord.length(); i++) { + if (wordInSentence.substring(i, i + preppedWord.length()).equalsIgnoreCase(preppedWord)) { + return true; + } } } return false; @@ -68,4 +72,23 @@ public static boolean isNonZeroUnsignedInteger(String s) { return false; } } + + //@@author iNekox3 + /** + * Returns true if {@code s} is within the range 2 to 11. + * e.g. 2, 3, 4, ..., 11 <br> + * Will return false for any other non-null string input + * e.g. empty string, "-1", "0", "+1", and " 2 " (untrimmed), "3 0" (contains whitespace), "1 a" (contains letters) + * @throws NullPointerException if {@code s} is null. + */ + public static boolean isWithinRange(String s) { + requireNonNull(s); + + try { + int value = Integer.parseInt(s); + return value >= ViewCommand.MIN_WEEK_NUMBER && value <= ViewCommand.MAX_WEEK_NUMBER; + } catch (NumberFormatException nfe) { + return false; + } + } } diff --git a/src/main/java/seedu/address/commons/util/XmlUtil.java b/src/main/java/seedu/progresschecker/commons/util/XmlUtil.java similarity index 98% rename from src/main/java/seedu/address/commons/util/XmlUtil.java rename to src/main/java/seedu/progresschecker/commons/util/XmlUtil.java index 5f61738627cc..de8908b881d5 100644 --- a/src/main/java/seedu/address/commons/util/XmlUtil.java +++ b/src/main/java/seedu/progresschecker/commons/util/XmlUtil.java @@ -1,4 +1,4 @@ -package seedu.address.commons.util; +package seedu.progresschecker.commons.util; import static java.util.Objects.requireNonNull; diff --git a/src/main/java/seedu/progresschecker/logic/CommandFormatListUtil.java b/src/main/java/seedu/progresschecker/logic/CommandFormatListUtil.java new file mode 100644 index 000000000000..79cf846ac4c5 --- /dev/null +++ b/src/main/java/seedu/progresschecker/logic/CommandFormatListUtil.java @@ -0,0 +1,80 @@ +package seedu.progresschecker.logic; + +import java.util.ArrayList; +import java.util.Collections; + +import seedu.progresschecker.logic.commands.AddCommand; +import seedu.progresschecker.logic.commands.AnswerCommand; +import seedu.progresschecker.logic.commands.ClearCommand; +import seedu.progresschecker.logic.commands.CloseIssueCommand; +import seedu.progresschecker.logic.commands.CompleteTaskCommand; +import seedu.progresschecker.logic.commands.CreateIssueCommand; +import seedu.progresschecker.logic.commands.DeleteCommand; +import seedu.progresschecker.logic.commands.EditCommand; +import seedu.progresschecker.logic.commands.EditIssueCommand; +import seedu.progresschecker.logic.commands.ExitCommand; +import seedu.progresschecker.logic.commands.FindCommand; +import seedu.progresschecker.logic.commands.GitLoginCommand; +import seedu.progresschecker.logic.commands.GitLogoutCommand; +import seedu.progresschecker.logic.commands.GoToTaskUrlCommand; +import seedu.progresschecker.logic.commands.HelpCommand; +import seedu.progresschecker.logic.commands.ListCommand; +import seedu.progresschecker.logic.commands.RedoCommand; +import seedu.progresschecker.logic.commands.ReopenIssueCommand; +import seedu.progresschecker.logic.commands.ResetTaskCommand; +import seedu.progresschecker.logic.commands.SelectCommand; +import seedu.progresschecker.logic.commands.SortCommand; +import seedu.progresschecker.logic.commands.ThemeCommand; +import seedu.progresschecker.logic.commands.UndoCommand; +import seedu.progresschecker.logic.commands.UploadCommand; +import seedu.progresschecker.logic.commands.ViewCommand; +import seedu.progresschecker.logic.commands.ViewTaskListCommand; + +//@@author adityaa1998 +/** + * Initialises and returns a list which contains different command formats + */ +public final class CommandFormatListUtil { + private static ArrayList<String> commandFormatList; + + public static ArrayList<String> getCommandFormatList () { + commandFormatList = new ArrayList<>(); + createCommandFormatList(); + return commandFormatList; + } + + /** + * Creates commandFormatList for existing commands + */ + private static void createCommandFormatList() { + commandFormatList.add(AddCommand.COMMAND_FORMAT); + commandFormatList.add(AnswerCommand.COMMAND_FORMAT); + commandFormatList.add(ClearCommand.COMMAND_WORD); + commandFormatList.add(DeleteCommand.COMMAND_FORMAT); + commandFormatList.add(CompleteTaskCommand.COMMAND_WORD); + commandFormatList.add(EditCommand.COMMAND_FORMAT); + commandFormatList.add(ExitCommand.COMMAND_WORD); + commandFormatList.add(FindCommand.COMMAND_FORMAT); + commandFormatList.add(GoToTaskUrlCommand.COMMAND_WORD); + commandFormatList.add(HelpCommand.COMMAND_WORD); + commandFormatList.add(ListCommand.COMMAND_WORD); + commandFormatList.add(RedoCommand.COMMAND_WORD); + commandFormatList.add(ResetTaskCommand.COMMAND_WORD); + commandFormatList.add(SelectCommand.COMMAND_FORMAT); + commandFormatList.add(SortCommand.COMMAND_WORD); + commandFormatList.add(UndoCommand.COMMAND_WORD); + commandFormatList.add(UploadCommand.COMMAND_FORMAT); + commandFormatList.add(ViewCommand.COMMAND_FORMAT); + commandFormatList.add(ViewTaskListCommand.COMMAND_FORMAT); + commandFormatList.add(CreateIssueCommand.COMMAND_FORMAT); + commandFormatList.add(EditIssueCommand.COMMAND_FORMAT); + commandFormatList.add(ReopenIssueCommand.COMMAND_FORMAT); + commandFormatList.add(CloseIssueCommand.COMMAND_FORMAT); + commandFormatList.add(GitLoginCommand.COMMAND_FORMAT); + commandFormatList.add(ThemeCommand.COMMAND_FORMAT); + commandFormatList.add(GitLogoutCommand.COMMAND_WORD); + + //sorting the commandFormatList + Collections.sort(commandFormatList); + } +} diff --git a/src/main/java/seedu/address/logic/CommandHistory.java b/src/main/java/seedu/progresschecker/logic/CommandHistory.java similarity index 94% rename from src/main/java/seedu/address/logic/CommandHistory.java rename to src/main/java/seedu/progresschecker/logic/CommandHistory.java index 10821acb3e2d..ace9000e86fd 100644 --- a/src/main/java/seedu/address/logic/CommandHistory.java +++ b/src/main/java/seedu/progresschecker/logic/CommandHistory.java @@ -1,4 +1,4 @@ -package seedu.address.logic; +package seedu.progresschecker.logic; import static java.util.Objects.requireNonNull; diff --git a/src/main/java/seedu/address/logic/ListElementPointer.java b/src/main/java/seedu/progresschecker/logic/ListElementPointer.java similarity index 98% rename from src/main/java/seedu/address/logic/ListElementPointer.java rename to src/main/java/seedu/progresschecker/logic/ListElementPointer.java index ca4085d98a11..c06d5c29fbaf 100644 --- a/src/main/java/seedu/address/logic/ListElementPointer.java +++ b/src/main/java/seedu/progresschecker/logic/ListElementPointer.java @@ -1,4 +1,4 @@ -package seedu.address.logic; +package seedu.progresschecker.logic; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/seedu/address/logic/Logic.java b/src/main/java/seedu/progresschecker/logic/Logic.java similarity index 55% rename from src/main/java/seedu/address/logic/Logic.java rename to src/main/java/seedu/progresschecker/logic/Logic.java index 8b34b862039a..596a15019eb0 100644 --- a/src/main/java/seedu/address/logic/Logic.java +++ b/src/main/java/seedu/progresschecker/logic/Logic.java @@ -1,10 +1,12 @@ -package seedu.address.logic; +package seedu.progresschecker.logic; import javafx.collections.ObservableList; -import seedu.address.logic.commands.CommandResult; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.Person; +import seedu.progresschecker.logic.commands.CommandResult; +import seedu.progresschecker.logic.commands.exceptions.CommandException; +import seedu.progresschecker.logic.parser.exceptions.ParseException; +import seedu.progresschecker.model.exercise.Exercise; +import seedu.progresschecker.model.issues.Issue; +import seedu.progresschecker.model.person.Person; /** * API of the Logic component @@ -22,6 +24,12 @@ public interface Logic { /** Returns an unmodifiable view of the filtered list of persons */ ObservableList<Person> getFilteredPersonList(); + /** Returns an unmodifiable view of the filtered list of exercises */ + ObservableList<Exercise> getFilteredExerciseList(); + + /** Returns an unmodifiable view of the filtered list of issue */ + ObservableList<Issue> getFilteredIssueList(); + /** Returns the list of input entered by the user, encapsulated in a {@code ListElementPointer} object */ ListElementPointer getHistorySnapshot(); } diff --git a/src/main/java/seedu/progresschecker/logic/LogicManager.java b/src/main/java/seedu/progresschecker/logic/LogicManager.java new file mode 100644 index 000000000000..5f72119ec0d9 --- /dev/null +++ b/src/main/java/seedu/progresschecker/logic/LogicManager.java @@ -0,0 +1,85 @@ +package seedu.progresschecker.logic; + +import java.util.logging.Logger; + +import javafx.collections.ObservableList; +import seedu.progresschecker.commons.core.ComponentManager; +import seedu.progresschecker.commons.core.LogsCenter; +import seedu.progresschecker.logic.commands.Command; +import seedu.progresschecker.logic.commands.CommandResult; +import seedu.progresschecker.logic.commands.ViewTaskListCommand; +import seedu.progresschecker.logic.commands.exceptions.CommandException; +import seedu.progresschecker.logic.parser.ProgressCheckerParser; +import seedu.progresschecker.logic.parser.exceptions.ParseException; +import seedu.progresschecker.model.Model; +import seedu.progresschecker.model.exercise.Exercise; +import seedu.progresschecker.model.issues.Issue; +import seedu.progresschecker.model.person.Person; + +/** + * The main LogicManager of the app. + */ +public class LogicManager extends ComponentManager implements Logic { + private static ViewTaskListCommand viewTaskListCommand = new ViewTaskListCommand(0); + + private final Logger logger = LogsCenter.getLogger(LogicManager.class); + + private final Model model; + private final CommandHistory history; + private final ProgressCheckerParser progressCheckerParser; + private final UndoRedoStack undoRedoStack; + + public LogicManager(Model model) { + this.model = model; + history = new CommandHistory(); + progressCheckerParser = new ProgressCheckerParser(); + undoRedoStack = new UndoRedoStack(); + } + + @Override + public CommandResult execute(String commandText) throws CommandException, ParseException { + logger.info("----------------[USER COMMAND][" + commandText + "]"); + try { + Command command = progressCheckerParser.parseCommand(commandText); + if (commandText.contains(ViewTaskListCommand.COMMAND_WORD) + || commandText.contains(ViewTaskListCommand.COMMAND_ALIAS)) { + this.viewTaskListCommand = (ViewTaskListCommand) command; + } + command.setData(model, history, undoRedoStack); + CommandResult result = command.execute(); + undoRedoStack.push(command); + return result; + } finally { + history.add(commandText); + } + } + + public static ViewTaskListCommand getCurrentViewTask () { + return viewTaskListCommand; + } + + @Override + public ObservableList<Person> getFilteredPersonList() { + return model.getFilteredPersonList(); + } + + //@@author iNekox3 + @Override + public ObservableList<Exercise> getFilteredExerciseList() { + return model.getFilteredExerciseList(); + } + + //@@author + + //@@author adityaa1998 + @Override + public ObservableList<Issue> getFilteredIssueList() { + return model.getFilteredIssueList(); + } + + //@@author + @Override + public ListElementPointer getHistorySnapshot() { + return new ListElementPointer(history.getHistory()); + } +} diff --git a/src/main/java/seedu/address/logic/UndoRedoStack.java b/src/main/java/seedu/progresschecker/logic/UndoRedoStack.java similarity index 89% rename from src/main/java/seedu/address/logic/UndoRedoStack.java rename to src/main/java/seedu/progresschecker/logic/UndoRedoStack.java index ddb62ef0ea87..96c2a8811e31 100644 --- a/src/main/java/seedu/address/logic/UndoRedoStack.java +++ b/src/main/java/seedu/progresschecker/logic/UndoRedoStack.java @@ -1,11 +1,11 @@ -package seedu.address.logic; +package seedu.progresschecker.logic; import java.util.Stack; -import seedu.address.logic.commands.Command; -import seedu.address.logic.commands.RedoCommand; -import seedu.address.logic.commands.UndoCommand; -import seedu.address.logic.commands.UndoableCommand; +import seedu.progresschecker.logic.commands.Command; +import seedu.progresschecker.logic.commands.RedoCommand; +import seedu.progresschecker.logic.commands.UndoCommand; +import seedu.progresschecker.logic.commands.UndoableCommand; /** * Maintains the undo-stack (the stack of commands that can be undone) and the redo-stack (the stack of diff --git a/src/main/java/seedu/progresschecker/logic/apisetup/ConnectTasksApi.java b/src/main/java/seedu/progresschecker/logic/apisetup/ConnectTasksApi.java new file mode 100644 index 000000000000..95519256d6d0 --- /dev/null +++ b/src/main/java/seedu/progresschecker/logic/apisetup/ConnectTasksApi.java @@ -0,0 +1,87 @@ +package seedu.progresschecker.logic.apisetup; + +import java.io.File; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.Collections; + +import com.google.api.client.auth.oauth2.Credential; +import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp; +import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver; +import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow; +import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets; +import com.google.api.client.http.HttpTransport; +import com.google.api.client.http.javanet.NetHttpTransport; +import com.google.api.client.json.JsonFactory; +import com.google.api.client.json.jackson2.JacksonFactory; +import com.google.api.client.util.store.DataStoreFactory; +import com.google.api.client.util.store.FileDataStoreFactory; +import com.google.api.services.tasks.Tasks; +import com.google.api.services.tasks.TasksScopes; + +//@@author EdwardKSG +/** + * Sets up Google Tasks API. + * i. Authorizes data access based on client credentials. + * ii. Builds service (initializes API). + */ +public class ConnectTasksApi { + private static final String appName = "ProgressChecker"; + private JsonFactory jsonFactory; + private HttpTransport transport; + private Credential credentials; + + public ConnectTasksApi() { + this.jsonFactory = new JacksonFactory(); + this.transport = new NetHttpTransport(); + this.credentials = null; + } + + /** + * Authorizes the data access requested by Tasks API by loading client secrets file. + */ + public void authorize() throws Exception { + final java.util.logging.Logger buggyLogger = java.util.logging.Logger.getLogger( + FileDataStoreFactory.class.getName()); + buggyLogger.setLevel(java.util.logging.Level.SEVERE); + + // Sets up files to store access token + DataStoreFactory datastore = new FileDataStoreFactory( + new File("tokens") + ); + + InputStream in = + ConnectTasksApi.class.getResourceAsStream("/client_id.json"); + + // Loads Client Secrets file downloaded from Google developer console. + GoogleClientSecrets clientSecrets = GoogleClientSecrets.load( + this.jsonFactory, + new InputStreamReader(in) + ); + + // Sets Up authorization code flow. + GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder( + this.transport, + this.jsonFactory, + clientSecrets, + Collections.singleton(TasksScopes.TASKS) + ).setDataStoreFactory(datastore).build(); + + // Authorizes with client credentials. + this.credentials = new AuthorizationCodeInstalledApp( + flow, + new LocalServerReceiver() + ).authorize("user"); + } + + /** + * Builds Google Tasks service. + */ + public Tasks getTasksService() { + return new Tasks.Builder( + this.transport, + this.jsonFactory, + this.credentials + ).setApplicationName(appName).build(); + } +} diff --git a/src/main/java/seedu/address/logic/commands/AddCommand.java b/src/main/java/seedu/progresschecker/logic/commands/AddCommand.java similarity index 52% rename from src/main/java/seedu/address/logic/commands/AddCommand.java rename to src/main/java/seedu/progresschecker/logic/commands/AddCommand.java index c334710c0ea3..3dfeda179461 100644 --- a/src/main/java/seedu/address/logic/commands/AddCommand.java +++ b/src/main/java/seedu/progresschecker/logic/commands/AddCommand.java @@ -1,40 +1,55 @@ -package seedu.address.logic.commands; +package seedu.progresschecker.logic.commands; import static java.util.Objects.requireNonNull; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.progresschecker.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.progresschecker.logic.parser.CliSyntax.PREFIX_MAJOR; +import static seedu.progresschecker.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.progresschecker.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.progresschecker.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.progresschecker.logic.parser.CliSyntax.PREFIX_USERNAME; +import static seedu.progresschecker.logic.parser.CliSyntax.PREFIX_YEAR; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.person.Person; -import seedu.address.model.person.exceptions.DuplicatePersonException; +import seedu.progresschecker.logic.commands.exceptions.CommandException; +import seedu.progresschecker.model.person.Person; +import seedu.progresschecker.model.person.exceptions.DuplicatePersonException; /** - * Adds a person to the address book. + * Adds a person to the ProgressChecker. */ public class AddCommand extends UndoableCommand { public static final String COMMAND_WORD = "add"; + public static final String COMMAND_ALIAS = "a"; + public static final String COMMAND_FORMAT = COMMAND_WORD + " " + + PREFIX_NAME + "NAME " + + PREFIX_PHONE + "PHONE " + + PREFIX_EMAIL + "EMAIL " + + PREFIX_USERNAME + "USERNAME " + + PREFIX_MAJOR + "MAJOR " + + PREFIX_YEAR + "YEAR " + + "[" + PREFIX_TAG + "TAG]..."; - public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the address book. " + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Adds a person to the ProgressChecker. " + "Parameters: " + PREFIX_NAME + "NAME " + PREFIX_PHONE + "PHONE " + PREFIX_EMAIL + "EMAIL " - + PREFIX_ADDRESS + "ADDRESS " + + PREFIX_USERNAME + "USERNAME " + + PREFIX_MAJOR + "MAJOR " + + PREFIX_YEAR + "YEAR " + "[" + PREFIX_TAG + "TAG]...\n" + "Example: " + COMMAND_WORD + " " + PREFIX_NAME + "John Doe " + PREFIX_PHONE + "98765432 " + PREFIX_EMAIL + "johnd@example.com " - + PREFIX_ADDRESS + "311, Clementi Ave 2, #02-25 " + + PREFIX_USERNAME + "JohnGithub " + + PREFIX_MAJOR + "Computer Science " + + PREFIX_YEAR + "2 " + PREFIX_TAG + "friends " + PREFIX_TAG + "owesMoney"; public static final String MESSAGE_SUCCESS = "New person added: %1$s"; - public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book"; + public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the ProgressChecker"; private final Person toAdd; diff --git a/src/main/java/seedu/progresschecker/logic/commands/AddDefaultTasksCommand.java b/src/main/java/seedu/progresschecker/logic/commands/AddDefaultTasksCommand.java new file mode 100644 index 000000000000..57be828a79fd --- /dev/null +++ b/src/main/java/seedu/progresschecker/logic/commands/AddDefaultTasksCommand.java @@ -0,0 +1,82 @@ +package seedu.progresschecker.logic.commands; + +import static java.util.Objects.requireNonNull; + +import static seedu.progresschecker.model.task.TaskListUtil.clearTaskList; +import static seedu.progresschecker.model.task.TaskListUtil.copyTaskList; +import static seedu.progresschecker.model.task.TaskListUtil.createTaskList; +import static seedu.progresschecker.model.task.TaskListUtil.setTaskListTitle; +import static seedu.progresschecker.model.task.TaskUtil.addMultipleTask; +import static seedu.progresschecker.storage.DefaultTasks.getDefaultTasks; +import static seedu.progresschecker.storage.TestTasks.getTestTasks; + +import seedu.progresschecker.commons.core.EventsCenter; +import seedu.progresschecker.commons.events.ui.NewResultAvailableEvent; +import seedu.progresschecker.logic.commands.exceptions.CommandException; +import seedu.progresschecker.model.task.SimplifiedTask; + +//@@author EdwardKSG +/** + * Adds a default task list to the user's Google account. + */ +public class AddDefaultTasksCommand extends Command { + + public static final String COMMAND_WORD = "newtasklist"; + public static final String COMMAND_ALIAS = "nl"; // short for "new list" + public static final String SOURCE_FILE_FOLDER = "/view"; + public static final String SOURCE_FILE = "/defaultTasks.txt"; + public static final String TEST_FILE = "/testTasks.txt"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Creates a new task list. " + + "Parameters: " + + "LISTNAME " + + "Example: " + COMMAND_WORD + " " + + "CS2103 LOs"; + + public static final String MESSAGE_SUCCESS = "New task list added: %1$s"; + public static final String DEFAULT_LIST_TITLE = "CS2103 LOs"; + public static final String FIRST_LIST_TITLE = "My List"; + public static final String DEFAULT_LIST_ID = "@default"; + public static final String CREATE_FAILURE = "Failed to create task list " + + "due to unexpected interrupt or API error: "; + public static final String START_MASSEGE = "We have a lot of tasks to initialize. " + + "The preparation may take a long time, please be patient :) Thank you!"; + public static final String LIST_FINISH_MASSEGE = "Task List created. Now pushing tasks into it: 0/"; + public static final String PUSH_TASK_ONGOING = "Pushing tasks into your task list: "; + + private String listTitle; + + /** + * Creates a AddDefaultTasksCommand to add the default task list with title {@code Sting} + */ + public AddDefaultTasksCommand(String title) { + requireNonNull(title); + listTitle = title; + } + + @Override + public CommandResult execute() throws CommandException { + try { + EventsCenter.getInstance().post(new NewResultAvailableEvent(START_MASSEGE)); + setTaskListTitle(DEFAULT_LIST_ID, DEFAULT_LIST_TITLE); + createTaskList(FIRST_LIST_TITLE); + copyTaskList(FIRST_LIST_TITLE, DEFAULT_LIST_ID); + clearTaskList(DEFAULT_LIST_ID); + + SimplifiedTask[] tasklist; + if (!listTitle.equals(DEFAULT_LIST_TITLE)) { + tasklist = getTestTasks(); + } else { + tasklist = getDefaultTasks(); + } + + addMultipleTask(tasklist, DEFAULT_LIST_ID); + + return new CommandResult(String.format(MESSAGE_SUCCESS, DEFAULT_LIST_TITLE)); + } catch (CommandException ce) { + throw ce; + } catch (Exception e) { + throw new CommandException(CREATE_FAILURE + DEFAULT_LIST_TITLE); + } + } +} diff --git a/src/main/java/seedu/progresschecker/logic/commands/AnswerCommand.java b/src/main/java/seedu/progresschecker/logic/commands/AnswerCommand.java new file mode 100644 index 000000000000..4f9dc96d5598 --- /dev/null +++ b/src/main/java/seedu/progresschecker/logic/commands/AnswerCommand.java @@ -0,0 +1,132 @@ +package seedu.progresschecker.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.progresschecker.logic.commands.ViewCommand.MAX_WEEK_NUMBER; +import static seedu.progresschecker.logic.commands.ViewCommand.MIN_WEEK_NUMBER; + +import java.util.List; +import java.util.Objects; + +import seedu.progresschecker.commons.core.Messages; +import seedu.progresschecker.logic.commands.exceptions.CommandException; +import seedu.progresschecker.model.exercise.Exercise; +import seedu.progresschecker.model.exercise.ModelAnswer; +import seedu.progresschecker.model.exercise.Question; +import seedu.progresschecker.model.exercise.QuestionIndex; +import seedu.progresschecker.model.exercise.QuestionType; +import seedu.progresschecker.model.exercise.StudentAnswer; +import seedu.progresschecker.model.exercise.exceptions.ExerciseNotFoundException; + +//@@author iNekox3 +/** + * Edits details of student answer of an exercise in the ProgressChecker. + */ +public class AnswerCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "answer"; + public static final String COMMAND_ALIAS = "ans"; + public static final String COMMAND_FORMAT = COMMAND_WORD + " QUESTION-INDEX" + " ANSWER"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Answer an exercise " + + "identified by the index number shown. " + + "Existing answer will be overwritten by the input value.\n" + + "Parameters: INDEX (must be in the format of WEEK.SECTION.QUESTION number " + + "where WEEK range from " + MIN_WEEK_NUMBER + " to " + MAX_WEEK_NUMBER + ") " + + "ANSWER\n" + + "Example: " + COMMAND_WORD + " 2.1.1 " + + "Procedural languages work at simple data structures and functions level."; + + public static final String MESSAGE_EDIT_EXERCISE_SUCCESS = "Answered Exercise: %1$s"; + + private final QuestionIndex questionIndex; + private final StudentAnswer studentAnswer; + + private Exercise exerciseToEdit; + private Exercise editedExercise; + + /** + * @param questionIndex of the question in the filtered exercise list to edit + * @param studentAnswer answer to edit the exercise with + */ + public AnswerCommand(QuestionIndex questionIndex, StudentAnswer studentAnswer) { + requireNonNull(questionIndex); + + this.questionIndex = questionIndex; + this.studentAnswer = studentAnswer; + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + try { + model.updateExercise(exerciseToEdit, editedExercise); + } catch (ExerciseNotFoundException enfe) { + throw new AssertionError("The target exercise cannot be missing"); + } + model.updateFilteredExerciseList(exercise -> exercise.getQuestionIndex().getWeekNumber() + == editedExercise.getQuestionIndex().getWeekNumber()); + return new CommandResult(String.format(MESSAGE_EDIT_EXERCISE_SUCCESS, questionIndex)); + } + + //TODO: store mapping of questionIndex to exercise's index in exerciseList in a separate data structure + @Override + protected void preprocessUndoableCommand() throws CommandException { + List<Exercise> exerciseList = model.getFilteredExerciseList(); + boolean isFound = false; + + if (!questionIndex.isValidIndex(questionIndex.toString())) { + throw new CommandException(String.format( + Messages.MESSAGE_INVALID_EXERCISE_INDEX, questionIndex.toString())); + } + + for (Exercise e : exerciseList) { + if (e.getQuestionIndex().toString().equals(questionIndex.toString())) { + exerciseToEdit = exerciseList.get(exerciseList.indexOf(e)); + editedExercise = createEditedExercise(exerciseToEdit, studentAnswer); + isFound = true; + break; + } + } + + if (!isFound) { + throw new CommandException(String.format( + Messages.MESSAGE_INVALID_EXERCISE_INDEX, questionIndex.toString())); + } + } + + /** + * Creates and returns a {@code Exercise} with the details of {@code exerciseToEdit} + * edited with {@code editExerciseDescriptor}. + */ + private static Exercise createEditedExercise(Exercise exerciseToEdit, StudentAnswer studentAnswer) { + assert exerciseToEdit != null; + + QuestionIndex questionIndex = exerciseToEdit.getQuestionIndex(); + QuestionType questionType = exerciseToEdit.getQuestionType(); + Question question = exerciseToEdit.getQuestion(); + StudentAnswer updatedStudentAnswer = studentAnswer; + ModelAnswer modelAnswer = exerciseToEdit.getModelAnswer(); + + return new Exercise(questionIndex, questionType, question, + updatedStudentAnswer, modelAnswer); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof AnswerCommand)) { + return false; + } + + // state check + AnswerCommand e = (AnswerCommand) other; + return questionIndex.equals(e.questionIndex) + && studentAnswer.equals(e.studentAnswer) + && Objects.equals(exerciseToEdit, e.exerciseToEdit); + } + +} diff --git a/src/main/java/seedu/address/logic/commands/ClearCommand.java b/src/main/java/seedu/progresschecker/logic/commands/ClearCommand.java similarity index 64% rename from src/main/java/seedu/address/logic/commands/ClearCommand.java rename to src/main/java/seedu/progresschecker/logic/commands/ClearCommand.java index ceeb7ba913c6..18b13769d802 100644 --- a/src/main/java/seedu/address/logic/commands/ClearCommand.java +++ b/src/main/java/seedu/progresschecker/logic/commands/ClearCommand.java @@ -1,22 +1,23 @@ -package seedu.address.logic.commands; +package seedu.progresschecker.logic.commands; import static java.util.Objects.requireNonNull; -import seedu.address.model.AddressBook; +import seedu.progresschecker.model.ProgressChecker; /** - * Clears the address book. + * Clears the ProgressChecker. */ public class ClearCommand extends UndoableCommand { public static final String COMMAND_WORD = "clear"; + public static final String COMMAND_ALIAS = "c"; public static final String MESSAGE_SUCCESS = "Address book has been cleared!"; @Override public CommandResult executeUndoableCommand() { requireNonNull(model); - model.resetData(new AddressBook()); + model.resetData(new ProgressChecker()); return new CommandResult(MESSAGE_SUCCESS); } } diff --git a/src/main/java/seedu/progresschecker/logic/commands/CloseIssueCommand.java b/src/main/java/seedu/progresschecker/logic/commands/CloseIssueCommand.java new file mode 100644 index 000000000000..9e6c5a474d76 --- /dev/null +++ b/src/main/java/seedu/progresschecker/logic/commands/CloseIssueCommand.java @@ -0,0 +1,52 @@ +package seedu.progresschecker.logic.commands; + +import java.io.IOException; + +import seedu.progresschecker.commons.core.index.Index; +import seedu.progresschecker.logic.commands.exceptions.CommandException; + +//@@author adityaa1998 +/** + * Close an issue on github + */ +public class CloseIssueCommand extends Command { + + public static final String COMMAND_WORD = "-issue"; + public static final String COMMAND_ALIAS = "cli"; + public static final String COMMAND_FORMAT = COMMAND_WORD + " ISSUE-INDEX"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + "\nParameters: ISSUE_INDEX (must be a positive valid index number)" + + "Example: \n" + COMMAND_WORD + " 2"; + + public static final String MESSAGE_SUCCESS = "Issue #%1$s closed successfully"; + public static final String MESSAGE_FAILURE = "Issue wasn't closed. Enter correct index number."; + public static final String MESSAGE_AUTHENTICATION_FAILURE = "Github isn't authenticated. " + + "Use 'gitlogin' command to authenticate first"; + + private final Index targetIndex; + + public CloseIssueCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute() throws CommandException { + try { + model.closeIssueOnGithub(targetIndex); + } catch (IOException ie) { + throw new CommandException(MESSAGE_FAILURE); + } catch (CommandException ce) { + throw new CommandException(MESSAGE_AUTHENTICATION_FAILURE); + } + + return new CommandResult(String.format(MESSAGE_SUCCESS, targetIndex.getOneBased())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof CloseIssueCommand // instanceof handles nulls + && this.targetIndex.equals(((CloseIssueCommand) other).targetIndex)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/Command.java b/src/main/java/seedu/progresschecker/logic/commands/Command.java similarity index 79% rename from src/main/java/seedu/address/logic/commands/Command.java rename to src/main/java/seedu/progresschecker/logic/commands/Command.java index 6580e0b51c90..5e7bb34a7799 100644 --- a/src/main/java/seedu/address/logic/commands/Command.java +++ b/src/main/java/seedu/progresschecker/logic/commands/Command.java @@ -1,10 +1,10 @@ -package seedu.address.logic.commands; +package seedu.progresschecker.logic.commands; -import seedu.address.commons.core.Messages; -import seedu.address.logic.CommandHistory; -import seedu.address.logic.UndoRedoStack; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; +import seedu.progresschecker.commons.core.Messages; +import seedu.progresschecker.logic.CommandHistory; +import seedu.progresschecker.logic.UndoRedoStack; +import seedu.progresschecker.logic.commands.exceptions.CommandException; +import seedu.progresschecker.model.Model; /** * Represents a command with hidden internal logic and the ability to be executed. diff --git a/src/main/java/seedu/address/logic/commands/CommandResult.java b/src/main/java/seedu/progresschecker/logic/commands/CommandResult.java similarity index 86% rename from src/main/java/seedu/address/logic/commands/CommandResult.java rename to src/main/java/seedu/progresschecker/logic/commands/CommandResult.java index abdc267a2c44..f2e2192ba743 100644 --- a/src/main/java/seedu/address/logic/commands/CommandResult.java +++ b/src/main/java/seedu/progresschecker/logic/commands/CommandResult.java @@ -1,4 +1,4 @@ -package seedu.address.logic.commands; +package seedu.progresschecker.logic.commands; import static java.util.Objects.requireNonNull; diff --git a/src/main/java/seedu/progresschecker/logic/commands/CompleteTaskCommand.java b/src/main/java/seedu/progresschecker/logic/commands/CompleteTaskCommand.java new file mode 100644 index 000000000000..751156dba7b5 --- /dev/null +++ b/src/main/java/seedu/progresschecker/logic/commands/CompleteTaskCommand.java @@ -0,0 +1,90 @@ +package seedu.progresschecker.logic.commands; + +import static java.util.Objects.requireNonNull; + +import static seedu.progresschecker.logic.commands.AddDefaultTasksCommand.DEFAULT_LIST_ID; +import static seedu.progresschecker.model.task.TaskUtil.completeTask; + +import javafx.util.Pair; +import seedu.progresschecker.logic.LogicManager; +import seedu.progresschecker.logic.commands.exceptions.CommandException; + +//@@author EdwardKSG +/** + * Sets a task with given index as completed. + */ +public class CompleteTaskCommand extends Command { + + public static final String COMMAND_WORD = "complete"; + public static final String COMMAND_ALIAS = "ct"; // short for "complete task" + public static final String DATA_FOLDER = "data/"; + public static final String TASK_PAGE = "tasklist.html"; + public static final String FILE_FAILURE = "Something is wrong with the file system."; + public static final String COMMAND_FORMAT = COMMAND_WORD + "INDEX"; + public static final String MESSAGE_INDEX_CONSTRAINTS = "The index should be an index in the task list displayed" + + "to you. It must be an integer that does not exceed the number of tasks in the list."; + public static final int DUMMY_WEEK = 0; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Mark task with the given index in the list as completed.\n" + + "Parameters: INDEX (an index in the task list)\n " + + "Example: " + COMMAND_WORD + 1; + + public static final String MESSAGE_SUCCESS = "Keep it up! Completed task: %1$s"; + public static final String MESSAGE_NO_ACTION = "This task is already completed: %1$s"; + public static final String COMPLETE_FAILURE = "Error. Failed to mark it as completed. Index: %1$s"; + public static final String UNKNOWN_ERROR = "Unknow error in the system occurred"; + + public static final int ERROR = -1; + public static final int NO_ACTION = 0; + public static final int SUCCESS = 1; + + private int index; + + /** + * Complete the task with index {@code int} + */ + public CompleteTaskCommand(int index) { + requireNonNull(index); + this.index = index; + } + + @Override + public CommandResult execute() throws CommandException { + try { + Pair<Integer, String> result = completeTask(index, DEFAULT_LIST_ID); + + if (result.getKey() == ERROR) { + return new CommandResult(String.format(result.getValue())); + } + + String titleWithCode = result.getValue(); // full file name + String[] parts = titleWithCode.split("&#"); // String array, each element is text between dots + + String title = parts[0]; + + if (result.getKey() == NO_ACTION) { + return new CommandResult(String.format(MESSAGE_NO_ACTION, index + ". " + title)); + } else if (result.getKey() == SUCCESS) { + ViewTaskListCommand view = LogicManager.getCurrentViewTask(); + view.updateView(); + return new CommandResult(String.format(MESSAGE_SUCCESS, index + ". " + title)); + } else { + // the command parser could never pass any value other than the above 4, thus we say "unknown error". + throw new CommandException(UNKNOWN_ERROR); + } + + } catch (CommandException ce) { + throw ce; + } catch (Exception e) { + throw new CommandException(COMPLETE_FAILURE + index); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof CompleteTaskCommand // instanceof handles nulls + && index == (((CompleteTaskCommand) other).index)); + } +} diff --git a/src/main/java/seedu/progresschecker/logic/commands/CreateIssueCommand.java b/src/main/java/seedu/progresschecker/logic/commands/CreateIssueCommand.java new file mode 100644 index 000000000000..1b29a60ccd0b --- /dev/null +++ b/src/main/java/seedu/progresschecker/logic/commands/CreateIssueCommand.java @@ -0,0 +1,72 @@ +package seedu.progresschecker.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.progresschecker.logic.parser.CliSyntax.PREFIX_ASSIGNEES; +import static seedu.progresschecker.logic.parser.CliSyntax.PREFIX_BODY; +import static seedu.progresschecker.logic.parser.CliSyntax.PREFIX_LABEL; +import static seedu.progresschecker.logic.parser.CliSyntax.PREFIX_MILESTONE; +import static seedu.progresschecker.logic.parser.CliSyntax.PREFIX_TITLE; + +import java.io.IOException; + +import seedu.progresschecker.logic.commands.exceptions.CommandException; +import seedu.progresschecker.model.issues.Issue; + +//@@author adityaa1998 +/** + * Create an issue on github + */ +public class CreateIssueCommand extends Command { + + public static final String COMMAND_WORD = "+issue"; + public static final String COMMAND_ALIAS = "ci"; + public static final String COMMAND_FORMAT = COMMAND_WORD + " " + + PREFIX_TITLE + "TITLE " + + PREFIX_ASSIGNEES + "ASSIGNEES " + + PREFIX_MILESTONE + "MILESTONE " + + PREFIX_BODY + "BODY " + + PREFIX_LABEL + "LABELS"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Create an issue in your team organisation. " + + "Parameters: " + + PREFIX_TITLE + "TITLE " + + PREFIX_ASSIGNEES + "ASSIGNEES " + + PREFIX_MILESTONE + "MILESTONE " + + PREFIX_BODY + "BODY " + + PREFIX_LABEL + "LABELS/n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_TITLE + "Add new create issue command " + + PREFIX_ASSIGNEES + "johndoe " + + PREFIX_MILESTONE + "v1.1 " + + PREFIX_BODY + "This is a test issue " + + PREFIX_LABEL + "bug"; + public static final String MESSAGE_SUCCESS = "Issue successfully created on Github"; + public static final String MESSAGE_FAILURE = "Please log into github first"; + + private final Issue toCreate; + + /** + * Creates an CreateIssueCommand to create the specified {@code Issue} + */ + public CreateIssueCommand(Issue issue) { + requireNonNull(issue); + toCreate = issue; + } + @Override + public CommandResult execute() throws CommandException { + + try { + model.createIssueOnGitHub(toCreate); + return new CommandResult(MESSAGE_SUCCESS); + } catch (IOException | CommandException e) { + throw new CommandException(MESSAGE_FAILURE); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof CreateIssueCommand // instanceof handles nulls + && toCreate.equals(((CreateIssueCommand) other).toCreate)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/DeleteCommand.java b/src/main/java/seedu/progresschecker/logic/commands/DeleteCommand.java similarity index 80% rename from src/main/java/seedu/address/logic/commands/DeleteCommand.java rename to src/main/java/seedu/progresschecker/logic/commands/DeleteCommand.java index b539d240001a..2f14590e5dc4 100644 --- a/src/main/java/seedu/address/logic/commands/DeleteCommand.java +++ b/src/main/java/seedu/progresschecker/logic/commands/DeleteCommand.java @@ -1,22 +1,24 @@ -package seedu.address.logic.commands; +package seedu.progresschecker.logic.commands; import static java.util.Objects.requireNonNull; import java.util.List; import java.util.Objects; -import seedu.address.commons.core.Messages; -import seedu.address.commons.core.index.Index; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.person.Person; -import seedu.address.model.person.exceptions.PersonNotFoundException; +import seedu.progresschecker.commons.core.Messages; +import seedu.progresschecker.commons.core.index.Index; +import seedu.progresschecker.logic.commands.exceptions.CommandException; +import seedu.progresschecker.model.person.Person; +import seedu.progresschecker.model.person.exceptions.PersonNotFoundException; /** - * Deletes a person identified using it's last displayed index from the address book. + * Deletes a person identified using it's last displayed index from the ProgressChecker. */ public class DeleteCommand extends UndoableCommand { public static final String COMMAND_WORD = "delete"; + public static final String COMMAND_ALIAS = "d"; + public static final String COMMAND_FORMAT = COMMAND_WORD + " INDEX"; public static final String MESSAGE_USAGE = COMMAND_WORD + ": Deletes the person identified by the index number used in the last person listing.\n" diff --git a/src/main/java/seedu/address/logic/commands/EditCommand.java b/src/main/java/seedu/progresschecker/logic/commands/EditCommand.java similarity index 66% rename from src/main/java/seedu/address/logic/commands/EditCommand.java rename to src/main/java/seedu/progresschecker/logic/commands/EditCommand.java index e6c3a3e034bc..287057f3e53b 100644 --- a/src/main/java/seedu/address/logic/commands/EditCommand.java +++ b/src/main/java/seedu/progresschecker/logic/commands/EditCommand.java @@ -1,12 +1,14 @@ -package seedu.address.logic.commands; +package seedu.progresschecker.logic.commands; import static java.util.Objects.requireNonNull; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; +import static seedu.progresschecker.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.progresschecker.logic.parser.CliSyntax.PREFIX_MAJOR; +import static seedu.progresschecker.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.progresschecker.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.progresschecker.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.progresschecker.logic.parser.CliSyntax.PREFIX_USERNAME; +import static seedu.progresschecker.logic.parser.CliSyntax.PREFIX_YEAR; +import static seedu.progresschecker.model.Model.PREDICATE_SHOW_ALL_PERSONS; import java.util.Collections; import java.util.HashSet; @@ -15,25 +17,36 @@ import java.util.Optional; import java.util.Set; -import seedu.address.commons.core.Messages; -import seedu.address.commons.core.index.Index; -import seedu.address.commons.util.CollectionUtil; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.person.exceptions.DuplicatePersonException; -import seedu.address.model.person.exceptions.PersonNotFoundException; -import seedu.address.model.tag.Tag; +import seedu.progresschecker.commons.core.Messages; +import seedu.progresschecker.commons.core.index.Index; +import seedu.progresschecker.commons.util.CollectionUtil; +import seedu.progresschecker.logic.commands.exceptions.CommandException; +import seedu.progresschecker.model.person.Email; +import seedu.progresschecker.model.person.GithubUsername; +import seedu.progresschecker.model.person.Major; +import seedu.progresschecker.model.person.Name; +import seedu.progresschecker.model.person.Person; +import seedu.progresschecker.model.person.Phone; +import seedu.progresschecker.model.person.Year; +import seedu.progresschecker.model.person.exceptions.DuplicatePersonException; +import seedu.progresschecker.model.person.exceptions.PersonNotFoundException; +import seedu.progresschecker.model.tag.Tag; /** - * Edits the details of an existing person in the address book. + * Edits the details of an existing person in the ProgressChecker. */ public class EditCommand extends UndoableCommand { public static final String COMMAND_WORD = "edit"; + public static final String COMMAND_ALIAS = "ed"; + public static final String COMMAND_FORMAT = COMMAND_WORD + " " + "INDEX " + + "[" + PREFIX_NAME + "NAME] " + + "[" + PREFIX_PHONE + "PHONE] " + + "[" + PREFIX_EMAIL + "EMAIL] " + + "[" + PREFIX_USERNAME + "USERNAME] " + + "[" + PREFIX_MAJOR + "MAJOR] " + + "[" + PREFIX_YEAR + "YEAR] " + + "[" + PREFIX_TAG + "TAG]..."; public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of the person identified " + "by the index number used in the last person listing. " @@ -42,7 +55,9 @@ public class EditCommand extends UndoableCommand { + "[" + PREFIX_NAME + "NAME] " + "[" + PREFIX_PHONE + "PHONE] " + "[" + PREFIX_EMAIL + "EMAIL] " - + "[" + PREFIX_ADDRESS + "ADDRESS] " + + "[" + PREFIX_USERNAME + "USERNAME] " + + "[" + PREFIX_MAJOR + "MAJOR] " + + "[" + PREFIX_YEAR + "YEAR] " + "[" + PREFIX_TAG + "TAG]...\n" + "Example: " + COMMAND_WORD + " 1 " + PREFIX_PHONE + "91234567 " @@ -50,7 +65,7 @@ public class EditCommand extends UndoableCommand { public static final String MESSAGE_EDIT_PERSON_SUCCESS = "Edited Person: %1$s"; public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; - public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book."; + public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the ProgressChecker."; private final Index index; private final EditPersonDescriptor editPersonDescriptor; @@ -105,10 +120,13 @@ private static Person createEditedPerson(Person personToEdit, EditPersonDescript Name updatedName = editPersonDescriptor.getName().orElse(personToEdit.getName()); Phone updatedPhone = editPersonDescriptor.getPhone().orElse(personToEdit.getPhone()); Email updatedEmail = editPersonDescriptor.getEmail().orElse(personToEdit.getEmail()); - Address updatedAddress = editPersonDescriptor.getAddress().orElse(personToEdit.getAddress()); + GithubUsername updatedUsername = editPersonDescriptor.getUsername().orElse(personToEdit.getUsername()); + Major updatedMajor = editPersonDescriptor.getMajor().orElse(personToEdit.getMajor()); + Year updatedYear = editPersonDescriptor.getYear().orElse(personToEdit.getYear()); Set<Tag> updatedTags = editPersonDescriptor.getTags().orElse(personToEdit.getTags()); - return new Person(updatedName, updatedPhone, updatedEmail, updatedAddress, updatedTags); + return new Person(updatedName, updatedPhone, updatedEmail, updatedUsername, updatedMajor, + updatedYear, updatedTags); } @Override @@ -138,7 +156,9 @@ public static class EditPersonDescriptor { private Name name; private Phone phone; private Email email; - private Address address; + private GithubUsername username; + private Major major; + private Year year; private Set<Tag> tags; public EditPersonDescriptor() {} @@ -151,7 +171,9 @@ public EditPersonDescriptor(EditPersonDescriptor toCopy) { setName(toCopy.name); setPhone(toCopy.phone); setEmail(toCopy.email); - setAddress(toCopy.address); + setUsername(toCopy.username); + setMajor(toCopy.major); + setYear(toCopy.year); setTags(toCopy.tags); } @@ -159,7 +181,8 @@ public EditPersonDescriptor(EditPersonDescriptor toCopy) { * Returns true if at least one field is edited. */ public boolean isAnyFieldEdited() { - return CollectionUtil.isAnyNonNull(this.name, this.phone, this.email, this.address, this.tags); + return CollectionUtil.isAnyNonNull(this.name, this.phone, this.email, this.username, + this.major, this.year, this.tags); } public void setName(Name name) { @@ -186,12 +209,28 @@ public Optional<Email> getEmail() { return Optional.ofNullable(email); } - public void setAddress(Address address) { - this.address = address; + public void setUsername(GithubUsername username) { + this.username = username; + } + + public Optional<GithubUsername> getUsername() { + return Optional.ofNullable(username); + } + + public void setMajor(Major major) { + this.major = major; + } + + public Optional<Major> getMajor() { + return Optional.ofNullable(major); + } + + public void setYear(Year year) { + this.year = year; } - public Optional<Address> getAddress() { - return Optional.ofNullable(address); + public Optional<Year> getYear() { + return Optional.ofNullable(year); } /** @@ -229,7 +268,9 @@ public boolean equals(Object other) { return getName().equals(e.getName()) && getPhone().equals(e.getPhone()) && getEmail().equals(e.getEmail()) - && getAddress().equals(e.getAddress()) + && getUsername().equals(e.getUsername()) + && getMajor().equals(e.getMajor()) + && getYear().equals(e.getYear()) && getTags().equals(e.getTags()); } } diff --git a/src/main/java/seedu/progresschecker/logic/commands/EditIssueCommand.java b/src/main/java/seedu/progresschecker/logic/commands/EditIssueCommand.java new file mode 100644 index 000000000000..59b2d3c84f4f --- /dev/null +++ b/src/main/java/seedu/progresschecker/logic/commands/EditIssueCommand.java @@ -0,0 +1,295 @@ +package seedu.progresschecker.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.progresschecker.logic.parser.CliSyntax.PREFIX_ASSIGNEES; +import static seedu.progresschecker.logic.parser.CliSyntax.PREFIX_BODY; +import static seedu.progresschecker.logic.parser.CliSyntax.PREFIX_LABEL; +import static seedu.progresschecker.logic.parser.CliSyntax.PREFIX_MILESTONE; +import static seedu.progresschecker.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.progresschecker.logic.parser.CliSyntax.PREFIX_TITLE; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +import org.kohsuke.github.GHIssue; +import org.kohsuke.github.GHLabel; +import org.kohsuke.github.GHRepository; +import org.kohsuke.github.GHUser; +import org.kohsuke.github.GitHub; + +import seedu.progresschecker.commons.core.index.Index; +import seedu.progresschecker.commons.util.CollectionUtil; +import seedu.progresschecker.logic.commands.exceptions.CommandException; +import seedu.progresschecker.model.issues.Assignees; +import seedu.progresschecker.model.issues.Body; +import seedu.progresschecker.model.issues.Issue; +import seedu.progresschecker.model.issues.Labels; +import seedu.progresschecker.model.issues.Milestone; +import seedu.progresschecker.model.issues.Title; + +//@@author adityaa1998 +/** + * Edits the details of an existing issue on Github. + */ +public class EditIssueCommand extends Command { + public static final String COMMAND_WORD = "editissue"; + public static final String COMMAND_ALIAS = "edi"; + public static final String COMMAND_FORMAT = COMMAND_WORD + " " + "INDEX " + + "[" + PREFIX_TITLE + "TITLE] " + + "[" + PREFIX_ASSIGNEES + "ASSIGNEES] " + + "[" + PREFIX_MILESTONE + "MILESTONE] " + + "[" + PREFIX_BODY + "BODY] " + + "[" + PREFIX_LABEL + "LABEL]..."; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Edits the details of an existing issue " + + "by the index number used in the issue listing. " + + "Existing values will be overwritten by the input values.\n" + + "Parameters: INDEX (must be a positive integer) " + + "[" + PREFIX_TITLE + "TITLE] " + + "[" + PREFIX_ASSIGNEES + "ASSIGNEES] " + + "[" + PREFIX_MILESTONE + "MILESTONE] " + + "[" + PREFIX_BODY + "BODY] " + + "[" + PREFIX_LABEL + "LABEL]..." + + "[" + PREFIX_TAG + "TAG]...\n" + + "Example: " + COMMAND_WORD + " 5 " + + PREFIX_TITLE + "Make a new attribute " + + PREFIX_MILESTONE + "v1.3"; + + public static final String MESSAGE_EDIT_ISSUE_SUCCESS = "Issue #%d was successfully edited."; + public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided."; + public static final String MESSAGE_ISSUE_INVALID = "Issue doesn't exist, enter correct index"; + + private final String repoName = new String("AdityaA1998/samplerepo-pr-practice"); + private final String userLogin = new String("anminkang"); + private final String userAuthentication = new String("aditya2018"); + + private final Index index; + private final EditIssueCommand.EditIssueDescriptor editIssueDescriptor; + + private Issue issueToEdit; + private Issue editedIssue; + + /** + * @param index of the issue on github that is to be edited + * @param editIssueDescriptor details to edit the issue with + */ + public EditIssueCommand(Index index, EditIssueCommand.EditIssueDescriptor editIssueDescriptor) + throws CommandException, IOException { + requireNonNull(index); + requireNonNull(editIssueDescriptor); + + this.index = index; + this.editIssueDescriptor = new EditIssueCommand.EditIssueDescriptor(editIssueDescriptor); + preprocess(); + } + + @Override + public CommandResult execute() throws CommandException { + try { + model.updateIssue(index, editedIssue); + } catch (IOException io) { + throw new CommandException(io.getLocalizedMessage()); + } + return new CommandResult(String.format(MESSAGE_EDIT_ISSUE_SUCCESS, index.getOneBased())); + } + + /** + * Preprocess data for existing issue + * @throws CommandException is thrown when invalid issue index is used + * @throws IOException when the authentication fails + */ + private void preprocess() throws CommandException, IOException { + GitHub github = GitHub.connectUsingPassword(userLogin, userAuthentication); + GHRepository repository = github.getRepository(repoName); + GHIssue issue; + try { + issue = repository.getIssue(index.getOneBased()); + } catch (IOException ie) { + throw new CommandException(MESSAGE_ISSUE_INVALID); + } + List<GHUser> gitAssigneeList = issue.getAssignees(); + ArrayList<GHLabel> gitLabelsList = new ArrayList<>(issue.getLabels()); + List<Assignees> assigneesList = new ArrayList<>(); + List<Labels> labelsList = new ArrayList<>(); + Milestone existingMilestone = null; + Body existingBody = new Body(""); + + if (issue.getMilestone() == null) { + existingMilestone = null; + } else { + existingMilestone = new Milestone(issue.getMilestone().getTitle()); + } + + for (int i = 0; i < gitAssigneeList.size(); i++) { + assigneesList.add(new Assignees(gitAssigneeList.get(i).getLogin())); + } + + for (int i = 0; i < labelsList.size(); i++) { + labelsList.add(new Labels(gitLabelsList.get(i).getName())); + } + + issueToEdit = new Issue(new Title(issue.getTitle()), assigneesList, existingMilestone, + existingBody, labelsList); + editedIssue = createEditedIssue(issueToEdit, editIssueDescriptor); + + } + /** + * Creates and returns a {@code Issue} with the details of {@code issueToEdit} + * edited with {@code editIssueDescriptor}. + */ + private static Issue createEditedIssue(Issue issueToEdit, + EditIssueCommand.EditIssueDescriptor editIssueDescriptor) { + assert issueToEdit != null; + + Title updatedTitle = editIssueDescriptor.getTitle().orElse(issueToEdit.getTitle()); + Set<Assignees> updatedAssignees = editIssueDescriptor.getAssignees() + .orElse(new HashSet<>(issueToEdit.getAssignees())); + Milestone updatedMilestone = editIssueDescriptor.getMilestone().orElse(issueToEdit.getMilestone()); + Body updatedBody = editIssueDescriptor.getBody().orElse(issueToEdit.getBody()); + Set<Labels> updatedLabels = editIssueDescriptor.getLabels().orElse(new HashSet<>(issueToEdit.getLabelsList())); + + List<Assignees> updatedAssigneesList = new ArrayList<>(updatedAssignees); + List<Labels> updatedLabelsList = new ArrayList<>(updatedLabels); + + return new Issue(updatedTitle, updatedAssigneesList, updatedMilestone, updatedBody, updatedLabelsList); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditIssueCommand)) { + return false; + } + + // state check + EditIssueCommand e = (EditIssueCommand) other; + return index.equals(e.index) + && editIssueDescriptor.equals(e.editIssueDescriptor) + && Objects.equals(issueToEdit, e.issueToEdit); + } + + /** + * Stores the details to edit the issue with. Each non-empty field value will replace the + * corresponding field value of the Issue. + */ + public static class EditIssueDescriptor { + private Title title; + private Set<Assignees> assignees; + private Milestone milestone; + private Body body; + private Set<Labels> labels; + + public EditIssueDescriptor() {} + + /** + * Copy constructor. + * A defensive copy of {@code labels} is used internally. + */ + public EditIssueDescriptor(EditIssueCommand.EditIssueDescriptor toCopy) { + setTitle(toCopy.title); + setAssignees(toCopy.assignees); + setMilestone(toCopy.milestone); + setBody(toCopy.body); + setLabels(toCopy.labels); + } + + /** + * Returns true if at least one field is edited. + */ + public boolean isAnyFieldEdited() { + return CollectionUtil.isAnyNonNull(this.title, this.assignees, this.milestone, this.body, + this.labels); + } + + public void setTitle(Title title) { + this.title = title; + } + + public Optional<Title> getTitle() { + return Optional.ofNullable(title); + } + + /** + * Sets {@code assignees} to this object's {@code assignees}. + * A defensive copy of {@code assignees} is used internally. + */ + public void setAssignees(Set<Assignees> assignees) { + this.assignees = (assignees != null) ? new HashSet<>(assignees) : null; + } + + /** + * Returns an unmodifiable assignees set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * Returns {@code Optional#empty()} if {@code labels} is null. + */ + public Optional<Set<Assignees>> getAssignees() { + return (assignees != null) ? Optional.of(Collections.unmodifiableSet(assignees)) : Optional.empty(); + } + + public void setMilestone(Milestone milestone) { + this.milestone = milestone; + } + + public Optional<Milestone> getMilestone() { + return Optional.ofNullable(milestone); + } + + public void setBody(Body body) { + this.body = body; + } + + public Optional<Body> getBody() { + return Optional.ofNullable(body); + } + + /** + * Sets {@code labels} to this object's {@code labels}. + * A defensive copy of {@code labels} is used internally. + */ + public void setLabels(Set<Labels> labels) { + this.labels = (labels != null) ? new HashSet<>(labels) : null; + } + + /** + * Returns an unmodifiable labels set, which throws {@code UnsupportedOperationException} + * if modification is attempted. + * Returns {@code Optional#empty()} if {@code labels} is null. + */ + public Optional<Set<Labels>> getLabels() { + return (labels != null) ? Optional.of(Collections.unmodifiableSet(labels)) : Optional.empty(); + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof EditCommand.EditPersonDescriptor)) { + return false; + } + + // state check + EditIssueCommand.EditIssueDescriptor e = (EditIssueCommand.EditIssueDescriptor) other; + + return getTitle().equals(e.getTitle()) + && getAssignees().equals(e.getAssignees()) + && getMilestone().equals(e.getMilestone()) + && getBody().equals(e.getBody()) + && getLabels().equals(e.getLabels()); + } + } +} diff --git a/src/main/java/seedu/address/logic/commands/ExitCommand.java b/src/main/java/seedu/progresschecker/logic/commands/ExitCommand.java similarity index 66% rename from src/main/java/seedu/address/logic/commands/ExitCommand.java rename to src/main/java/seedu/progresschecker/logic/commands/ExitCommand.java index fbd1beb2966e..3e639b6f8b68 100644 --- a/src/main/java/seedu/address/logic/commands/ExitCommand.java +++ b/src/main/java/seedu/progresschecker/logic/commands/ExitCommand.java @@ -1,7 +1,7 @@ -package seedu.address.logic.commands; +package seedu.progresschecker.logic.commands; -import seedu.address.commons.core.EventsCenter; -import seedu.address.commons.events.ui.ExitAppRequestEvent; +import seedu.progresschecker.commons.core.EventsCenter; +import seedu.progresschecker.commons.events.ui.ExitAppRequestEvent; /** * Terminates the program. @@ -9,6 +9,7 @@ public class ExitCommand extends Command { public static final String COMMAND_WORD = "exit"; + public static final String COMMAND_ALIAS = "e"; public static final String MESSAGE_EXIT_ACKNOWLEDGEMENT = "Exiting Address Book as requested ..."; diff --git a/src/main/java/seedu/address/logic/commands/FindCommand.java b/src/main/java/seedu/progresschecker/logic/commands/FindCommand.java similarity index 77% rename from src/main/java/seedu/address/logic/commands/FindCommand.java rename to src/main/java/seedu/progresschecker/logic/commands/FindCommand.java index b1e671f633d2..da3a25707604 100644 --- a/src/main/java/seedu/address/logic/commands/FindCommand.java +++ b/src/main/java/seedu/progresschecker/logic/commands/FindCommand.java @@ -1,14 +1,16 @@ -package seedu.address.logic.commands; +package seedu.progresschecker.logic.commands; -import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.progresschecker.model.person.NameContainsKeywordsPredicate; /** - * Finds and lists all persons in address book whose name contains any of the argument keywords. + * Finds and lists all persons in ProgressChecker whose name contains any of the argument keywords. * Keyword matching is case sensitive. */ public class FindCommand extends Command { public static final String COMMAND_WORD = "find"; + public static final String COMMAND_ALIAS = "search"; + public static final String COMMAND_FORMAT = COMMAND_WORD + " NAME"; public static final String MESSAGE_USAGE = COMMAND_WORD + ": Finds all persons whose names contain any of " + "the specified keywords (case-sensitive) and displays them as a list with index numbers.\n" diff --git a/src/main/java/seedu/progresschecker/logic/commands/GitLoginCommand.java b/src/main/java/seedu/progresschecker/logic/commands/GitLoginCommand.java new file mode 100644 index 000000000000..903c6b03c450 --- /dev/null +++ b/src/main/java/seedu/progresschecker/logic/commands/GitLoginCommand.java @@ -0,0 +1,68 @@ +package seedu.progresschecker.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.progresschecker.logic.parser.CliSyntax.PREFIX_GIT_PASSCODE; +import static seedu.progresschecker.logic.parser.CliSyntax.PREFIX_GIT_REPO; +import static seedu.progresschecker.logic.parser.CliSyntax.PREFIX_GIT_USERNAME; + +import java.io.IOException; + +import seedu.progresschecker.logic.commands.exceptions.CommandException; +import seedu.progresschecker.model.credentials.GitDetails; + +//@@author adityaa1998 +/** + * Logins into github from app for issue creation + */ +public class GitLoginCommand extends Command { + + public static final String COMMAND_WORD = "gitlogin"; + public static final String COMMAND_ALIAS = "gl"; + public static final String COMMAND_FORMAT = COMMAND_WORD + " " + + PREFIX_GIT_USERNAME + "USERNAME " + + PREFIX_GIT_PASSCODE + "PASSCODE " + + PREFIX_GIT_REPO + "REPOSITORY "; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Logs into github \n" + + "Parameters: " + + PREFIX_GIT_USERNAME + "USERNAME " + + PREFIX_GIT_PASSCODE + "PASSCODE " + + PREFIX_GIT_REPO + "REPOSITORY \n" + + "Example: " + COMMAND_WORD + " " + + PREFIX_GIT_USERNAME + "johndoe " + + PREFIX_GIT_PASSCODE + "dummy123 " + + PREFIX_GIT_REPO + "CS2103/main "; + public static final String MESSAGE_SUCCESS = "You have successfully authenticated github!"; + public static final String MESSAGE_FAILURE = "Oops? Maybe the password or the username is incorrect"; + + private final GitDetails toAuthenticate; + + /** + * Creates an GitDetails object to authenticate with github {@code GitDetails} + */ + public GitLoginCommand(GitDetails gitDetails) { + requireNonNull(gitDetails); + toAuthenticate = gitDetails; + } + + @Override + public CommandResult execute() throws CommandException { + + try { + model.loginGithub(toAuthenticate); + return new CommandResult(MESSAGE_SUCCESS); + } catch (IOException e) { + throw new CommandException(MESSAGE_FAILURE); + } catch (CommandException ce) { + throw new CommandException(ce.getMessage()); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof GitLoginCommand // instanceof handles nulls + && toAuthenticate.equals(((GitLoginCommand) other).toAuthenticate)); + } + +} diff --git a/src/main/java/seedu/progresschecker/logic/commands/GitLogoutCommand.java b/src/main/java/seedu/progresschecker/logic/commands/GitLogoutCommand.java new file mode 100644 index 000000000000..55226495ec71 --- /dev/null +++ b/src/main/java/seedu/progresschecker/logic/commands/GitLogoutCommand.java @@ -0,0 +1,30 @@ +package seedu.progresschecker.logic.commands; + +import seedu.progresschecker.logic.commands.exceptions.CommandException; + +//@@author adityaa1998 +/** + * Logs out of github + */ +public class GitLogoutCommand extends Command { + + public static final String COMMAND_WORD = "gitlogout"; + public static final String COMMAND_ALIAS = "glo"; + public static final String COMMAND_FORMAT = COMMAND_WORD; + + public static final String MESSAGE_USAGE = COMMAND_WORD; + public static final String MESSAGE_SUCCESS = "You have successfully logged out of github!"; + public static final String MESSAGE_FAILURE = "You are currently not logged in"; + + @Override + public CommandResult execute() throws CommandException { + + try { + model.logoutGithub(); + return new CommandResult(MESSAGE_SUCCESS); + } catch (CommandException e) { + throw new CommandException(MESSAGE_FAILURE); + } + } + +} diff --git a/src/main/java/seedu/progresschecker/logic/commands/GoToTaskUrlCommand.java b/src/main/java/seedu/progresschecker/logic/commands/GoToTaskUrlCommand.java new file mode 100644 index 000000000000..035cf8eee1d2 --- /dev/null +++ b/src/main/java/seedu/progresschecker/logic/commands/GoToTaskUrlCommand.java @@ -0,0 +1,79 @@ +package seedu.progresschecker.logic.commands; + +import static java.util.Objects.requireNonNull; + +import static seedu.progresschecker.logic.commands.AddDefaultTasksCommand.DEFAULT_LIST_ID; +import static seedu.progresschecker.logic.commands.ViewTaskListCommand.TASK_TAB; +import static seedu.progresschecker.model.task.TaskUtil.INDEX_OUT_OF_BOUND; +import static seedu.progresschecker.model.task.TaskUtil.getTaskUrl; + +import javafx.util.Pair; +import seedu.progresschecker.commons.core.EventsCenter; +import seedu.progresschecker.commons.events.ui.LoadUrlEvent; +import seedu.progresschecker.commons.events.ui.TabLoadChangedEvent; +import seedu.progresschecker.logic.commands.exceptions.CommandException; + +//@@author EdwardKSG +/** + * Goes to the webpage of task with given index. + */ +public class GoToTaskUrlCommand extends Command { + + public static final String COMMAND_WORD = "goto"; + public static final String COMMAND_ALIAS = "go"; + public static final String COMMAND_FORMAT = COMMAND_WORD + "INDEX"; + public static final String MESSAGE_INDEX_CONSTRAINTS = "The index should be an index in the task list displayed" + + "to you. It must be an integer that does not exceed the number of tasks in the list."; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Open the URL of task with the given index in the list.\n" + + "Parameters: INDEX (an index in the task list)\n " + + "Example: " + COMMAND_WORD + 1; + + public static final String MESSAGE_SUCCESS = "Viewing webpage of task: %1$s"; + public static final String MESSAGE_NO_URL = "This task does not have a webpage: %1$s"; + public static final String VIEW_URL_FAILURE = "Error. Cannot open the URL. Index: %1$s"; + + private int index; + + /** + * Gets URL of the task with index {@code int} + */ + public GoToTaskUrlCommand(int index) { + requireNonNull(index); + this.index = index; + } + + @Override + public CommandResult execute() throws CommandException { + try { + Pair<String, String> result = getTaskUrl(index, DEFAULT_LIST_ID); + + String url = result.getKey(); + + String titleWithCode = result.getValue(); // full file name + String[] parts = titleWithCode.split("&#"); // String array, each element is text between dots + String title = parts[0]; + + if (url.equals("")) { + return new CommandResult(String.format(INDEX_OUT_OF_BOUND)); + } + + EventsCenter.getInstance().post(new LoadUrlEvent(url)); + EventsCenter.getInstance().post(new TabLoadChangedEvent(TASK_TAB)); + + return new CommandResult(String.format(MESSAGE_SUCCESS, index + ". " + title)); + } catch (CommandException ce) { + throw ce; + } catch (Exception e) { + throw new CommandException(VIEW_URL_FAILURE + index); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof GoToTaskUrlCommand // instanceof handles nulls + && index == (((GoToTaskUrlCommand) other).index)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/HelpCommand.java b/src/main/java/seedu/progresschecker/logic/commands/HelpCommand.java similarity index 72% rename from src/main/java/seedu/address/logic/commands/HelpCommand.java rename to src/main/java/seedu/progresschecker/logic/commands/HelpCommand.java index 10febf6d9136..c8f27e7555cc 100644 --- a/src/main/java/seedu/address/logic/commands/HelpCommand.java +++ b/src/main/java/seedu/progresschecker/logic/commands/HelpCommand.java @@ -1,7 +1,7 @@ -package seedu.address.logic.commands; +package seedu.progresschecker.logic.commands; -import seedu.address.commons.core.EventsCenter; -import seedu.address.commons.events.ui.ShowHelpRequestEvent; +import seedu.progresschecker.commons.core.EventsCenter; +import seedu.progresschecker.commons.events.ui.ShowHelpRequestEvent; /** * Format full help instructions for every command for display. @@ -9,6 +9,7 @@ public class HelpCommand extends Command { public static final String COMMAND_WORD = "help"; + public static final String COMMAND_ALIAS = "h"; public static final String MESSAGE_USAGE = COMMAND_WORD + ": Shows program usage instructions.\n" + "Example: " + COMMAND_WORD; diff --git a/src/main/java/seedu/address/logic/commands/HistoryCommand.java b/src/main/java/seedu/progresschecker/logic/commands/HistoryCommand.java similarity index 84% rename from src/main/java/seedu/address/logic/commands/HistoryCommand.java rename to src/main/java/seedu/progresschecker/logic/commands/HistoryCommand.java index f87abee5511d..235eef86986a 100644 --- a/src/main/java/seedu/address/logic/commands/HistoryCommand.java +++ b/src/main/java/seedu/progresschecker/logic/commands/HistoryCommand.java @@ -1,13 +1,13 @@ -package seedu.address.logic.commands; +package seedu.progresschecker.logic.commands; import static java.util.Objects.requireNonNull; import java.util.Collections; import java.util.List; -import seedu.address.logic.CommandHistory; -import seedu.address.logic.UndoRedoStack; -import seedu.address.model.Model; +import seedu.progresschecker.logic.CommandHistory; +import seedu.progresschecker.logic.UndoRedoStack; +import seedu.progresschecker.model.Model; /** * Lists all the commands entered by user from the start of app launch. diff --git a/src/main/java/seedu/address/logic/commands/ListCommand.java b/src/main/java/seedu/progresschecker/logic/commands/ListCommand.java similarity index 60% rename from src/main/java/seedu/address/logic/commands/ListCommand.java rename to src/main/java/seedu/progresschecker/logic/commands/ListCommand.java index 7b6463780824..9c79ac3eb7a0 100644 --- a/src/main/java/seedu/address/logic/commands/ListCommand.java +++ b/src/main/java/seedu/progresschecker/logic/commands/ListCommand.java @@ -1,13 +1,14 @@ -package seedu.address.logic.commands; +package seedu.progresschecker.logic.commands; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; +import static seedu.progresschecker.model.Model.PREDICATE_SHOW_ALL_PERSONS; /** - * Lists all persons in the address book to the user. + * Lists all persons in the ProgressChecker to the user. */ public class ListCommand extends Command { public static final String COMMAND_WORD = "list"; + public static final String COMMAND_ALIAS = "l"; public static final String MESSAGE_SUCCESS = "Listed all persons"; diff --git a/src/main/java/seedu/progresschecker/logic/commands/ListIssuesCommand.java b/src/main/java/seedu/progresschecker/logic/commands/ListIssuesCommand.java new file mode 100644 index 000000000000..338ce420a174 --- /dev/null +++ b/src/main/java/seedu/progresschecker/logic/commands/ListIssuesCommand.java @@ -0,0 +1,57 @@ +package seedu.progresschecker.logic.commands; + +import java.io.IOException; + +import seedu.progresschecker.commons.core.EventsCenter; +import seedu.progresschecker.commons.events.ui.TabLoadChangedEvent; +import seedu.progresschecker.commons.exceptions.IllegalValueException; +import seedu.progresschecker.logic.commands.exceptions.CommandException; + +//@@author adityaa1998 +/** + * Finds and lists all issues from github with the specified state in the argument. + * Keyword matching is case insensitive. + */ +public class ListIssuesCommand extends Command { + + public static final String COMMAND_WORD = "listissue"; + public static final String COMMAND_ALIAS = "lis"; + public static final String COMMAND_FORMAT = COMMAND_WORD + " STATE"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Lists all the issues " + + "of the specified state with the respective github issue index.\n" + + "Parameters: KEYWORD\n" + + "Example: " + COMMAND_WORD + " CLOSE"; + private static final String MESSAGE_INVALID_STATE = "Please enter correct issue state"; + private static final String MESSAGE_VALIDATION_FAILURE = "Please log into github first"; + private static final String tabType = "issues"; + private static final String MESSAGE_SUCCESS = "All the %s issues are being viewed"; + + private static String state; + + public ListIssuesCommand(String state) { + this.state = state; + } + + @Override + public CommandResult execute() throws CommandException { + try { + model.listIssues(state); + EventsCenter.getInstance().post(new TabLoadChangedEvent(tabType)); + return new CommandResult(String.format(MESSAGE_SUCCESS, state)); + } catch (IllegalValueException ie) { + throw new CommandException(MESSAGE_INVALID_STATE); + } catch (IOException ie) { + throw new CommandException(MESSAGE_INVALID_STATE); + } catch (CommandException ce) { + throw new CommandException(MESSAGE_VALIDATION_FAILURE); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ListIssuesCommand // instanceof handles nulls + && this.state.equals(((ListIssuesCommand) other).state)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/RedoCommand.java b/src/main/java/seedu/progresschecker/logic/commands/RedoCommand.java similarity index 67% rename from src/main/java/seedu/address/logic/commands/RedoCommand.java rename to src/main/java/seedu/progresschecker/logic/commands/RedoCommand.java index 7b99d0f372fc..847b7c3bed6b 100644 --- a/src/main/java/seedu/address/logic/commands/RedoCommand.java +++ b/src/main/java/seedu/progresschecker/logic/commands/RedoCommand.java @@ -1,11 +1,11 @@ -package seedu.address.logic.commands; +package seedu.progresschecker.logic.commands; -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.progresschecker.commons.util.CollectionUtil.requireAllNonNull; -import seedu.address.logic.CommandHistory; -import seedu.address.logic.UndoRedoStack; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; +import seedu.progresschecker.logic.CommandHistory; +import seedu.progresschecker.logic.UndoRedoStack; +import seedu.progresschecker.logic.commands.exceptions.CommandException; +import seedu.progresschecker.model.Model; /** * Redo the previously undone command. @@ -13,6 +13,7 @@ public class RedoCommand extends Command { public static final String COMMAND_WORD = "redo"; + public static final String COMMAND_ALIAS = "r"; public static final String MESSAGE_SUCCESS = "Redo success!"; public static final String MESSAGE_FAILURE = "No more commands to redo!"; diff --git a/src/main/java/seedu/progresschecker/logic/commands/ReopenIssueCommand.java b/src/main/java/seedu/progresschecker/logic/commands/ReopenIssueCommand.java new file mode 100644 index 000000000000..59b8394eb176 --- /dev/null +++ b/src/main/java/seedu/progresschecker/logic/commands/ReopenIssueCommand.java @@ -0,0 +1,53 @@ +package seedu.progresschecker.logic.commands; + +import java.io.IOException; + +import seedu.progresschecker.commons.core.index.Index; +import seedu.progresschecker.logic.commands.exceptions.CommandException; + +//@@author adityaa1998 +/** + * Reopens an issue on github + */ +public class ReopenIssueCommand extends Command { + + public static final String COMMAND_WORD = "reopenissue"; + public static final String COMMAND_ALIAS = "ri"; + public static final String COMMAND_FORMAT = COMMAND_WORD + " ISSUE-INDEX"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + "\nParameters: ISSUE_INDEX (must be a positive valid index number)" + + "Example: \n" + COMMAND_WORD + " 2"; + + public static final String MESSAGE_SUCCESS = "Issue #%1$s was reopened successfully"; + public static final String MESSAGE_FAILURE = "Issue wasn't reopened. Enter correct index number."; + public static final String MESSAGE_AUTHENTICATION_FAILURE = "Github isn't authenticated. " + + "Use 'gitlogin' command to authenticate first"; + + private final Index targetIndex; + + public ReopenIssueCommand(Index targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public CommandResult execute() throws CommandException { + try { + model.reopenIssueOnGithub(targetIndex); + } catch (IOException ie) { + throw new CommandException(MESSAGE_FAILURE); + } catch (CommandException ce) { + throw new CommandException(MESSAGE_AUTHENTICATION_FAILURE); + } + + return new CommandResult(String.format(MESSAGE_SUCCESS, targetIndex.getOneBased())); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ReopenIssueCommand // instanceof handles nulls + && this.targetIndex.equals(((ReopenIssueCommand) other).targetIndex)); // state check + } +} + diff --git a/src/main/java/seedu/progresschecker/logic/commands/ResetTaskCommand.java b/src/main/java/seedu/progresschecker/logic/commands/ResetTaskCommand.java new file mode 100644 index 000000000000..d18e19b02a03 --- /dev/null +++ b/src/main/java/seedu/progresschecker/logic/commands/ResetTaskCommand.java @@ -0,0 +1,88 @@ +package seedu.progresschecker.logic.commands; + +import static java.util.Objects.requireNonNull; + +import static seedu.progresschecker.logic.commands.AddDefaultTasksCommand.DEFAULT_LIST_ID; +import static seedu.progresschecker.model.task.TaskUtil.undoTask; + +import javafx.util.Pair; +import seedu.progresschecker.logic.LogicManager; +import seedu.progresschecker.logic.commands.exceptions.CommandException; + +//@@author EdwardKSG +/** + * Sets a task with given index as incompleted. + */ +public class ResetTaskCommand extends Command { + + public static final String COMMAND_WORD = "reset"; + public static final String COMMAND_ALIAS = "rt"; // short for "reset task" + public static final String DATA_FOLDER = "data/"; + public static final String TASK_PAGE = "tasklist.html"; + public static final String FILE_FAILURE = "Something is wrong with the file system."; + public static final String COMMAND_FORMAT = COMMAND_WORD + "INDEX"; + public static final String MESSAGE_INDEX_CONSTRAINTS = "The index should be an index in the task list displayed" + + "to you. It must be an integer that does not exceed the number of tasks in the list."; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Mark task with the given index in the list as incompleted.\n" + + "Parameters: INDEX (an index in the task list)\n " + + "Example: " + COMMAND_WORD + 1; + + public static final String MESSAGE_SUCCESS = "Reset task: %1$s"; + public static final String MESSAGE_NO_ACTION = "This task is not completed yet: %1$s"; + public static final String RESET_FAILURE = "Error. Failed to mark it as incompleted. Index: %1$s"; + public static final String UNKNOWN_ERROR = "Unknow error in the system occurred"; + + public static final int ERROR = -1; + public static final int NO_ACTION = 0; + public static final int SUCCESS = 1; + + private int index; + + /** + * Reset the task with index {@code int} + */ + public ResetTaskCommand(int index) { + requireNonNull(index); + this.index = index; + } + + @Override + public CommandResult execute() throws CommandException { + try { + Pair<Integer, String> result = undoTask(index, DEFAULT_LIST_ID); + + if (result.getKey() == ERROR) { + return new CommandResult(String.format(result.getValue())); + } + + String titleWithCode = result.getValue(); // full file name + String[] parts = titleWithCode.split("&#"); // String array, each element is text between dots + + String title = parts[0]; + + if (result.getKey() == NO_ACTION) { + return new CommandResult(String.format(MESSAGE_NO_ACTION, index + ". " + title)); + } else if (result.getKey() == SUCCESS) { + ViewTaskListCommand view = LogicManager.getCurrentViewTask(); + view.updateView(); + return new CommandResult(String.format(MESSAGE_SUCCESS, index + ". " + title)); + } else { + // the command parser could never pass any value other than the above 4, thus we say "unknown error". + throw new CommandException(UNKNOWN_ERROR); + } + } catch (CommandException ce) { + throw ce; + } catch (Exception e) { + throw new CommandException(RESET_FAILURE + index); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ResetTaskCommand // instanceof handles nulls + && index == (((ResetTaskCommand) other).index)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/SelectCommand.java b/src/main/java/seedu/progresschecker/logic/commands/SelectCommand.java similarity index 66% rename from src/main/java/seedu/address/logic/commands/SelectCommand.java rename to src/main/java/seedu/progresschecker/logic/commands/SelectCommand.java index 9e3840a9dde6..d0a0555494e6 100644 --- a/src/main/java/seedu/address/logic/commands/SelectCommand.java +++ b/src/main/java/seedu/progresschecker/logic/commands/SelectCommand.java @@ -1,20 +1,23 @@ -package seedu.address.logic.commands; +package seedu.progresschecker.logic.commands; import java.util.List; -import seedu.address.commons.core.EventsCenter; -import seedu.address.commons.core.Messages; -import seedu.address.commons.core.index.Index; -import seedu.address.commons.events.ui.JumpToListRequestEvent; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.person.Person; +import seedu.progresschecker.commons.core.EventsCenter; +import seedu.progresschecker.commons.core.Messages; +import seedu.progresschecker.commons.core.index.Index; +import seedu.progresschecker.commons.events.ui.JumpToListRequestEvent; +import seedu.progresschecker.commons.events.ui.TabLoadChangedEvent; +import seedu.progresschecker.logic.commands.exceptions.CommandException; +import seedu.progresschecker.model.person.Person; /** - * Selects a person identified using it's last displayed index from the address book. + * Selects a person identified using it's last displayed index from the ProgressChecker. */ public class SelectCommand extends Command { public static final String COMMAND_WORD = "select"; + public static final String COMMAND_ALIAS = "s"; + public static final String COMMAND_FORMAT = COMMAND_WORD + " INDEX"; public static final String MESSAGE_USAGE = COMMAND_WORD + ": Selects the person identified by the index number used in the last person listing.\n" @@ -22,6 +25,7 @@ public class SelectCommand extends Command { + "Example: " + COMMAND_WORD + " 1"; public static final String MESSAGE_SELECT_PERSON_SUCCESS = "Selected Person: %1$s"; + public static final String tabType = "profile"; private final Index targetIndex; @@ -39,6 +43,7 @@ public CommandResult execute() throws CommandException { } EventsCenter.getInstance().post(new JumpToListRequestEvent(targetIndex)); + EventsCenter.getInstance().post(new TabLoadChangedEvent(tabType)); return new CommandResult(String.format(MESSAGE_SELECT_PERSON_SUCCESS, targetIndex.getOneBased())); } diff --git a/src/main/java/seedu/progresschecker/logic/commands/SortCommand.java b/src/main/java/seedu/progresschecker/logic/commands/SortCommand.java new file mode 100644 index 000000000000..20632b803652 --- /dev/null +++ b/src/main/java/seedu/progresschecker/logic/commands/SortCommand.java @@ -0,0 +1,22 @@ +package seedu.progresschecker.logic.commands; + +import static java.util.Objects.requireNonNull; + +//@@author: Livian1107 +/** + * Sorts all persons in the ProgressChecker in alphabetical order. + */ +public class SortCommand extends UndoableCommand { + public static final String COMMAND_WORD = "sort"; + public static final String COMMAND_ALIAS = "so"; + + public static final String MESSAGE_SUCCESS = "Sorted all persons in alphabetical order"; + + @Override + protected CommandResult executeUndoableCommand() { + requireNonNull(model); + model.sort(); + return new CommandResult(MESSAGE_SUCCESS); + } + +} diff --git a/src/main/java/seedu/progresschecker/logic/commands/TaskCommandUtil.java b/src/main/java/seedu/progresschecker/logic/commands/TaskCommandUtil.java new file mode 100644 index 000000000000..935809b6f6b4 --- /dev/null +++ b/src/main/java/seedu/progresschecker/logic/commands/TaskCommandUtil.java @@ -0,0 +1,299 @@ +package seedu.progresschecker.logic.commands; + +import static seedu.progresschecker.logic.commands.AddDefaultTasksCommand.DEFAULT_LIST_TITLE; +import static seedu.progresschecker.model.task.TaskUtil.NOTE_TOKEN; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.List; +import java.util.logging.Logger; + +import com.google.api.services.tasks.model.Task; + +import seedu.progresschecker.commons.core.LogsCenter; +import seedu.progresschecker.commons.util.FileUtil; +import seedu.progresschecker.logic.commands.exceptions.CommandException; +import seedu.progresschecker.ui.CommandBox; + +//@@author EdwardKSG +/** + * Some util methods to be used by task-relevant commands. + */ +public class TaskCommandUtil { + + public static final String COMMAND_WORD = "viewtask"; + public static final String COMMAND_ALIAS = "vt"; + public static final String FILE_FAILURE = "Something is wrong with the file system."; + public static final String BAR_FAILURE = "Fail to get the progress bar."; + public static final String COMMAND_FORMAT = COMMAND_WORD; + public static final int COMPULSORY = -13; // parser returns -13 for compulsory tasks + public static final int SUBMISSION = -20; // parser returns -10 for tasks need submission + public static final String COMPULSORY_STR = " [Compulsory]"; + public static final String SUBMISSION_STR = " [Submission]"; + public static final String TITLE_COLOR_DAY = "grey"; + public static final String BACKGROUND_COLOR_DAY = "white"; + public static final String TITLE_COLOR_NIGHT = "white"; + public static final String BACKGROUND_COLOR_NIGHT = "#202226"; + + private final Logger logger = LogsCenter.getLogger(CommandBox.class); + + /** + * Writes the loaded task list to an html file. Loads the tasks. + * + * @param list task list serialized in a java List. + * @param indexList stores the corresponding index in the full list (the current showing list is a filtered + * result. + * @param file File object of the html file. + * @param targetWeek indicates the filter argument received for viewing task list + * @return progressInt the percentage of task completed (without the "%" sign) + */ + int writeToHtml(List<Task> list, List<Integer> indexList, File file, int targetWeek) throws CommandException { + String backgroundColor = BACKGROUND_COLOR_DAY; + String titleColor = TITLE_COLOR_DAY; + double countCompleted = 0; + double countIncomp = 0; + + int size = list.size(); + + try { + FileUtil.createIfMissing(file); + + FileWriter fw1 = new FileWriter(file, false); + BufferedWriter bw1 = new BufferedWriter(fw1); + PrintWriter out1 = new PrintWriter(bw1); + + out1.print(""); + + FileWriter fw = new FileWriter(file, true); + BufferedWriter bw = new BufferedWriter(fw); + PrintWriter out = new PrintWriter(bw); + + out.print("<!DOCTYPE html>\n" + + "<html lang=\"en\">\n" + + "<head>\n" + + " <title>Task List\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "\n" + + "" + + "
"); + if (targetWeek > 0) { + out.print("

" + + DEFAULT_LIST_TITLE + " Week: " + targetWeek + "

\n
\n"); + } else if (targetWeek == COMPULSORY) { + out.print("

" + + DEFAULT_LIST_TITLE + COMPULSORY_STR + "

\n
\n"); + } else if (targetWeek == SUBMISSION) { + out.print("

" + + DEFAULT_LIST_TITLE + SUBMISSION_STR + "

\n
\n"); + } else { + out.print("

" + + DEFAULT_LIST_TITLE + "

\n
\n"); + } + + for (int i = 0; i < size; i++) { + Task task = list.get(i); + int index = indexList.get(i); + + String status = task.getStatus(); + String notesWithUrl = task.getNotes(); + String[] parts = notesWithUrl.split(NOTE_TOKEN); + + String notes = parts[0]; + String url = parts[1]; + + if (status.length() >= 11) { + out.print("
\n" + + "
" + + index + ". " + task.getTitle() + "
\n" + + "
\n" + + "
⚠  " + + task.getDue().toString().substring(0, 10) + "
\n" + + "
⚑" + + "  Please work on it :) ☐
\n"); + countIncomp++; + } else { + out.print("
\n" + + "
" + + index + ". " + task.getTitle() + "
\n" + + "
\n" + + "
⚠  " + + task.getDue().toString().substring(0, 10) + "
\n" + + "
⚑" + + "  Completed! ☑
\n"); + countCompleted++; + } + + out.print("
✎  " + + notes + "
\n" + + "

" + + url + + "

\n" + + "
\n" + + "
\n"); + } + + out.print("
\n" + + "
\n" + + "\n"); + + double percent = countCompleted / (countCompleted + countIncomp); + int progressInt = (int) (percent * 100); + String progressDevision = (int) countCompleted + "/" + (int) (countCompleted + countIncomp); + + out.print("\n"); + + out.print("

" + "You have completed " + progressDevision + + " !" + "

"); + + out.print("\n" + + ""); + + out1.close(); + out.close(); + + return progressInt; + + } catch (IOException e) { + logger.info(FILE_FAILURE); + throw new CommandException(FILE_FAILURE); + + } + } + + /** + * Writes the progress bar to an html file. + * + * @param percentage the percentage of completed tasks. + * @param file File object of the html file. + * @param targetWeek indicates the filter argument received for viewing task list + */ + void writeToHtmlBar(int percentage, File file, int targetWeek) throws CommandException { + String backgroundColor = BACKGROUND_COLOR_DAY; + String titleColor = TITLE_COLOR_DAY; + + try { + FileUtil.createIfMissing(file); + + FileWriter fw1 = new FileWriter(file, false); + BufferedWriter bw1 = new BufferedWriter(fw1); + PrintWriter out1 = new PrintWriter(bw1); + + out1.print(""); + + FileWriter fw = new FileWriter(file, true); + BufferedWriter bw = new BufferedWriter(fw); + PrintWriter out = new PrintWriter(bw); + + out.print("\n" + + "\n" + + "\n" + + " progresschecker\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "\n" + + "\n" + + "
\n" + + "

Your Progress"); + + if (targetWeek > 0) { + out.print("(Week" + targetWeek + ")"); + } else if (targetWeek == COMPULSORY) { + out.print("([Compulsory])"); + } else if (targetWeek == SUBMISSION) { + out.print("([Submission])"); + } + + out.print(": " + + percentage + "%

\n" + + "
\n" + + "
\n" + + "
\n" + + " " + percentage + "% (on the way)\n" + + "
\n" + + "
\n" + + "
\n" + + "\n" + + "\n"); + + out1.close(); + out.close(); + + } catch (IOException e) { + logger.info(FILE_FAILURE); + throw new CommandException(BAR_FAILURE); + + } + } + + /** + * Writes an html file to involve both the progress bar and task list (the file is for backup, + * preview and debugging purposes. + * + * @param file File object of the html file. + */ + void writeToHtmlChecker(File file) throws CommandException { + try { + FileUtil.createIfMissing(file); + + FileWriter fw1 = new FileWriter(file, false); + BufferedWriter bw1 = new BufferedWriter(fw1); + PrintWriter out1 = new PrintWriter(bw1); + + out1.print(""); + + FileWriter fw = new FileWriter(file, true); + BufferedWriter bw = new BufferedWriter(fw); + PrintWriter out = new PrintWriter(bw); + + out.print("\n" + + "\n" + + "\n" + + " \n" + + " \n" + + "\n" + + "\n"); + + out1.close(); + out.close(); + + } catch (IOException e) { + logger.info(FILE_FAILURE); + throw new CommandException(FILE_FAILURE); + + } + } + + /** + * Reads the content of a text file to a String. + * + * @param path file path + * @param encoding the encoding standard, such as StandardCharsets.UTF_8. + */ + static String readFile(String path, Charset encoding) throws IOException { + byte[] encoded = Files.readAllBytes(Paths.get(path)); + return new String(encoded, encoding); + } +} diff --git a/src/main/java/seedu/progresschecker/logic/commands/ThemeCommand.java b/src/main/java/seedu/progresschecker/logic/commands/ThemeCommand.java new file mode 100644 index 000000000000..55b92bc977c2 --- /dev/null +++ b/src/main/java/seedu/progresschecker/logic/commands/ThemeCommand.java @@ -0,0 +1,42 @@ +package seedu.progresschecker.logic.commands; + +import seedu.progresschecker.commons.core.EventsCenter; +import seedu.progresschecker.commons.events.ui.ChangeThemeEvent; + +//@@author: Livian1107 +/** + * Changes the thmem of ProgressChecker. + */ +public class ThemeCommand extends UndoableCommand { + public static final String COMMAND_WORD = "theme"; + public static final String COMMAND_ALIAS = "t"; + + public static final String COMMAND_FORMAT = COMMAND_WORD + " " + + " THEME"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Change theme of ProgressChecker.\n" + + "Parameters: " + "Theme(either 'day' or 'night')\n" + + "Example: " + COMMAND_WORD + "day"; + + public static final String MESSAGE_SUCCESS = "Change to theme %1$s"; + + public final String theme; + + public ThemeCommand(String theme) { + this.theme = theme; + } + + @Override + protected CommandResult executeUndoableCommand() { + EventsCenter.getInstance().post(new ChangeThemeEvent(theme)); + return new CommandResult(String.format(MESSAGE_SUCCESS, theme)); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ThemeCommand // instanceof handles nulls + && this.theme.equals(((ThemeCommand) other).theme)); // state check + } +} diff --git a/src/main/java/seedu/address/logic/commands/UndoCommand.java b/src/main/java/seedu/progresschecker/logic/commands/UndoCommand.java similarity index 67% rename from src/main/java/seedu/address/logic/commands/UndoCommand.java rename to src/main/java/seedu/progresschecker/logic/commands/UndoCommand.java index 1f3dcea8bbaa..11286d04c261 100644 --- a/src/main/java/seedu/address/logic/commands/UndoCommand.java +++ b/src/main/java/seedu/progresschecker/logic/commands/UndoCommand.java @@ -1,11 +1,11 @@ -package seedu.address.logic.commands; +package seedu.progresschecker.logic.commands; -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.progresschecker.commons.util.CollectionUtil.requireAllNonNull; -import seedu.address.logic.CommandHistory; -import seedu.address.logic.UndoRedoStack; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.Model; +import seedu.progresschecker.logic.CommandHistory; +import seedu.progresschecker.logic.UndoRedoStack; +import seedu.progresschecker.logic.commands.exceptions.CommandException; +import seedu.progresschecker.model.Model; /** * Undo the previous {@code UndoableCommand}. @@ -13,6 +13,7 @@ public class UndoCommand extends Command { public static final String COMMAND_WORD = "undo"; + public static final String COMMAND_ALIAS = "u"; public static final String MESSAGE_SUCCESS = "Undo success!"; public static final String MESSAGE_FAILURE = "No more commands to undo!"; diff --git a/src/main/java/seedu/address/logic/commands/UndoableCommand.java b/src/main/java/seedu/progresschecker/logic/commands/UndoableCommand.java similarity index 62% rename from src/main/java/seedu/address/logic/commands/UndoableCommand.java rename to src/main/java/seedu/progresschecker/logic/commands/UndoableCommand.java index c107ffcd9cb3..4d5d6ec47763 100644 --- a/src/main/java/seedu/address/logic/commands/UndoableCommand.java +++ b/src/main/java/seedu/progresschecker/logic/commands/UndoableCommand.java @@ -1,27 +1,27 @@ -package seedu.address.logic.commands; +package seedu.progresschecker.logic.commands; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; -import static seedu.address.model.Model.PREDICATE_SHOW_ALL_PERSONS; +import static seedu.progresschecker.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.progresschecker.model.Model.PREDICATE_SHOW_ALL_PERSONS; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.model.AddressBook; -import seedu.address.model.ReadOnlyAddressBook; +import seedu.progresschecker.logic.commands.exceptions.CommandException; +import seedu.progresschecker.model.ProgressChecker; +import seedu.progresschecker.model.ReadOnlyProgressChecker; /** * Represents a command which can be undone and redone. */ public abstract class UndoableCommand extends Command { - private ReadOnlyAddressBook previousAddressBook; + private ReadOnlyProgressChecker previousProgressChecker; protected abstract CommandResult executeUndoableCommand() throws CommandException; /** - * Stores the current state of {@code model#addressBook}. + * Stores the current state of {@code model#progressChecker}. */ - private void saveAddressBookSnapshot() { + private void saveProgressCheckerSnapshot() { requireNonNull(model); - this.previousAddressBook = new AddressBook(model.getAddressBook()); + this.previousProgressChecker = new ProgressChecker(model.getProgressChecker()); } /** @@ -31,13 +31,13 @@ private void saveAddressBookSnapshot() { protected void preprocessUndoableCommand() throws CommandException {} /** - * Reverts the AddressBook to the state before this command + * Reverts the ProgressChecker to the state before this command * was executed and updates the filtered person list to * show all persons. */ protected final void undo() { - requireAllNonNull(model, previousAddressBook); - model.resetData(previousAddressBook); + requireAllNonNull(model, previousProgressChecker); + model.resetData(previousProgressChecker); model.updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); } @@ -58,7 +58,7 @@ protected final void redo() { @Override public final CommandResult execute() throws CommandException { - saveAddressBookSnapshot(); + saveProgressCheckerSnapshot(); preprocessUndoableCommand(); return executeUndoableCommand(); } diff --git a/src/main/java/seedu/progresschecker/logic/commands/UploadCommand.java b/src/main/java/seedu/progresschecker/logic/commands/UploadCommand.java new file mode 100644 index 000000000000..61353f01c698 --- /dev/null +++ b/src/main/java/seedu/progresschecker/logic/commands/UploadCommand.java @@ -0,0 +1,161 @@ +package seedu.progresschecker.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.progresschecker.commons.util.FileUtil.REGEX_VALID_IMAGE; +import static seedu.progresschecker.commons.util.FileUtil.copyFile; +import static seedu.progresschecker.commons.util.FileUtil.createMissing; +import static seedu.progresschecker.commons.util.FileUtil.getFileExtension; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Date; +import java.util.List; + +import seedu.progresschecker.commons.core.Messages; +import seedu.progresschecker.commons.core.index.Index; +import seedu.progresschecker.commons.exceptions.IllegalValueException; +import seedu.progresschecker.logic.commands.exceptions.CommandException; +import seedu.progresschecker.model.person.Person; +import seedu.progresschecker.model.person.exceptions.DuplicatePersonException; +import seedu.progresschecker.model.person.exceptions.PersonNotFoundException; +import seedu.progresschecker.model.photo.PhotoPath; +import seedu.progresschecker.model.photo.exceptions.DuplicatePhotoException; + +//@@author Livian1107 +/** + * Uploads a photo to the profile. + */ +public class UploadCommand extends UndoableCommand { + + public static final String COMMAND_WORD = "upload"; + public static final String COMMAND_ALIAS = "up"; + public static final String COMMAND_FORMAT = COMMAND_WORD + " " + "INDEX " + + "[PATH]"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Uploads a photo to the specified profile.\n" + + "The valid photo extensions are 'jpg', 'jpeg' or 'png'.\n" + + "Parameter: INDEX(must be a positive integer) PATH...\n" + + "Example: " + COMMAND_WORD + " 1 C:\\Users\\User\\Desktop\\photo.png\n"; + + public static final String MESSAGE_SUCCESS = "New photo uploaded!"; + public static final String MESSAGE_COPY_FAIL = "Cannot copy file!"; + public static final String MESSAGE_IMAGE_NOT_FOUND = "The image cannot be found!"; + public static final String MESSAGE_IMAGE_DUPLICATE = "Upload the same image!"; + public static final String MESSAGE_LOCAL_PATH_CONSTRAINTS = + "The photo path should be a valid path on your PC. " + + "It should start with the name of your PC user name, " + + "followed by several folders, e.g.\"C:\\Usres\\User\\Desktop\\photo.png\". \n" + + "The file should exist. \n" + + "The path of file cannot contain any whitespaces inside. \n" + + "The valid extensions of the file should be 'jpg', 'jpeg' or 'png'. \n"; + + public static final String REGEX_VALID_LOCAL_PATH = + "([a-zA-Z]:)?(\\\\\\w+)+\\\\" + REGEX_VALID_IMAGE; + public static final String PATH_SAVED_FILE = "src/main/resources/images/contact/"; + + private final Index targetIndex; + + private Person personToUpdate; + private PhotoPath photoPath; + private String localPath; + private String savePath; + + /** + * Creates an UploadCommand to upload the profile photo with specified {@code Path} + */ + public UploadCommand(Index index, String path) throws IllegalValueException, IOException { + requireNonNull(path); + requireNonNull(index); + if (isValidLocalPath(path)) { + this.localPath = path; + this.targetIndex = index; + this.savePath = copyLocalPhoto(localPath); + this.photoPath = new PhotoPath(savePath); + } else { + throw new IllegalValueException(MESSAGE_LOCAL_PATH_CONSTRAINTS); + } + } + + @Override + public CommandResult executeUndoableCommand() throws CommandException { + requireNonNull(personToUpdate); + try { + model.addPhoto(photoPath); + model.uploadPhoto(personToUpdate, savePath); + return new CommandResult(MESSAGE_SUCCESS); + } catch (PersonNotFoundException pnfe) { + throw new AssertionError("The target person cannot be missing"); + } catch (DuplicatePhotoException e) { + throw new CommandException(MESSAGE_IMAGE_DUPLICATE); + } catch (DuplicatePersonException e) { + throw new CommandException(MESSAGE_IMAGE_DUPLICATE); + } + } + + @Override + protected void preprocessUndoableCommand() throws CommandException { + List lastShownList = model.getFilteredPersonList(); + + if (targetIndex.getZeroBased() >= lastShownList.size()) { + throw new CommandException(Messages.MESSAGE_INVALID_PERSON_DISPLAYED_INDEX); + } + + personToUpdate = lastShownList.get(targetIndex.getZeroBased()); + } + + /** + * Returns true when the String path provided is a valid local path + */ + public static boolean isValidLocalPath(String path) { + return path.matches(REGEX_VALID_LOCAL_PATH); + } + + /** + * Copies uploaded photo file to specified saved path + * @param localPath is the path of uploaded photo + * @return String of saved path of uploaded photo + */ + public String copyLocalPhoto(String localPath) throws IOException { + File localFile = new File(localPath); + String newPath = createSavePath(localPath); + + if (!localFile.exists()) { + throw new FileNotFoundException(MESSAGE_LOCAL_PATH_CONSTRAINTS); + } + + createSavedPhoto(newPath); + + try { + copyFile(localPath, newPath); + } catch (IOException e) { + throw new IOException(MESSAGE_COPY_FAIL); + } + return newPath; + } + + /** + * Create a new path for uploaded photo to save + * @param localPath is the String of uploaded photo on user PC + * @return savePath String of uploaded photo + */ + public static String createSavePath(String localPath) { + Date date = new Date(); + Long num = date.getTime(); + String createPath = PATH_SAVED_FILE + num.toString() + getFileExtension(localPath); + return createPath; + } + + /** + * Creates a new file to save profile photo + * @param path to save photo + */ + public void createSavedPhoto(String path) { + File savedPhoto = new File(path); + try { + createMissing(savedPhoto); + } catch (IOException e) { + assert false : "Fail to create the file!"; + } + } +} diff --git a/src/main/java/seedu/progresschecker/logic/commands/ViewCommand.java b/src/main/java/seedu/progresschecker/logic/commands/ViewCommand.java new file mode 100644 index 000000000000..6ca90ce5a92e --- /dev/null +++ b/src/main/java/seedu/progresschecker/logic/commands/ViewCommand.java @@ -0,0 +1,60 @@ +package seedu.progresschecker.logic.commands; + +import seedu.progresschecker.commons.core.EventsCenter; +import seedu.progresschecker.commons.events.ui.TabLoadChangedEvent; + +//@@author iNekox3 +/** + * Change view of the tab pane in main window based on categories. + */ +public class ViewCommand extends Command { + + public static final int MIN_WEEK_NUMBER = 2; + public static final int MAX_WEEK_NUMBER = 11; + + public static final String COMMAND_WORD = "view"; + public static final String COMMAND_ALIAS = "v"; + public static final String COMMAND_FORMAT = COMMAND_WORD + " TYPE"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + + ": Change the tab view to profiles, tasks, exercises, or issues.\n" + + "Parameters: TYPE (must be either 'profile', 'task', 'exercise', or 'issues')\n" + + "INDEX (if TYPE is exercise and must be within " + MIN_WEEK_NUMBER + + " and " + MAX_WEEK_NUMBER + " (boundary numbers inclusive)\n" + + "Example: " + COMMAND_WORD + " exercise\n" + + COMMAND_WORD + "exercise 5"; + + public static final String MESSAGE_SUCCESS_TAB = "Viewing tab %1$s"; + public static final String MESSAGE_SUCCESS_WEEK = "Viewing week %1$s's exercises"; + + private final String type; + private final int weekNumber; + private final boolean isToggleExerciseByWeek; + + public ViewCommand(String type, int weekNumber, boolean isToggleExerciseByWeek) { + this.type = type; + this.weekNumber = weekNumber; + this.isToggleExerciseByWeek = isToggleExerciseByWeek; + } + + @Override + public CommandResult execute() { + if (!isToggleExerciseByWeek) { + EventsCenter.getInstance().post(new TabLoadChangedEvent(type)); + return new CommandResult(String.format(MESSAGE_SUCCESS_TAB, type)); + } else { + model.updateFilteredExerciseList(exercise -> exercise.getQuestionIndex().getWeekNumber() == weekNumber); + EventsCenter.getInstance().post(new TabLoadChangedEvent(type)); + return new CommandResult(String.format(MESSAGE_SUCCESS_WEEK, weekNumber)); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ViewCommand // instanceof handles nulls + && this.type.equals(((ViewCommand) other).type)) // state check + && this.weekNumber == ((ViewCommand) other).weekNumber + && this.isToggleExerciseByWeek == ((ViewCommand) other).isToggleExerciseByWeek; + } +} diff --git a/src/main/java/seedu/progresschecker/logic/commands/ViewTaskListCommand.java b/src/main/java/seedu/progresschecker/logic/commands/ViewTaskListCommand.java new file mode 100644 index 000000000000..97baa8f84cb0 --- /dev/null +++ b/src/main/java/seedu/progresschecker/logic/commands/ViewTaskListCommand.java @@ -0,0 +1,199 @@ +package seedu.progresschecker.logic.commands; + +import static seedu.progresschecker.logic.commands.AddDefaultTasksCommand.DEFAULT_LIST_ID; +import static seedu.progresschecker.logic.commands.AddDefaultTasksCommand.DEFAULT_LIST_TITLE; +import static seedu.progresschecker.logic.commands.TaskCommandUtil.readFile; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.LinkedList; +import java.util.List; +import java.util.logging.Logger; + +import com.google.api.services.tasks.model.Task; + +import seedu.progresschecker.commons.core.EventsCenter; +import seedu.progresschecker.commons.core.LogsCenter; +import seedu.progresschecker.commons.events.ui.LoadBarEvent; +import seedu.progresschecker.commons.events.ui.LoadTaskEvent; +import seedu.progresschecker.commons.events.ui.TabLoadChangedEvent; +import seedu.progresschecker.logic.commands.exceptions.CommandException; +import seedu.progresschecker.model.task.TaskListUtil; +import seedu.progresschecker.ui.CommandBox; + +//@@author EdwardKSG +/** + * View the web view of a particular TaskList (with the name provided). + */ +public class ViewTaskListCommand extends Command { + + public static final String COMMAND_WORD = "viewtask"; + public static final String COMMAND_ALIAS = "vt"; + public static final String DATA_FOLDER = "data/"; + public static final String TASK_PAGE = "tasklist.html"; + public static final String BAR_PAGE = "bar.html"; + public static final String CHECKER_PAGE = "progresschecker.html"; + public static final String PROGRESS_CHECKER_PAGE = "/view/progresschecker.html"; + public static final String FILE_FAILURE = "Something is wrong with the file system."; + public static final String BAR_FAILURE = "Fail to get the progress bar."; + public static final String UNKNOWN_ERROR = "Unknow error in the system occurred"; + public static final String COMMAND_FORMAT = COMMAND_WORD; + public static final String MESSAGE_TITLE_CONSTRAINTS = "The title of a task list should not exceed " + + "49 characters (as specified by Google Task."; + public static final String TASK_TAB = "task"; + public static final int MAX_TITLE_LENGTH = 49; + public static final int MAX_WEEK = 13; + public static final int ALL_WEEK = 0; + public static final int COMPULSORY = -13; // parser returns -13 for compulsory tasks + public static final int SUBMISSION = -20; // parser returns -10 for tasks need submission + public static final String COMPULSORY_STR = " [Compulsory]"; + public static final String SUBMISSION_STR = " [Submission]"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + // TODO: change description and parameter range when appropriate + + ": View tasks in the default task list, filtered to show only tasks at the input week or the" + + " input category. Only ONE filter keyword is allowed.\n" + + "Parameters: FILTER_KEYWORD (filter by week: must be an integer ranging from 1 to 13, or an asterisk (*)" + + " which means all weeks\n" + + " filter by category: \"compulsory\" or \"com\" means compulsory. " + + "\"submission\" or \"sub\" means to get the task that needs submission." + + "Example: " + COMMAND_WORD + "3\n" + + "Example: " + COMMAND_WORD + "sub"; + + public static final String MESSAGE_SUCCESS = "Viewing task list: %1$s"; + + // when the value of it is -13 or -20, it means the command is filtering compulsory or needsSubmission tasks + private final int targetWeek; + + private final Logger logger = LogsCenter.getLogger(CommandBox.class); + + public ViewTaskListCommand(int targetWeek) { + this.targetWeek = targetWeek; + } + + @Override + public CommandResult execute() throws CommandException { + updateView(); + + String result = chooseResult(); + return new CommandResult(result); + } + + /** + * Updates the HTML file and refresh the browser panel + * + * @throws CommandException + */ + public void updateView() throws CommandException { + List list = TaskListUtil.searchTaskListById(DEFAULT_LIST_ID); + List filteredList = new LinkedList(); + List indexList = new LinkedList(); + + try { + applyFilter (filteredList, indexList, list); + } catch (CommandException ce) { + throw ce; + } + + TaskCommandUtil util = new TaskCommandUtil(); + File htmlFile = new File(DATA_FOLDER + TASK_PAGE); + int progressInt = util.writeToHtml(filteredList, indexList, htmlFile, targetWeek); + File htmlBarFile = new File(DATA_FOLDER + BAR_PAGE); + util.writeToHtmlBar(progressInt, htmlBarFile, targetWeek); + File htmlCheckerFile = new File(DATA_FOLDER + CHECKER_PAGE); + util.writeToHtmlChecker(htmlCheckerFile); + try { + EventsCenter.getInstance().post(new LoadBarEvent(readFile(htmlBarFile.getAbsolutePath(), + StandardCharsets.UTF_8))); + EventsCenter.getInstance().post(new LoadTaskEvent(readFile(htmlFile.getAbsolutePath(), + StandardCharsets.UTF_8))); + EventsCenter.getInstance().post(new TabLoadChangedEvent(TASK_TAB)); + } catch (IOException ioe) { + logger.info(FILE_FAILURE); + throw new CommandException(FILE_FAILURE); + } + } + + /** + * Applies the filter argument to get the filtered list. + * + * @param filteredList the resulting list with filters applied. + * @param indexList the corresponding indices for {@code List filteredList}. + * @param list the raw list before processing. + * @throws CommandException + */ + private void applyFilter (List filteredList, List indexList, List list) + throws CommandException { + if (targetWeek > 0) { + int count = 1; + for (Task task : list) { + if (task.getTitle().contains("LO[W" + targetWeek)) { + filteredList.add(task); + indexList.add(count); + } + count++; + } + } else if (targetWeek == ALL_WEEK) { + int count = 1; + for (Task task : list) { + if (task.getTitle().contains("LO[W")) { + filteredList.add(task); + indexList.add(count); + } + count++; + } + } else if (targetWeek == COMPULSORY) { + int count = 1; + for (Task task : list) { + if (task.getTitle().contains("[Compulsory]")) { + filteredList.add(task); + indexList.add(count); + } + count++; + } + } else if (targetWeek == SUBMISSION) { + int count = 1; + for (Task task : list) { + if (task.getTitle().contains("[Submission]")) { + filteredList.add(task); + indexList.add(count); + } + count++; + } + } else { + throw new CommandException(UNKNOWN_ERROR); + } + } + + /** + * Choose a proper response message according to the filter argument. + * + * @return String the response message. + * @throws CommandException + */ + private String chooseResult () throws CommandException { + if (targetWeek > 0) { + return String.format(MESSAGE_SUCCESS, + DEFAULT_LIST_TITLE + " Week: " + targetWeek); + } else if (targetWeek == ALL_WEEK) { + return String.format(MESSAGE_SUCCESS, DEFAULT_LIST_TITLE); + } else if (targetWeek == COMPULSORY) { + return String.format(MESSAGE_SUCCESS, + DEFAULT_LIST_TITLE + COMPULSORY_STR); + } else if (targetWeek == SUBMISSION) { + return String.format(MESSAGE_SUCCESS, + DEFAULT_LIST_TITLE + SUBMISSION_STR); + } else { + // the command parser could never pass any value other than the above 4, thus we say "unknown error". + throw new CommandException(UNKNOWN_ERROR); + } + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ViewTaskListCommand // instanceof handles nulls + && targetWeek == (((ViewTaskListCommand) other).targetWeek)); + } +} diff --git a/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java b/src/main/java/seedu/progresschecker/logic/commands/exceptions/CommandException.java similarity index 78% rename from src/main/java/seedu/address/logic/commands/exceptions/CommandException.java rename to src/main/java/seedu/progresschecker/logic/commands/exceptions/CommandException.java index ed23ad42eb26..2c6bfdb37bc5 100644 --- a/src/main/java/seedu/address/logic/commands/exceptions/CommandException.java +++ b/src/main/java/seedu/progresschecker/logic/commands/exceptions/CommandException.java @@ -1,4 +1,4 @@ -package seedu.address.logic.commands.exceptions; +package seedu.progresschecker.logic.commands.exceptions; /** * Represents an error which occurs during execution of a {@link Command}. diff --git a/src/main/java/seedu/progresschecker/logic/parser/AddCommandParser.java b/src/main/java/seedu/progresschecker/logic/parser/AddCommandParser.java new file mode 100644 index 000000000000..23aa2acf26c9 --- /dev/null +++ b/src/main/java/seedu/progresschecker/logic/parser/AddCommandParser.java @@ -0,0 +1,73 @@ +package seedu.progresschecker.logic.parser; + +import static seedu.progresschecker.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.progresschecker.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.progresschecker.logic.parser.CliSyntax.PREFIX_MAJOR; +import static seedu.progresschecker.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.progresschecker.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.progresschecker.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.progresschecker.logic.parser.CliSyntax.PREFIX_USERNAME; +import static seedu.progresschecker.logic.parser.CliSyntax.PREFIX_YEAR; + +import java.util.Set; +import java.util.stream.Stream; + +import seedu.progresschecker.commons.exceptions.IllegalValueException; +import seedu.progresschecker.logic.commands.AddCommand; +import seedu.progresschecker.logic.parser.exceptions.ParseException; +import seedu.progresschecker.model.person.Email; +import seedu.progresschecker.model.person.GithubUsername; +import seedu.progresschecker.model.person.Major; +import seedu.progresschecker.model.person.Name; +import seedu.progresschecker.model.person.Person; +import seedu.progresschecker.model.person.Phone; +import seedu.progresschecker.model.person.Year; +import seedu.progresschecker.model.tag.Tag; + +/** + * Parses input arguments and creates a new AddCommand object + */ +public class AddCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the AddCommand + * and returns an AddCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public AddCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, + PREFIX_USERNAME, PREFIX_MAJOR, PREFIX_YEAR, PREFIX_TAG); + + if (!arePrefixesPresent(argMultimap, PREFIX_NAME, PREFIX_MAJOR, PREFIX_PHONE, PREFIX_EMAIL, + PREFIX_USERNAME, PREFIX_YEAR) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); + } + + try { + Name name = ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME)).get(); + Phone phone = ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE)).get(); + Email email = ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL)).get(); + GithubUsername username = ParserUtil.parseUsername(argMultimap.getValue(PREFIX_USERNAME)).get(); + Major major = ParserUtil.parseMajor(argMultimap.getValue(PREFIX_MAJOR)).get(); + Year year = ParserUtil.parseYear(argMultimap.getValue(PREFIX_YEAR)).get(); + Set tagList = ParserUtil.parseTags(argMultimap.getAllValues(PREFIX_TAG)); + + Person person = new Person(name, phone, email, username, major, year, tagList); + + return new AddCommand(person); + } catch (IllegalValueException ive) { + throw new ParseException(ive.getMessage(), ive); + } + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + +} diff --git a/src/main/java/seedu/progresschecker/logic/parser/AnswerCommandParser.java b/src/main/java/seedu/progresschecker/logic/parser/AnswerCommandParser.java new file mode 100644 index 000000000000..8d19238c1335 --- /dev/null +++ b/src/main/java/seedu/progresschecker/logic/parser/AnswerCommandParser.java @@ -0,0 +1,43 @@ +package seedu.progresschecker.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.progresschecker.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.progresschecker.commons.exceptions.IllegalValueException; +import seedu.progresschecker.logic.commands.AnswerCommand; +import seedu.progresschecker.logic.parser.exceptions.ParseException; +import seedu.progresschecker.model.exercise.QuestionIndex; +import seedu.progresschecker.model.exercise.StudentAnswer; + +//@@author iNekox3 +/** + * Parses input arguments and creates a new AnswerCommand object + */ +public class AnswerCommandParser implements Parser { + + private static final int QUESTION_INDEX_INDEX = 0; + private static final int ANSWER_INDEX = 1; + + /** + * Parses the given {@code String} of arguments in the context of the AnswerCommand + * and returns an AnswerCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public AnswerCommand parse(String args) throws ParseException { + requireNonNull(args); + try { + String[] content = args.trim().split(" ", 2); + + QuestionIndex questionIndex; + questionIndex = ParserUtil.parseQuestionIndex(content[QUESTION_INDEX_INDEX]); + StudentAnswer studentAnswer = ParserUtil.parseStudentAnswer(content[ANSWER_INDEX]); + + return new AnswerCommand(questionIndex, studentAnswer); + } catch (ArrayIndexOutOfBoundsException e) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AnswerCommand.MESSAGE_USAGE)); + } catch (IllegalValueException ive) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AnswerCommand.MESSAGE_USAGE)); + } + } + +} diff --git a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java b/src/main/java/seedu/progresschecker/logic/parser/ArgumentMultimap.java similarity index 98% rename from src/main/java/seedu/address/logic/parser/ArgumentMultimap.java rename to src/main/java/seedu/progresschecker/logic/parser/ArgumentMultimap.java index 954c8e18f8ea..978a874cc80c 100644 --- a/src/main/java/seedu/address/logic/parser/ArgumentMultimap.java +++ b/src/main/java/seedu/progresschecker/logic/parser/ArgumentMultimap.java @@ -1,4 +1,4 @@ -package seedu.address.logic.parser; +package seedu.progresschecker.logic.parser; import java.util.ArrayList; import java.util.HashMap; diff --git a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java b/src/main/java/seedu/progresschecker/logic/parser/ArgumentTokenizer.java similarity index 99% rename from src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java rename to src/main/java/seedu/progresschecker/logic/parser/ArgumentTokenizer.java index a1bddbb6b979..35a96e6a9d8e 100644 --- a/src/main/java/seedu/address/logic/parser/ArgumentTokenizer.java +++ b/src/main/java/seedu/progresschecker/logic/parser/ArgumentTokenizer.java @@ -1,4 +1,4 @@ -package seedu.address.logic.parser; +package seedu.progresschecker.logic.parser; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/seedu/progresschecker/logic/parser/CliSyntax.java b/src/main/java/seedu/progresschecker/logic/parser/CliSyntax.java new file mode 100644 index 000000000000..b1140756366a --- /dev/null +++ b/src/main/java/seedu/progresschecker/logic/parser/CliSyntax.java @@ -0,0 +1,28 @@ +package seedu.progresschecker.logic.parser; + +/** + * Contains Command Line Interface (CLI) syntax definitions common to multiple commands + */ +public class CliSyntax { + + /* Prefix definitions */ + public static final Prefix PREFIX_NAME = new Prefix("n/"); + public static final Prefix PREFIX_PHONE = new Prefix("p/"); + public static final Prefix PREFIX_EMAIL = new Prefix("e/"); + public static final Prefix PREFIX_USERNAME = new Prefix("g/"); + public static final Prefix PREFIX_MAJOR = new Prefix("m/"); + public static final Prefix PREFIX_YEAR = new Prefix("y/"); + public static final Prefix PREFIX_TAG = new Prefix("t/"); + public static final Prefix PREFIX_TITLE = new Prefix("ti/"); + public static final Prefix PREFIX_ASSIGNEES = new Prefix("a/"); + public static final Prefix PREFIX_MILESTONE = new Prefix("ms/"); + public static final Prefix PREFIX_BODY = new Prefix("b/"); + public static final Prefix PREFIX_LABEL = new Prefix("l/"); + public static final Prefix PREFIX_GIT_USERNAME = new Prefix("gu/"); + public static final Prefix PREFIX_GIT_REPO = new Prefix("r/"); + public static final Prefix PREFIX_GIT_PASSCODE = new Prefix("pc/"); + + + + +} diff --git a/src/main/java/seedu/progresschecker/logic/parser/CloseIssueCommandParser.java b/src/main/java/seedu/progresschecker/logic/parser/CloseIssueCommandParser.java new file mode 100644 index 000000000000..b47e856ed1ed --- /dev/null +++ b/src/main/java/seedu/progresschecker/logic/parser/CloseIssueCommandParser.java @@ -0,0 +1,31 @@ +package seedu.progresschecker.logic.parser; + +import static seedu.progresschecker.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.progresschecker.commons.core.index.Index; +import seedu.progresschecker.commons.exceptions.IllegalValueException; +import seedu.progresschecker.logic.commands.CloseIssueCommand; +import seedu.progresschecker.logic.parser.exceptions.ParseException; + +//@@author adityaa1998 +/** + * Parses input arguments and creates a new CloseIssueCommand object + */ +public class CloseIssueCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the CloseIssueCommand + * and returns an CloseIssueCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public CloseIssueCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new CloseIssueCommand(index); + } catch (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, CloseIssueCommand.MESSAGE_USAGE)); + } + } +} + diff --git a/src/main/java/seedu/progresschecker/logic/parser/CompleteTaskCommandParser.java b/src/main/java/seedu/progresschecker/logic/parser/CompleteTaskCommandParser.java new file mode 100644 index 000000000000..43c54cf3448b --- /dev/null +++ b/src/main/java/seedu/progresschecker/logic/parser/CompleteTaskCommandParser.java @@ -0,0 +1,30 @@ +package seedu.progresschecker.logic.parser; + +import static seedu.progresschecker.logic.parser.ParserUtil.MESSAGE_INVALID_INDEX_OR_FORMAT; + +import seedu.progresschecker.commons.exceptions.IllegalValueException; +import seedu.progresschecker.logic.commands.CompleteTaskCommand; +import seedu.progresschecker.logic.parser.exceptions.ParseException; + +//@@author EdwardKSG +/** + * Parses input arguments and creates a new CompleteTaskCommand object + */ +public class CompleteTaskCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the CompleteTaskCommand + * and returns an CompleteTaskCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public CompleteTaskCommand parse(String args) throws ParseException { + try { + int index = ParserUtil.parseTaskIndex(args); + return new CompleteTaskCommand(index); + } catch (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_INDEX_OR_FORMAT, CompleteTaskCommand.MESSAGE_USAGE)); + } + } + +} diff --git a/src/main/java/seedu/progresschecker/logic/parser/CreateIssueParser.java b/src/main/java/seedu/progresschecker/logic/parser/CreateIssueParser.java new file mode 100644 index 000000000000..eaf1610d7931 --- /dev/null +++ b/src/main/java/seedu/progresschecker/logic/parser/CreateIssueParser.java @@ -0,0 +1,72 @@ +package seedu.progresschecker.logic.parser; + +import static seedu.progresschecker.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.progresschecker.logic.parser.CliSyntax.PREFIX_ASSIGNEES; +import static seedu.progresschecker.logic.parser.CliSyntax.PREFIX_BODY; +import static seedu.progresschecker.logic.parser.CliSyntax.PREFIX_LABEL; +import static seedu.progresschecker.logic.parser.CliSyntax.PREFIX_MILESTONE; +import static seedu.progresschecker.logic.parser.CliSyntax.PREFIX_TITLE; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.stream.Stream; + +import seedu.progresschecker.commons.exceptions.IllegalValueException; +import seedu.progresschecker.logic.commands.CreateIssueCommand; +import seedu.progresschecker.logic.parser.exceptions.ParseException; +import seedu.progresschecker.model.issues.Assignees; +import seedu.progresschecker.model.issues.Body; +import seedu.progresschecker.model.issues.Issue; +import seedu.progresschecker.model.issues.Labels; +import seedu.progresschecker.model.issues.Milestone; +import seedu.progresschecker.model.issues.Title; + +//@@author adityaa1998 +/** + * Parses input arguments and creates a new CreateIssueCommand object + */ +public class CreateIssueParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the CreateIssueCommand + * and returns an createIssue object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public CreateIssueCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_TITLE, PREFIX_ASSIGNEES, + PREFIX_MILESTONE, PREFIX_BODY, PREFIX_LABEL); + + if (!arePrefixesPresent(argMultimap, PREFIX_TITLE) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, CreateIssueCommand.MESSAGE_USAGE)); + } + + try { + Title title = ParserUtil.parseTitle(argMultimap.getValue(PREFIX_TITLE)).get(); + Set assigneeSet = ParserUtil.parseAssignees(argMultimap.getAllValues(PREFIX_ASSIGNEES)); + Milestone milestone = ParserUtil.parseMilestone(argMultimap.getValue(PREFIX_MILESTONE)).orElse(null); + Body body = ParserUtil.parseBody(argMultimap.getValue(PREFIX_BODY).orElse("")); + Set labelSet = ParserUtil.parseLabels(argMultimap.getAllValues(PREFIX_LABEL)); + + List assigneesList = new ArrayList<>(assigneeSet); + List labelsList = new ArrayList<>(labelSet); + + Issue issue = new Issue(title, assigneesList, milestone, body, labelsList); + + return new CreateIssueCommand(issue); + } catch (IllegalValueException ive) { + throw new ParseException(ive.getMessage(), ive); + } + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + +} diff --git a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java b/src/main/java/seedu/progresschecker/logic/parser/DeleteCommandParser.java similarity index 58% rename from src/main/java/seedu/address/logic/parser/DeleteCommandParser.java rename to src/main/java/seedu/progresschecker/logic/parser/DeleteCommandParser.java index fe9c1653850e..c84adaf909a9 100644 --- a/src/main/java/seedu/address/logic/parser/DeleteCommandParser.java +++ b/src/main/java/seedu/progresschecker/logic/parser/DeleteCommandParser.java @@ -1,11 +1,11 @@ -package seedu.address.logic.parser; +package seedu.progresschecker.logic.parser; -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.progresschecker.logic.parser.ParserUtil.MESSAGE_INVALID_INDEX_OR_FORMAT; -import seedu.address.commons.core.index.Index; -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.logic.commands.DeleteCommand; -import seedu.address.logic.parser.exceptions.ParseException; +import seedu.progresschecker.commons.core.index.Index; +import seedu.progresschecker.commons.exceptions.IllegalValueException; +import seedu.progresschecker.logic.commands.DeleteCommand; +import seedu.progresschecker.logic.parser.exceptions.ParseException; /** * Parses input arguments and creates a new DeleteCommand object @@ -23,7 +23,7 @@ public DeleteCommand parse(String args) throws ParseException { return new DeleteCommand(index); } catch (IllegalValueException ive) { throw new ParseException( - String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE)); + String.format(MESSAGE_INVALID_INDEX_OR_FORMAT, DeleteCommand.MESSAGE_USAGE)); } } diff --git a/src/main/java/seedu/address/logic/parser/EditCommandParser.java b/src/main/java/seedu/progresschecker/logic/parser/EditCommandParser.java similarity index 62% rename from src/main/java/seedu/address/logic/parser/EditCommandParser.java rename to src/main/java/seedu/progresschecker/logic/parser/EditCommandParser.java index c9cdbed26cf1..b2d7a6945ab6 100644 --- a/src/main/java/seedu/address/logic/parser/EditCommandParser.java +++ b/src/main/java/seedu/progresschecker/logic/parser/EditCommandParser.java @@ -1,24 +1,26 @@ -package seedu.address.logic.parser; +package seedu.progresschecker.logic.parser; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import static seedu.address.logic.parser.CliSyntax.PREFIX_ADDRESS; -import static seedu.address.logic.parser.CliSyntax.PREFIX_EMAIL; -import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME; -import static seedu.address.logic.parser.CliSyntax.PREFIX_PHONE; -import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.progresschecker.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.progresschecker.logic.parser.CliSyntax.PREFIX_EMAIL; +import static seedu.progresschecker.logic.parser.CliSyntax.PREFIX_MAJOR; +import static seedu.progresschecker.logic.parser.CliSyntax.PREFIX_NAME; +import static seedu.progresschecker.logic.parser.CliSyntax.PREFIX_PHONE; +import static seedu.progresschecker.logic.parser.CliSyntax.PREFIX_TAG; +import static seedu.progresschecker.logic.parser.CliSyntax.PREFIX_USERNAME; +import static seedu.progresschecker.logic.parser.CliSyntax.PREFIX_YEAR; import java.util.Collection; import java.util.Collections; import java.util.Optional; import java.util.Set; -import seedu.address.commons.core.index.Index; -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.logic.commands.EditCommand; -import seedu.address.logic.commands.EditCommand.EditPersonDescriptor; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.tag.Tag; +import seedu.progresschecker.commons.core.index.Index; +import seedu.progresschecker.commons.exceptions.IllegalValueException; +import seedu.progresschecker.logic.commands.EditCommand; +import seedu.progresschecker.logic.commands.EditCommand.EditPersonDescriptor; +import seedu.progresschecker.logic.parser.exceptions.ParseException; +import seedu.progresschecker.model.tag.Tag; /** * Parses input arguments and creates a new EditCommand object @@ -33,7 +35,8 @@ public class EditCommandParser implements Parser { public EditCommand parse(String args) throws ParseException { requireNonNull(args); ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_ADDRESS, PREFIX_TAG); + ArgumentTokenizer.tokenize(args, PREFIX_NAME, PREFIX_PHONE, PREFIX_EMAIL, PREFIX_USERNAME, + PREFIX_MAJOR, PREFIX_YEAR, PREFIX_TAG); Index index; @@ -48,7 +51,10 @@ public EditCommand parse(String args) throws ParseException { ParserUtil.parseName(argMultimap.getValue(PREFIX_NAME)).ifPresent(editPersonDescriptor::setName); ParserUtil.parsePhone(argMultimap.getValue(PREFIX_PHONE)).ifPresent(editPersonDescriptor::setPhone); ParserUtil.parseEmail(argMultimap.getValue(PREFIX_EMAIL)).ifPresent(editPersonDescriptor::setEmail); - ParserUtil.parseAddress(argMultimap.getValue(PREFIX_ADDRESS)).ifPresent(editPersonDescriptor::setAddress); + ParserUtil.parseUsername(argMultimap.getValue(PREFIX_USERNAME)) + .ifPresent(editPersonDescriptor::setUsername); + ParserUtil.parseMajor(argMultimap.getValue(PREFIX_MAJOR)).ifPresent(editPersonDescriptor::setMajor); + ParserUtil.parseYear(argMultimap.getValue(PREFIX_YEAR)).ifPresent(editPersonDescriptor::setYear); parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setTags); } catch (IllegalValueException ive) { throw new ParseException(ive.getMessage(), ive); diff --git a/src/main/java/seedu/progresschecker/logic/parser/EditIssueCommandParser.java b/src/main/java/seedu/progresschecker/logic/parser/EditIssueCommandParser.java new file mode 100644 index 000000000000..7448020e5cd5 --- /dev/null +++ b/src/main/java/seedu/progresschecker/logic/parser/EditIssueCommandParser.java @@ -0,0 +1,107 @@ +package seedu.progresschecker.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.progresschecker.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.progresschecker.logic.parser.CliSyntax.PREFIX_ASSIGNEES; +import static seedu.progresschecker.logic.parser.CliSyntax.PREFIX_BODY; +import static seedu.progresschecker.logic.parser.CliSyntax.PREFIX_LABEL; +import static seedu.progresschecker.logic.parser.CliSyntax.PREFIX_MILESTONE; +import static seedu.progresschecker.logic.parser.CliSyntax.PREFIX_TITLE; + +import java.io.IOException; +import java.util.Collection; +import java.util.Collections; +import java.util.Optional; +import java.util.Set; + +import seedu.progresschecker.commons.core.index.Index; +import seedu.progresschecker.commons.exceptions.IllegalValueException; +import seedu.progresschecker.logic.commands.EditIssueCommand; +import seedu.progresschecker.logic.commands.exceptions.CommandException; +import seedu.progresschecker.logic.parser.exceptions.ParseException; +import seedu.progresschecker.model.issues.Assignees; +import seedu.progresschecker.model.issues.Labels; + +//@@author adityaa1998 +/** + * Parses input arguments and creates a new EditIssueCommand object + */ +public class EditIssueCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the EditIssueCommand + * and returns an EditIssueCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public EditIssueCommand parse(String args) throws ParseException { + requireNonNull(args); + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_TITLE, PREFIX_ASSIGNEES, PREFIX_MILESTONE, PREFIX_BODY, + PREFIX_LABEL); + + Index index; + + try { + index = ParserUtil.parseIndex(argMultimap.getPreamble()); + } catch (IllegalValueException ive) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditIssueCommand.MESSAGE_USAGE)); + } + + EditIssueCommand.EditIssueDescriptor editIssueDescriptor = new EditIssueCommand.EditIssueDescriptor(); + try { + ParserUtil.parseTitle(argMultimap.getValue(PREFIX_TITLE)).ifPresent(editIssueDescriptor::setTitle); + parseAssigneesForEdit(argMultimap.getAllValues(PREFIX_ASSIGNEES)) + .ifPresent(editIssueDescriptor::setAssignees); + ParserUtil.parseMilestone(argMultimap.getValue(PREFIX_MILESTONE)) + .ifPresent(editIssueDescriptor::setMilestone); + ParserUtil.parseBody(argMultimap.getValue(PREFIX_BODY)) + .ifPresent(editIssueDescriptor::setBody); + parseLabelsForEdit(argMultimap.getAllValues(PREFIX_LABEL)).ifPresent(editIssueDescriptor::setLabels); + } catch (IllegalValueException ive) { + throw new ParseException(ive.getMessage(), ive); + } + + if (!editIssueDescriptor.isAnyFieldEdited()) { + throw new ParseException(EditIssueCommand.MESSAGE_NOT_EDITED); + } + + try { + return new EditIssueCommand(index, editIssueDescriptor); + } catch (CommandException ce) { + throw new ParseException(EditIssueCommand.MESSAGE_NOT_EDITED); + } catch (IOException ie) { + throw new ParseException(EditIssueCommand.MESSAGE_NOT_EDITED); + } + } + + /** + * Parses {@code Collection labels} into a {@code Set} if {@code labels} is non-empty. + * If {@code labels} contain only one element which is an empty string, it will be parsed into a + * {@code Set} containing zero labels. + */ + private Optional> parseLabelsForEdit(Collection labels) throws IllegalValueException { + assert labels != null; + + if (labels.isEmpty()) { + return Optional.empty(); + } + Collection labelSet = labels.size() == 1 && labels.contains("") ? Collections.emptySet() : labels; + return Optional.of(ParserUtil.parseLabels(labels)); + } + + /** + * Parses {@code Collection assignees} into a {@code Set} if {@code assignees} is non-empty. + * If {@code assignees} contain only one element which is an empty string, it will be parsed into a + * {@code Set} containing zero assignees. + */ + private Optional> parseAssigneesForEdit(Collection assignees) throws IllegalValueException { + assert assignees != null; + + if (assignees.isEmpty()) { + return Optional.empty(); + } + Collection assigneesSet = assignees.size() == 1 + && assignees.contains("") ? Collections.emptySet() : assignees; + return Optional.of(ParserUtil.parseAssignees(assignees)); + } +} diff --git a/src/main/java/seedu/address/logic/parser/FindCommandParser.java b/src/main/java/seedu/progresschecker/logic/parser/FindCommandParser.java similarity index 72% rename from src/main/java/seedu/address/logic/parser/FindCommandParser.java rename to src/main/java/seedu/progresschecker/logic/parser/FindCommandParser.java index b186a967cb94..78fb5fcd486c 100644 --- a/src/main/java/seedu/address/logic/parser/FindCommandParser.java +++ b/src/main/java/seedu/progresschecker/logic/parser/FindCommandParser.java @@ -1,12 +1,12 @@ -package seedu.address.logic.parser; +package seedu.progresschecker.logic.parser; -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.progresschecker.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; import java.util.Arrays; -import seedu.address.logic.commands.FindCommand; -import seedu.address.logic.parser.exceptions.ParseException; -import seedu.address.model.person.NameContainsKeywordsPredicate; +import seedu.progresschecker.logic.commands.FindCommand; +import seedu.progresschecker.logic.parser.exceptions.ParseException; +import seedu.progresschecker.model.person.NameContainsKeywordsPredicate; /** * Parses input arguments and creates a new FindCommand object diff --git a/src/main/java/seedu/progresschecker/logic/parser/GitLoginCommandParser.java b/src/main/java/seedu/progresschecker/logic/parser/GitLoginCommandParser.java new file mode 100644 index 000000000000..427d64d0b60d --- /dev/null +++ b/src/main/java/seedu/progresschecker/logic/parser/GitLoginCommandParser.java @@ -0,0 +1,59 @@ +package seedu.progresschecker.logic.parser; + +import static seedu.progresschecker.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.progresschecker.logic.parser.CliSyntax.PREFIX_GIT_PASSCODE; +import static seedu.progresschecker.logic.parser.CliSyntax.PREFIX_GIT_REPO; +import static seedu.progresschecker.logic.parser.CliSyntax.PREFIX_GIT_USERNAME; + +import java.util.stream.Stream; + +import seedu.progresschecker.commons.exceptions.IllegalValueException; +import seedu.progresschecker.logic.commands.GitLoginCommand; +import seedu.progresschecker.logic.parser.exceptions.ParseException; +import seedu.progresschecker.model.credentials.GitDetails; +import seedu.progresschecker.model.credentials.Passcode; +import seedu.progresschecker.model.credentials.Repository; +import seedu.progresschecker.model.credentials.Username; + +//@@author adityaa1998 +/** + * Parses input arguments and creates a new GitDetails object + */ +public class GitLoginCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the GitLoginCommand + * and returns an GitLoginCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public GitLoginCommand parse(String args) throws ParseException { + ArgumentMultimap argMultimap = + ArgumentTokenizer.tokenize(args, PREFIX_GIT_USERNAME, PREFIX_GIT_PASSCODE, PREFIX_GIT_REPO); + + if (!arePrefixesPresent(argMultimap, PREFIX_GIT_USERNAME, PREFIX_GIT_PASSCODE, PREFIX_GIT_REPO) + || !argMultimap.getPreamble().isEmpty()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, GitLoginCommand.MESSAGE_USAGE)); + } + + try { + Username username = ParserUtil.parseGitUsername(argMultimap.getValue(PREFIX_GIT_USERNAME)).get(); + Passcode passcode = ParserUtil.parsePasscode(argMultimap.getValue(PREFIX_GIT_PASSCODE)).get(); + Repository repository = ParserUtil.parseRepository(argMultimap.getValue(PREFIX_GIT_REPO)).get(); + + GitDetails details = new GitDetails(username, passcode, repository); + + return new GitLoginCommand(details); + } catch (IllegalValueException ive) { + throw new ParseException(ive.getMessage(), ive); + } + } + + /** + * Returns true if none of the prefixes contains empty {@code Optional} values in the given + * {@code ArgumentMultimap}. + */ + private static boolean arePrefixesPresent(ArgumentMultimap argumentMultimap, Prefix... prefixes) { + return Stream.of(prefixes).allMatch(prefix -> argumentMultimap.getValue(prefix).isPresent()); + } + +} diff --git a/src/main/java/seedu/progresschecker/logic/parser/GoToTaskUrlCommandParser.java b/src/main/java/seedu/progresschecker/logic/parser/GoToTaskUrlCommandParser.java new file mode 100644 index 000000000000..399d371d62b6 --- /dev/null +++ b/src/main/java/seedu/progresschecker/logic/parser/GoToTaskUrlCommandParser.java @@ -0,0 +1,30 @@ +package seedu.progresschecker.logic.parser; + +import static seedu.progresschecker.logic.parser.ParserUtil.MESSAGE_INVALID_INDEX_OR_FORMAT; + +import seedu.progresschecker.commons.exceptions.IllegalValueException; +import seedu.progresschecker.logic.commands.GoToTaskUrlCommand; +import seedu.progresschecker.logic.parser.exceptions.ParseException; + +//@@author EdwardKSG +/** + * Parses input arguments and creates a new GoToTaskUrlCommand object + */ +public class GoToTaskUrlCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the GoToTaskUrlCommand + * and returns a GoToTaskUrlCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public GoToTaskUrlCommand parse(String args) throws ParseException { + try { + int index = ParserUtil.parseTaskIndex(args); + return new GoToTaskUrlCommand(index); + } catch (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_INDEX_OR_FORMAT, GoToTaskUrlCommand.MESSAGE_USAGE)); + } + } + +} diff --git a/src/main/java/seedu/progresschecker/logic/parser/ListIssuesCommandParser.java b/src/main/java/seedu/progresschecker/logic/parser/ListIssuesCommandParser.java new file mode 100644 index 000000000000..14a15ed5d69e --- /dev/null +++ b/src/main/java/seedu/progresschecker/logic/parser/ListIssuesCommandParser.java @@ -0,0 +1,29 @@ +package seedu.progresschecker.logic.parser; + +import static seedu.progresschecker.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.progresschecker.commons.exceptions.IllegalValueException; +import seedu.progresschecker.logic.commands.ListIssuesCommand; +import seedu.progresschecker.logic.parser.exceptions.ParseException; + +//@@author adityaa1998 +/** + * Parses input arguments and creates a new ListIssuesCommand object + */ +public class ListIssuesCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the ListIssueCommand + * and returns an ListIssueCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public ListIssuesCommand parse(String args) throws ParseException { + try { + String issueState = ParserUtil.parseStateType(args); + return new ListIssuesCommand(issueState); + } catch (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ListIssuesCommand.MESSAGE_USAGE)); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/Parser.java b/src/main/java/seedu/progresschecker/logic/parser/Parser.java similarity index 69% rename from src/main/java/seedu/address/logic/parser/Parser.java rename to src/main/java/seedu/progresschecker/logic/parser/Parser.java index d6551ad8e3ff..837d0631a825 100644 --- a/src/main/java/seedu/address/logic/parser/Parser.java +++ b/src/main/java/seedu/progresschecker/logic/parser/Parser.java @@ -1,7 +1,7 @@ -package seedu.address.logic.parser; +package seedu.progresschecker.logic.parser; -import seedu.address.logic.commands.Command; -import seedu.address.logic.parser.exceptions.ParseException; +import seedu.progresschecker.logic.commands.Command; +import seedu.progresschecker.logic.parser.exceptions.ParseException; /** * Represents a Parser that is able to parse user input into a {@code Command} of type {@code T}. diff --git a/src/main/java/seedu/progresschecker/logic/parser/ParserUtil.java b/src/main/java/seedu/progresschecker/logic/parser/ParserUtil.java new file mode 100644 index 000000000000..dea570455e48 --- /dev/null +++ b/src/main/java/seedu/progresschecker/logic/parser/ParserUtil.java @@ -0,0 +1,569 @@ +package seedu.progresschecker.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.progresschecker.logic.commands.ViewTaskListCommand.ALL_WEEK; +import static seedu.progresschecker.logic.commands.ViewTaskListCommand.COMPULSORY; +import static seedu.progresschecker.logic.commands.ViewTaskListCommand.MAX_TITLE_LENGTH; +import static seedu.progresschecker.logic.commands.ViewTaskListCommand.MESSAGE_TITLE_CONSTRAINTS; +import static seedu.progresschecker.logic.commands.ViewTaskListCommand.SUBMISSION; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; + +import seedu.progresschecker.commons.core.index.Index; +import seedu.progresschecker.commons.exceptions.IllegalValueException; +import seedu.progresschecker.commons.util.StringUtil; +import seedu.progresschecker.model.credentials.Passcode; +import seedu.progresschecker.model.credentials.Repository; +import seedu.progresschecker.model.credentials.Username; +import seedu.progresschecker.model.exercise.QuestionIndex; +import seedu.progresschecker.model.exercise.StudentAnswer; +import seedu.progresschecker.model.issues.Assignees; +import seedu.progresschecker.model.issues.Body; +import seedu.progresschecker.model.issues.Labels; +import seedu.progresschecker.model.issues.Milestone; +import seedu.progresschecker.model.issues.Title; +import seedu.progresschecker.model.person.Email; +import seedu.progresschecker.model.person.GithubUsername; +import seedu.progresschecker.model.person.Major; +import seedu.progresschecker.model.person.Name; +import seedu.progresschecker.model.person.Phone; +import seedu.progresschecker.model.person.Year; +import seedu.progresschecker.model.tag.Tag; + +/** + * Contains utility methods used for parsing strings in the various *Parser classes. + * {@code ParserUtil} contains methods that take in {@code Optional} as parameters. However, it goes against Java's + * convention (see https://stackoverflow.com/a/39005452) as {@code Optional} should only be used a return type. + * Justification: The methods in concern receive {@code Optional} return values from other methods as parameters and + * return {@code Optional} values based on whether the parameters were present. Therefore, it is redundant to unwrap the + * initial {@code Optional} before passing to {@code ParserUtil} as a parameter and then re-wrap it into an + * {@code Optional} return value inside {@code ParserUtil} methods. + */ +public class ParserUtil { + + public static final String MESSAGE_INVALID_INDEX = "Index is not a non-zero unsigned integer."; + public static final String MESSAGE_INVALID_INDEX_OR_FORMAT = "Wrong format or index is not a positive integer."; + public static final String MESSAGE_INVALID_TASK_FILTER = "Filter keyword is not an integer 1~13 " + + "or * or \"submission\" or \"sub\" or \"compulsory\" or \"com\"."; + public static final String MESSAGE_INVALID_TAB_TYPE = "Invalid command format!"; + public static final String MESSAGE_INVALID_WEEK_NUMBER = "Invalid command format!"; + public static final String MESSAGE_INSUFFICIENT_PARTS = "Number of parts must be more than 1."; + + /** + * Parses {@code oneBasedIndex} into an {@code Index} and returns it. Leading and trailing whitespaces will be + * trimmed. + * @throws IllegalValueException if the specified index is invalid (not non-zero unsigned integer). + */ + public static Index parseIndex(String oneBasedIndex) throws IllegalValueException { + String trimmedIndex = oneBasedIndex.trim(); + if (!StringUtil.isNonZeroUnsignedInteger(trimmedIndex)) { + throw new IllegalValueException(MESSAGE_INVALID_INDEX); + } + return Index.fromOneBased(Integer.parseInt(trimmedIndex)); + } + + //@@author EdwardKSG + /** + * Parses {@code String} into an {@code int} and returns it. Leading and trailing whitespaces will be + * trimmed. + * @throws IllegalValueException if the specified index is invalid (not non-zero unsigned integer). + */ + public static int parseTaskIndex(String index) throws IllegalValueException { + String trimmedIndex = index.trim(); + if (!StringUtil.isNonZeroUnsignedInteger(trimmedIndex)) { + throw new IllegalValueException(MESSAGE_INVALID_INDEX); + } + return Integer.parseInt(trimmedIndex); + } + + /** + * Parses {@code String} into an {@code int} and returns it. Leading and trailing whitespaces will be + * trimmed. + * @throws IllegalValueException if the specified week number is invalid (neither an integer ranging from 1 to 13 + * nor an asterisk(*)). + */ + public static int parseTaskWeek(String week) throws IllegalValueException { + String trimmedWeek = week.trim(); + if (trimmedWeek.equals("*")) { + return ALL_WEEK; + } else if (trimmedWeek.equals("sub") || trimmedWeek.equals("submission")) { + return SUBMISSION; + } else if (trimmedWeek.equals("com") || trimmedWeek.equals("compulsory")) { + return COMPULSORY; + } else if (!StringUtil.isNonZeroUnsignedInteger(trimmedWeek)) { + throw new IllegalValueException(MESSAGE_INVALID_TASK_FILTER); + } + int intWeek = Integer.parseInt(trimmedWeek); + if (intWeek >= 1 && intWeek <= 13) { + return intWeek; + } else { + throw new IllegalValueException(MESSAGE_INVALID_TASK_FILTER); + } + } + //@@author + + //@@author iNekox3 + /** + * Parses {@code type} into a {@code String[]} and returns it. Leading and trailing whitespaces will be + * trimmed. + * @throws IllegalValueException if the specified type is invalid, that is: + * i. type is not of string "profile", "task", or "exercise". + * ii. type is of "exercise" and comes with an index specified + * in which the index does not fall between the accepted range. + */ + public static String[] parseTabType(String type) throws IllegalValueException { + String trimmedType = type.trim(); + String[] trimmedTypeArray = trimmedType.split(" "); + if (!trimmedTypeArray[0].equals("profile") + && !trimmedTypeArray[0].equals("task") + && !trimmedTypeArray[0].equals("exercise") + && !trimmedTypeArray[0].equals("issues")) { + throw new IllegalValueException(MESSAGE_INVALID_TAB_TYPE); + } + + if (trimmedTypeArray.length > 1 + && trimmedTypeArray[0].equals("exercise") + && !StringUtil.isWithinRange(trimmedTypeArray[1])) { + throw new IllegalValueException(MESSAGE_INVALID_WEEK_NUMBER); + } + + return trimmedTypeArray; + } + + //@@author Livian1107 + /** + * Parses {@code type} into a {@code String} and returns it. Leading and trailing whitespaces will be + * trimmed. + * @throws IllegalValueException if the specified theme is invalid (not of string "day" or "night"). + */ + public static String parseTheme(String theme) throws IllegalValueException { + String trimmedType = theme.trim(); + if (!trimmedType.equals("day") && !trimmedType.equals("night")) { + throw new IllegalValueException(MESSAGE_INVALID_TAB_TYPE); + } + return trimmedType; + } + + //@@author EdwardKSG + /** + * Parses a {@code String Title} into a {@code String}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code Title} is invalid. + */ + public static String parseTaskTitle(String title) throws IllegalValueException { + requireNonNull(title); + String trimmedTitle = title.trim(); + if (trimmedTitle.length() > MAX_TITLE_LENGTH) { + throw new IllegalValueException(MESSAGE_TITLE_CONSTRAINTS); + } + return trimmedTitle; + } + //@@author + + /** + * Parses a {@code String name} into a {@code Name}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code name} is invalid. + */ + public static Name parseName(String name) throws IllegalValueException { + requireNonNull(name); + String trimmedName = name.trim(); + if (!Name.isValidName(trimmedName)) { + throw new IllegalValueException(Name.MESSAGE_NAME_CONSTRAINTS); + } + return new Name(trimmedName); + } + + /** + * Parses a {@code Optional name} into an {@code Optional} if {@code name} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseName(Optional name) throws IllegalValueException { + requireNonNull(name); + return name.isPresent() ? Optional.of(parseName(name.get())) : Optional.empty(); + } + + //@@author adityaa1998 + /** + * Parses a {@code String title} into a {@code Title}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code title} is invalid. + */ + + public static Title parseTitle(String title) throws IllegalValueException { + requireNonNull(title); + String trimmedTitle = title.trim(); + if (!Title.isValidTitle(trimmedTitle)) { + throw new IllegalValueException(Title.MESSAGE_TITLE_CONSTRAINTS); + } + return new Title(trimmedTitle); + } + + /** + * Parses a {@code Optional title} into an {@code Optional} if {@code title} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional parseTitle(Optional<String> title) throws IllegalValueException { + requireNonNull(title); + return title.isPresent() ? Optional.of(parseTitle(title.get())) : Optional.empty(); + } + + /** + * Parses a {@code String assignees} into a {@code Assignees}. + * Leading and trailing whitespaces will be trimmed. + */ + + public static Assignees parseAssignees(String assignees) throws IllegalValueException { + requireNonNull(assignees); + String trimmedAssignees = assignees.trim(); + if (!Assignees.isValidAssignee(trimmedAssignees)) { + throw new IllegalValueException(Assignees.MESSAGE_ASSIGNEES_CONSTRAINTS); + } + return new Assignees(trimmedAssignees); + } + + /** + * Parses {@code Collection<String> assignees} into a {@code Set<Assignees>}. + */ + public static Set<Assignees> parseAssignees(Collection<String> assignees) throws IllegalValueException { + requireNonNull(assignees); + final Set<Assignees> assigneesSet = new HashSet<>(); + for (String assigneeName : assignees) { + assigneesSet.add(parseAssignees(assigneeName)); + } + return assigneesSet; + } + + /** + * Parses a {@code String labels} into a {@code Labels}. + * Leading and trailing whitespaces will be trimmed. + */ + + public static Labels parseLabels(String labels) throws IllegalValueException { + requireNonNull(labels); + String trimmedLabels = labels.trim(); + if (!Labels.isValidLabel(trimmedLabels)) { + throw new IllegalValueException(Labels.MESSAGE_LABEL_CONSTRAINTS); + } + return new Labels(trimmedLabels); + } + + /** + * Parses {@code Collection<String> labels} into a {@code Set<Labels>}. + */ + public static Set<Labels> parseLabels(Collection<String> labels) throws IllegalValueException { + requireNonNull(labels); + final Set<Labels> labelsSet = new HashSet<>(); + for (String labelName : labels) { + labelsSet.add(parseLabels(labelName)); + } + return labelsSet; + } + + + /** + * Parses a {@code String milestone} into a {@code Milestone}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code milestone} is invalid. + */ + + public static Milestone parseMilestone(String milestone) throws IllegalValueException { + requireNonNull(milestone); + String trimmedMilestone = milestone.trim(); + return new Milestone(trimmedMilestone); + } + + /** + * Parses a {@code Optional<String> milestone} into an {@code Optional<Milestone>} if {@code milestone} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional<Milestone> parseMilestone(Optional<String> milestone) throws IllegalValueException { + requireNonNull(milestone); + return milestone.isPresent() ? Optional.of(parseMilestone(milestone.get())) : Optional.empty(); + } + + /** + * Parses a {@code String body} into a {@code Body}. + * Leading and trailing whitespaces will be trimmed. + */ + + public static Body parseBody(String body) throws IllegalValueException { + requireNonNull(body); + String trimmedBody = body.trim(); + if (!Body.isValidBody(trimmedBody)) { + throw new IllegalValueException(Body.MESSAGE_BODY_CONSTRAINTS); + } + return new Body(trimmedBody); + } + + /** + * Parses a {@code Optional<String> body} into an {@code Optional<Body>} if {@code body} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional<Body> parseBody(Optional<String> body) throws IllegalValueException { + requireNonNull(body); + return body.isPresent() ? Optional.of(parseBody(body.get())) : Optional.empty(); + } + + /** + * Parses a {@code String username} into a {@code Username}. + * Leading and trailing whitespaces will be trimmed. + */ + public static Username parseGitUsername(String username) throws IllegalValueException { + requireNonNull(username); + String trimmedUsername = username.trim(); + if (!Username.isValidUsername(trimmedUsername)) { + throw new IllegalValueException(Username.MESSAGE_GITUSERNAME_CONSTRAINTS); + } + return new Username(trimmedUsername); + } + + /** + Parses a {@code Optional<String> username} into an {@code Optional<Username>} if {@code username} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional<Username> parseGitUsername(Optional<String> username) throws IllegalValueException { + requireNonNull(username); + return username.isPresent() ? Optional.of(parseGitUsername(username.get())) : Optional.empty(); + } + + /** + * Parses a {@code String passcode} into a {@code Passcode}. + */ + public static Passcode parsePasscode(String passcode) throws IllegalValueException { + requireNonNull(passcode); + if (!Passcode.isValidPasscode(passcode)) { + throw new IllegalValueException(Passcode.MESSAGE_PASSCODE_CONSTRAINTS); + } + return new Passcode(passcode); + } + + /** + Parses a {@code Optional<String> Passcode} into an {@code Optional<Passcode>} if {@code passcpde} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional<Passcode> parsePasscode(Optional<String> passcode) throws IllegalValueException { + requireNonNull(passcode); + return passcode.isPresent() ? Optional.of(parsePasscode(passcode.get())) : Optional.empty(); + } + + /** + * Parses a {@code String repositroy} into a {@code Repository}. + * Leading and trailing whitespaces will be trimmed. + */ + public static Repository parseRepository(String repository) throws IllegalValueException { + requireNonNull(repository); + String trimmedRepository = repository.trim(); + if (!Repository.isValidRepository(trimmedRepository)) { + throw new IllegalValueException(Repository.MESSAGE_REPOSITORY_CONSTRAINTS); + } + return new Repository(trimmedRepository); + } + + /** + Parses a {@code Optional<String> Repository} into an {@code Optional<Repository>} if {@code repository} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional<Repository> parseRepository(Optional<String> repository) throws IllegalValueException { + requireNonNull(repository); + return repository.isPresent() ? Optional.of(parseRepository(repository.get())) : Optional.empty(); + } + + /** + * Parses a {@code String state} into a trimmed string. + * Leading and trailing whitespaces will be trimmed. + */ + public static String parseStateType(String state) throws IllegalValueException { + requireNonNull(state); + String trimmedState = state.trim(); + return trimmedState; + } + //@@author + + //@@author EdwardKSG + /** + * Parses a {@code String username} into a {@code GithubUsername}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code username} is invalid. + */ + + public static GithubUsername parseUsername(String username) throws IllegalValueException { + requireNonNull(username); + String trimmedUsername = username.trim(); + if (!GithubUsername.isValidUsername(trimmedUsername)) { + throw new IllegalValueException(GithubUsername.MESSAGE_USERNAME_CONSTRAINTS); + } + return new GithubUsername(trimmedUsername); + } + + /** + * Parses a {@code Optional<String> username} into an {@code Optional<GithubUsername>} + * if {@code username} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional<GithubUsername> parseUsername(Optional<String> username) throws IllegalValueException { + requireNonNull(username); + return username.isPresent() ? Optional.of(parseUsername(username.get())) : Optional.empty(); + } + //@@author + + /** + * Parses a {@code String phone} into a {@code Phone}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code phone} is invalid. + */ + public static Phone parsePhone(String phone) throws IllegalValueException { + requireNonNull(phone); + String trimmedPhone = phone.trim(); + if (!Phone.isValidPhone(trimmedPhone)) { + throw new IllegalValueException(Phone.MESSAGE_PHONE_CONSTRAINTS); + } + return new Phone(trimmedPhone); + } + + /** + * Parses a {@code Optional<String> phone} into an {@code Optional<Phone>} if {@code phone} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional<Phone> parsePhone(Optional<String> phone) throws IllegalValueException { + requireNonNull(phone); + return phone.isPresent() ? Optional.of(parsePhone(phone.get())) : Optional.empty(); + } + + //@@author EdwardKSG + /** + * Parses a {@code String major} into an {@code Major}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code major} is invalid. + */ + public static Major parseMajor(String major) throws IllegalValueException { + requireNonNull(major); + String trimmedMajor = major.trim(); + if (!Major.isValidMajor(trimmedMajor)) { + throw new IllegalValueException(Major.MESSAGE_MAJOR_CONSTRAINTS); + } + return new Major(trimmedMajor); + } + + /** + * Parses a {@code Optional<String> major} into an {@code Optional<Major>} if {@code major} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional<Major> parseMajor(Optional<String> major) throws IllegalValueException { + requireNonNull(major); + return major.isPresent() ? Optional.of(parseMajor(major.get())) : Optional.empty(); + } + + /** + * Parses a {@code String year} into an {@code Year}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code year} is invalid. + */ + public static Year parseYear(String year) throws IllegalValueException { + requireNonNull(year); + String trimmedYear = year.trim(); + if (!Year.isValidYear(trimmedYear)) { + throw new IllegalValueException(Year.MESSAGE_YEAR_CONSTRAINTS); + } + return new Year(trimmedYear); + } + + /** + * Parses a {@code Optional<String> year} into an {@code Optional<Year>} if {@code year} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional<Year> parseYear(Optional<String> year) throws IllegalValueException { + requireNonNull(year); + return year.isPresent() ? Optional.of(parseYear(year.get())) : Optional.empty(); + } + //@@author + + /** + * Parses a {@code String email} into an {@code Email}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code email} is invalid. + */ + public static Email parseEmail(String email) throws IllegalValueException { + requireNonNull(email); + String trimmedEmail = email.trim(); + if (!Email.isValidEmail(trimmedEmail)) { + throw new IllegalValueException(Email.MESSAGE_EMAIL_CONSTRAINTS); + } + return new Email(trimmedEmail); + } + + /** + * Parses a {@code Optional<String> email} into an {@code Optional<Email>} if {@code email} is present. + * See header comment of this class regarding the use of {@code Optional} parameters. + */ + public static Optional<Email> parseEmail(Optional<String> email) throws IllegalValueException { + requireNonNull(email); + return email.isPresent() ? Optional.of(parseEmail(email.get())) : Optional.empty(); + } + + /** + * Parses a {@code String tag} into a {@code Tag}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code tag} is invalid. + */ + public static Tag parseTag(String tag) throws IllegalValueException { + requireNonNull(tag); + String trimmedTag = tag.trim(); + if (!Tag.isValidTagName(trimmedTag)) { + throw new IllegalValueException(Tag.MESSAGE_TAG_CONSTRAINTS); + } + return new Tag(trimmedTag); + } + + /** + * Parses {@code Collection<String> tags} into a {@code Set<Tag>}. + */ + public static Set<Tag> parseTags(Collection<String> tags) throws IllegalValueException { + requireNonNull(tags); + final Set<Tag> tagSet = new HashSet<>(); + for (String tagName : tags) { + tagSet.add(parseTag(tagName)); + } + return tagSet; + } + + //@@author iNekox3 + /** + * Parses a {@code String questionIndex} into a {@code QuestionIndex}. + * Leading and trailing whitespaces will be trimmed. + * + * @throws IllegalValueException if the given {@code questionIndex} is invalid. + */ + public static QuestionIndex parseQuestionIndex(String questionIndex) throws IllegalValueException { + requireNonNull(questionIndex); + String trimmedQuestionIndex = questionIndex.trim(); + if (!QuestionIndex.isValidIndex(trimmedQuestionIndex)) { + throw new IllegalValueException(QuestionIndex.MESSAGE_INDEX_CONSTRAINTS); + } + return new QuestionIndex(trimmedQuestionIndex); + } + + /** + * Parses a {@code String studentAnswer} into a {@code StudentAnswer}. + * Leading and trailing whitespaces will be trimmed. + */ + public static StudentAnswer parseStudentAnswer(String studentAnswer) { + requireNonNull(studentAnswer); + String trimmedStudentAnswer = studentAnswer.trim(); + + return new StudentAnswer(trimmedStudentAnswer); + } +} diff --git a/src/main/java/seedu/address/logic/parser/Prefix.java b/src/main/java/seedu/progresschecker/logic/parser/Prefix.java similarity index 94% rename from src/main/java/seedu/address/logic/parser/Prefix.java rename to src/main/java/seedu/progresschecker/logic/parser/Prefix.java index c859d5fa5db1..5420fc8af0f8 100644 --- a/src/main/java/seedu/address/logic/parser/Prefix.java +++ b/src/main/java/seedu/progresschecker/logic/parser/Prefix.java @@ -1,4 +1,4 @@ -package seedu.address.logic.parser; +package seedu.progresschecker.logic.parser; /** * A prefix that marks the beginning of an argument in an arguments string. diff --git a/src/main/java/seedu/progresschecker/logic/parser/ProgressCheckerParser.java b/src/main/java/seedu/progresschecker/logic/parser/ProgressCheckerParser.java new file mode 100644 index 000000000000..4a7c6ecfccc4 --- /dev/null +++ b/src/main/java/seedu/progresschecker/logic/parser/ProgressCheckerParser.java @@ -0,0 +1,190 @@ +package seedu.progresschecker.logic.parser; + +import static seedu.progresschecker.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.progresschecker.commons.core.Messages.MESSAGE_UNKNOWN_COMMAND; +import static seedu.progresschecker.logic.commands.AddDefaultTasksCommand.DEFAULT_LIST_TITLE; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import seedu.progresschecker.logic.commands.AddCommand; +import seedu.progresschecker.logic.commands.AddDefaultTasksCommand; +import seedu.progresschecker.logic.commands.AnswerCommand; +import seedu.progresschecker.logic.commands.ClearCommand; +import seedu.progresschecker.logic.commands.CloseIssueCommand; +import seedu.progresschecker.logic.commands.Command; +import seedu.progresschecker.logic.commands.CompleteTaskCommand; +import seedu.progresschecker.logic.commands.CreateIssueCommand; +import seedu.progresschecker.logic.commands.DeleteCommand; +import seedu.progresschecker.logic.commands.EditCommand; +import seedu.progresschecker.logic.commands.EditIssueCommand; +import seedu.progresschecker.logic.commands.ExitCommand; +import seedu.progresschecker.logic.commands.FindCommand; +import seedu.progresschecker.logic.commands.GitLoginCommand; +import seedu.progresschecker.logic.commands.GitLogoutCommand; +import seedu.progresschecker.logic.commands.GoToTaskUrlCommand; +import seedu.progresschecker.logic.commands.HelpCommand; +import seedu.progresschecker.logic.commands.HistoryCommand; +import seedu.progresschecker.logic.commands.ListCommand; +import seedu.progresschecker.logic.commands.ListIssuesCommand; +import seedu.progresschecker.logic.commands.RedoCommand; +import seedu.progresschecker.logic.commands.ReopenIssueCommand; +import seedu.progresschecker.logic.commands.ResetTaskCommand; +import seedu.progresschecker.logic.commands.SelectCommand; +import seedu.progresschecker.logic.commands.SortCommand; +import seedu.progresschecker.logic.commands.ThemeCommand; +import seedu.progresschecker.logic.commands.UndoCommand; +import seedu.progresschecker.logic.commands.UploadCommand; +import seedu.progresschecker.logic.commands.ViewCommand; +import seedu.progresschecker.logic.commands.ViewTaskListCommand; +import seedu.progresschecker.logic.parser.exceptions.ParseException; + +/** + * Parses user input. + */ +public class ProgressCheckerParser { + + /** + * Used for initial separation of command word and args. + */ + private static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?<commandWord>\\S+)(?<arguments>.*)"); + + /** + * Parses user input into command for execution. + * + * @param userInput full user input string + * @return the command based on the user input + * @throws ParseException if the user input does not conform the expected format + */ + public Command parseCommand(String userInput) throws ParseException { + final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim()); + if (!matcher.matches()) { + throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); + } + + final String commandWord = matcher.group("commandWord"); + final String commandWordInLowerCase = commandWord.toLowerCase(); + final String arguments = matcher.group("arguments"); + switch (commandWordInLowerCase) { + + case AddDefaultTasksCommand.COMMAND_WORD: + case AddDefaultTasksCommand.COMMAND_ALIAS: + return new AddDefaultTasksCommand(DEFAULT_LIST_TITLE); + + case ViewTaskListCommand.COMMAND_WORD: + case ViewTaskListCommand.COMMAND_ALIAS: + return new ViewTaskListCommandParser().parse(arguments); + + case CompleteTaskCommand.COMMAND_WORD: + case CompleteTaskCommand.COMMAND_ALIAS: + return new CompleteTaskCommandParser().parse(arguments); + + case ThemeCommand.COMMAND_WORD: + case ThemeCommand.COMMAND_ALIAS: + return new ThemeCommandParser().parse(arguments); + + case ResetTaskCommand.COMMAND_WORD: + case ResetTaskCommand.COMMAND_ALIAS: + return new ResetTaskCommandParser().parse(arguments); + + case GoToTaskUrlCommand.COMMAND_WORD: + case GoToTaskUrlCommand.COMMAND_ALIAS: + return new GoToTaskUrlCommandParser().parse(arguments); + + case AddCommand.COMMAND_WORD: + case AddCommand.COMMAND_ALIAS: + return new AddCommandParser().parse(arguments); + + case AnswerCommand.COMMAND_WORD: + case AnswerCommand.COMMAND_ALIAS: + return new AnswerCommandParser().parse(arguments); + + case EditCommand.COMMAND_WORD: + case EditCommand.COMMAND_ALIAS: + return new EditCommandParser().parse(arguments); + + case EditIssueCommand.COMMAND_WORD: + case EditIssueCommand.COMMAND_ALIAS: + return new EditIssueCommandParser().parse(arguments); + + case SelectCommand.COMMAND_WORD: + case SelectCommand.COMMAND_ALIAS: + return new SelectCommandParser().parse(arguments); + + case DeleteCommand.COMMAND_WORD: + case DeleteCommand.COMMAND_ALIAS: + return new DeleteCommandParser().parse(arguments); + + case UploadCommand.COMMAND_WORD: + case UploadCommand.COMMAND_ALIAS: + return new UploadCommandParser().parse(arguments); + + case ClearCommand.COMMAND_WORD: + case ClearCommand.COMMAND_ALIAS: + return new ClearCommand(); + + case CreateIssueCommand.COMMAND_WORD: + case CreateIssueCommand.COMMAND_ALIAS: + return new CreateIssueParser().parse(arguments); + + case FindCommand.COMMAND_WORD: + case FindCommand.COMMAND_ALIAS: + return new FindCommandParser().parse(arguments); + + case ListCommand.COMMAND_WORD: + case ListCommand.COMMAND_ALIAS: + return new ListCommand(); + + case HistoryCommand.COMMAND_WORD: + return new HistoryCommand(); + + case ExitCommand.COMMAND_WORD: + case ExitCommand.COMMAND_ALIAS: + return new ExitCommand(); + + case HelpCommand.COMMAND_WORD: + case HelpCommand.COMMAND_ALIAS: + return new HelpCommand(); + + case UndoCommand.COMMAND_WORD: + case UndoCommand.COMMAND_ALIAS: + return new UndoCommand(); + + case RedoCommand.COMMAND_WORD: + case RedoCommand.COMMAND_ALIAS: + return new RedoCommand(); + + case SortCommand.COMMAND_WORD: + case SortCommand.COMMAND_ALIAS: + return new SortCommand(); + + case ViewCommand.COMMAND_WORD: + case ViewCommand.COMMAND_ALIAS: + return new ViewCommandParser().parse(arguments); + + case ReopenIssueCommand.COMMAND_WORD: + case ReopenIssueCommand.COMMAND_ALIAS: + return new ReopenIssueCommandParser().parse(arguments); + + case CloseIssueCommand.COMMAND_WORD: + case CloseIssueCommand.COMMAND_ALIAS: + return new CloseIssueCommandParser().parse(arguments); + + case GitLoginCommand.COMMAND_WORD: + case GitLoginCommand.COMMAND_ALIAS: + return new GitLoginCommandParser().parse(arguments); + + case GitLogoutCommand.COMMAND_WORD: + case GitLogoutCommand.COMMAND_ALIAS: + return new GitLogoutCommand(); + + case ListIssuesCommand.COMMAND_WORD: + case ListIssuesCommand.COMMAND_ALIAS: + return new ListIssuesCommandParser().parse(arguments); + + default: + throw new ParseException(MESSAGE_UNKNOWN_COMMAND); + } + } + +} diff --git a/src/main/java/seedu/progresschecker/logic/parser/ReopenIssueCommandParser.java b/src/main/java/seedu/progresschecker/logic/parser/ReopenIssueCommandParser.java new file mode 100644 index 000000000000..d7af5ac5f051 --- /dev/null +++ b/src/main/java/seedu/progresschecker/logic/parser/ReopenIssueCommandParser.java @@ -0,0 +1,31 @@ +package seedu.progresschecker.logic.parser; + +import static seedu.progresschecker.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.progresschecker.commons.core.index.Index; +import seedu.progresschecker.commons.exceptions.IllegalValueException; +import seedu.progresschecker.logic.commands.ReopenIssueCommand; +import seedu.progresschecker.logic.parser.exceptions.ParseException; + +//@@author adityaa1998 +/** + * Parses input arguments and creates a new CloseIssueCommand object + */ +public class ReopenIssueCommandParser implements Parser<ReopenIssueCommand> { + + /** + * Parses the given {@code String} of arguments in the context of the ReopenIssueCommand + * and returns an ReopenIssueCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public ReopenIssueCommand parse(String args) throws ParseException { + try { + Index index = ParserUtil.parseIndex(args); + return new ReopenIssueCommand(index); + } catch (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ReopenIssueCommand.MESSAGE_USAGE)); + } + } +} + diff --git a/src/main/java/seedu/progresschecker/logic/parser/ResetTaskCommandParser.java b/src/main/java/seedu/progresschecker/logic/parser/ResetTaskCommandParser.java new file mode 100644 index 000000000000..326bd4d6d04d --- /dev/null +++ b/src/main/java/seedu/progresschecker/logic/parser/ResetTaskCommandParser.java @@ -0,0 +1,30 @@ +package seedu.progresschecker.logic.parser; + +import static seedu.progresschecker.logic.parser.ParserUtil.MESSAGE_INVALID_INDEX_OR_FORMAT; + +import seedu.progresschecker.commons.exceptions.IllegalValueException; +import seedu.progresschecker.logic.commands.ResetTaskCommand; +import seedu.progresschecker.logic.parser.exceptions.ParseException; + +//@@author EdwardKSG +/** + * Parses input arguments and creates a new ResetTaskCommand object + */ +public class ResetTaskCommandParser implements Parser<ResetTaskCommand> { + + /** + * Parses the given {@code String} of arguments in the context of the ResetTaskCommand + * and returns an ResetTaskCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public ResetTaskCommand parse(String args) throws ParseException { + try { + int index = ParserUtil.parseTaskIndex(args); + return new ResetTaskCommand(index); + } catch (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_INDEX_OR_FORMAT, ResetTaskCommand.MESSAGE_USAGE)); + } + } + +} diff --git a/src/main/java/seedu/address/logic/parser/SelectCommandParser.java b/src/main/java/seedu/progresschecker/logic/parser/SelectCommandParser.java similarity index 66% rename from src/main/java/seedu/address/logic/parser/SelectCommandParser.java rename to src/main/java/seedu/progresschecker/logic/parser/SelectCommandParser.java index bbcae8f4b588..4b7af57cc714 100644 --- a/src/main/java/seedu/address/logic/parser/SelectCommandParser.java +++ b/src/main/java/seedu/progresschecker/logic/parser/SelectCommandParser.java @@ -1,11 +1,11 @@ -package seedu.address.logic.parser; +package seedu.progresschecker.logic.parser; -import static seedu.address.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; +import static seedu.progresschecker.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; -import seedu.address.commons.core.index.Index; -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.logic.commands.SelectCommand; -import seedu.address.logic.parser.exceptions.ParseException; +import seedu.progresschecker.commons.core.index.Index; +import seedu.progresschecker.commons.exceptions.IllegalValueException; +import seedu.progresschecker.logic.commands.SelectCommand; +import seedu.progresschecker.logic.parser.exceptions.ParseException; /** * Parses input arguments and creates a new SelectCommand object diff --git a/src/main/java/seedu/progresschecker/logic/parser/ThemeCommandParser.java b/src/main/java/seedu/progresschecker/logic/parser/ThemeCommandParser.java new file mode 100644 index 000000000000..79378a71583e --- /dev/null +++ b/src/main/java/seedu/progresschecker/logic/parser/ThemeCommandParser.java @@ -0,0 +1,29 @@ +package seedu.progresschecker.logic.parser; + +import static seedu.progresschecker.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.progresschecker.commons.exceptions.IllegalValueException; +import seedu.progresschecker.logic.commands.ThemeCommand; +import seedu.progresschecker.logic.parser.exceptions.ParseException; + +//@@author Livian1107 +/** + * Parses input arguments and creates a new ThemeCommand object + */ +public class ThemeCommandParser implements Parser<ThemeCommand> { + + /** + * Parses the given {@code String} of arguments in the context of the ViewCommand + * and returns an ViewCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public ThemeCommand parse(String args) throws ParseException { + try { + String theme = ParserUtil.parseTheme(args); + return new ThemeCommand(theme); + } catch (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ThemeCommand.MESSAGE_USAGE)); + } + } +} diff --git a/src/main/java/seedu/progresschecker/logic/parser/UploadCommandParser.java b/src/main/java/seedu/progresschecker/logic/parser/UploadCommandParser.java new file mode 100644 index 000000000000..626b22e3e199 --- /dev/null +++ b/src/main/java/seedu/progresschecker/logic/parser/UploadCommandParser.java @@ -0,0 +1,39 @@ +package seedu.progresschecker.logic.parser; + +import static java.util.Objects.requireNonNull; +import static seedu.progresschecker.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.io.IOException; + +import seedu.progresschecker.commons.core.index.Index; +import seedu.progresschecker.commons.exceptions.IllegalValueException; +import seedu.progresschecker.logic.commands.UploadCommand; +import seedu.progresschecker.logic.parser.exceptions.ParseException; + +//@@author Livian1107 +/** + * Parses input arguments and creates a new UploadCommand object + */ +public class UploadCommandParser implements Parser<UploadCommand> { + /** + * Parses the given {@code String} of arguments in the context of the UploadCommand + * and returns an UploadCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public UploadCommand parse(String args) throws ParseException { + requireNonNull(args); + Index index; + String[] content = args.trim().split(" "); + try { + index = ParserUtil.parseIndex(content[0]); + return new UploadCommand(index, content[1]); + } catch (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, UploadCommand.MESSAGE_USAGE)); + } catch (IOException e) { + throw new ParseException( + UploadCommand.MESSAGE_IMAGE_NOT_FOUND); + } + + } +} diff --git a/src/main/java/seedu/progresschecker/logic/parser/ViewCommandParser.java b/src/main/java/seedu/progresschecker/logic/parser/ViewCommandParser.java new file mode 100644 index 000000000000..667f450efe58 --- /dev/null +++ b/src/main/java/seedu/progresschecker/logic/parser/ViewCommandParser.java @@ -0,0 +1,36 @@ +package seedu.progresschecker.logic.parser; + +import static seedu.progresschecker.commons.core.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import seedu.progresschecker.commons.exceptions.IllegalValueException; +import seedu.progresschecker.logic.commands.ViewCommand; +import seedu.progresschecker.logic.parser.exceptions.ParseException; + +//@@author iNekox3 +/** + * Parses input arguments and creates a new ViewCommand object + */ +public class ViewCommandParser implements Parser<ViewCommand> { + + /** + * Parses the given {@code String} of arguments in the context of the ViewCommand + * and returns an ViewCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public ViewCommand parse(String args) throws ParseException { + try { + String[] typeArray = ParserUtil.parseTabType(args); + String type = typeArray[0]; + int weekNumber = -1; + boolean isToggleExerciseByWeek = false; + if (typeArray.length > 1) { + weekNumber = Integer.parseInt(typeArray[1]); + isToggleExerciseByWeek = true; + } + return new ViewCommand(type, weekNumber, isToggleExerciseByWeek); + } catch (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_COMMAND_FORMAT, ViewCommand.MESSAGE_USAGE)); + } + } +} diff --git a/src/main/java/seedu/progresschecker/logic/parser/ViewTaskListCommandParser.java b/src/main/java/seedu/progresschecker/logic/parser/ViewTaskListCommandParser.java new file mode 100644 index 000000000000..c20be4bba040 --- /dev/null +++ b/src/main/java/seedu/progresschecker/logic/parser/ViewTaskListCommandParser.java @@ -0,0 +1,29 @@ +package seedu.progresschecker.logic.parser; + +import static seedu.progresschecker.logic.parser.ParserUtil.MESSAGE_INVALID_TASK_FILTER; + +import seedu.progresschecker.commons.exceptions.IllegalValueException; +import seedu.progresschecker.logic.commands.ViewTaskListCommand; +import seedu.progresschecker.logic.parser.exceptions.ParseException; + +//@@author EdwardKSG +/** + * Parses input arguments and creates a new ViewTaskListCommand object + */ +public class ViewTaskListCommandParser implements Parser<ViewTaskListCommand> { + + /** + * Parses the given {@code String} of arguments in the context of the ViewTaskListCommand + * and returns an ViewTaskListCommand object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public ViewTaskListCommand parse(String args) throws ParseException { + try { + int week = ParserUtil.parseTaskWeek(args); + return new ViewTaskListCommand(week); + } catch (IllegalValueException ive) { + throw new ParseException( + String.format(MESSAGE_INVALID_TASK_FILTER, ViewTaskListCommand.MESSAGE_USAGE)); + } + } +} diff --git a/src/main/java/seedu/address/logic/parser/exceptions/ParseException.java b/src/main/java/seedu/progresschecker/logic/parser/exceptions/ParseException.java similarity index 70% rename from src/main/java/seedu/address/logic/parser/exceptions/ParseException.java rename to src/main/java/seedu/progresschecker/logic/parser/exceptions/ParseException.java index 158a1a54c1c5..907b922515ca 100644 --- a/src/main/java/seedu/address/logic/parser/exceptions/ParseException.java +++ b/src/main/java/seedu/progresschecker/logic/parser/exceptions/ParseException.java @@ -1,6 +1,6 @@ -package seedu.address.logic.parser.exceptions; +package seedu.progresschecker.logic.parser.exceptions; -import seedu.address.commons.exceptions.IllegalValueException; +import seedu.progresschecker.commons.exceptions.IllegalValueException; /** * Represents a parse error encountered by a parser. diff --git a/src/main/java/seedu/progresschecker/model/Model.java b/src/main/java/seedu/progresschecker/model/Model.java new file mode 100644 index 000000000000..586354e3f087 --- /dev/null +++ b/src/main/java/seedu/progresschecker/model/Model.java @@ -0,0 +1,125 @@ +package seedu.progresschecker.model; + +import java.io.IOException; +import java.util.function.Predicate; + +import javafx.collections.ObservableList; +import seedu.progresschecker.commons.core.index.Index; +import seedu.progresschecker.commons.exceptions.IllegalValueException; +import seedu.progresschecker.logic.commands.exceptions.CommandException; +import seedu.progresschecker.model.credentials.GitDetails; +import seedu.progresschecker.model.exercise.Exercise; +import seedu.progresschecker.model.exercise.exceptions.ExerciseNotFoundException; +import seedu.progresschecker.model.issues.Issue; +import seedu.progresschecker.model.person.Person; +import seedu.progresschecker.model.person.exceptions.DuplicatePersonException; +import seedu.progresschecker.model.person.exceptions.PersonNotFoundException; +import seedu.progresschecker.model.photo.PhotoPath; +import seedu.progresschecker.model.photo.exceptions.DuplicatePhotoException; + +/** + * The API of the Model component. + */ +public interface Model { + /** {@code Predicate} that always evaluate to true */ + Predicate<Person> PREDICATE_SHOW_ALL_PERSONS = unused -> true; + + /** {@code Predicate} that always evaluate to true */ + Predicate<Issue> PREDICATE_SHOW_ALL_ISSUES = unused -> true; + + /** Clears existing backing model and replaces with the provided new data. */ + void resetData(ReadOnlyProgressChecker newData); + + /** Returns the ProgressChecker */ + ReadOnlyProgressChecker getProgressChecker(); + + /** Deletes the given person. */ + void deletePerson(Person target) throws PersonNotFoundException; + + /** Adds the given person */ + void addPerson(Person person) throws DuplicatePersonException; + + /** Sorts the persons in ProgressChecker according to their names in alphabetical order */ + void sort(); + + //@@author adityaa1998 + /** authenticates git using password */ + void loginGithub(GitDetails gitdetails) throws IOException, CommandException; + + /** authenticates git using password */ + void logoutGithub() throws CommandException; + + /** creates an issue on github */ + void createIssueOnGitHub(Issue issue) throws IOException, CommandException; + + /** reopen issue on github */ + void reopenIssueOnGithub(Index index) throws IOException, CommandException; + + /** closes an issue issue on github */ + void closeIssueOnGithub(Index index) throws IOException, CommandException; + + /**viwes issues of the specified state */ + void listIssues(String state) throws IllegalValueException, IOException, CommandException; + + /** + * Replaces the fields in Issue {@code index} with {@code editedIssue}. + * + * @throws IOException if while updating the issue there is some problem in authentication + */ + void updateIssue(Index index, Issue editedIssue) throws IOException, CommandException; + + /** Returns unmodifiable view of the filtered issue list */ + ObservableList<Issue> getFilteredIssueList(); + + /** + * Updates the filter of the filtered person list to filter by the given {@code predicate}. + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredIssueList(Predicate<Issue> predicate); + //@@author + + /** + * Replaces the given person {@code target} with {@code editedPerson}. + * + * @throws DuplicatePersonException if updating the person's details causes the person to be equivalent to + * another existing person in the list. + * @throws PersonNotFoundException if {@code target} could not be found in the list. + */ + void updatePerson(Person target, Person editedPerson) + throws DuplicatePersonException, PersonNotFoundException; + + /** Returns an unmodifiable view of the filtered person list */ + ObservableList<Person> getFilteredPersonList(); + + /** + * Updates the filter of the filtered person list to filter by the given {@code predicate}. + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredPersonList(Predicate<Person> predicate); + + //@@author iNekox3 + /** + * Replaces the given exercise {@code target} with {@code editedExercise}. + * + * @throws ExerciseNotFoundException if {@code target} could not be found in the list. + */ + void updateExercise(Exercise target, Exercise editedExercise) + throws ExerciseNotFoundException; + + /** Returns an unmodifiable view of the filtered exercise list */ + ObservableList<Exercise> getFilteredExerciseList(); + + /** + * Updates the filter of the filtered exercise list to filter by the given {@code predicate}. + * @throws NullPointerException if {@code predicate} is null. + */ + void updateFilteredExerciseList(Predicate<Exercise> predicate); + + //@@author + /** Uploads the given photo with given path */ + void uploadPhoto(Person target, String path) + throws PersonNotFoundException, DuplicatePersonException; + + /** Adds a new uploaded photo path */ + void addPhoto(PhotoPath photoPath) throws DuplicatePhotoException; +} diff --git a/src/main/java/seedu/progresschecker/model/ModelManager.java b/src/main/java/seedu/progresschecker/model/ModelManager.java new file mode 100644 index 000000000000..9631d8481779 --- /dev/null +++ b/src/main/java/seedu/progresschecker/model/ModelManager.java @@ -0,0 +1,251 @@ +package seedu.progresschecker.model; + +import static java.util.Objects.requireNonNull; +import static seedu.progresschecker.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.progresschecker.logic.commands.ViewCommand.MAX_WEEK_NUMBER; + +import java.io.IOException; +import java.util.function.Predicate; +import java.util.logging.Logger; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.collections.transformation.FilteredList; +import seedu.progresschecker.commons.core.ComponentManager; +import seedu.progresschecker.commons.core.LogsCenter; +import seedu.progresschecker.commons.core.index.Index; +import seedu.progresschecker.commons.events.model.ProgressCheckerChangedEvent; +import seedu.progresschecker.commons.exceptions.IllegalValueException; +import seedu.progresschecker.logic.commands.exceptions.CommandException; +import seedu.progresschecker.model.credentials.GitDetails; +import seedu.progresschecker.model.exercise.Exercise; +import seedu.progresschecker.model.exercise.exceptions.ExerciseNotFoundException; +import seedu.progresschecker.model.issues.Issue; +import seedu.progresschecker.model.person.Person; +import seedu.progresschecker.model.person.exceptions.DuplicatePersonException; +import seedu.progresschecker.model.person.exceptions.PersonNotFoundException; +import seedu.progresschecker.model.photo.PhotoPath; +import seedu.progresschecker.model.photo.exceptions.DuplicatePhotoException; + +/** + * Represents the in-memory model of the ProgressChecker data. + * All changes to any model should be synchronized. + */ +public class ModelManager extends ComponentManager implements Model { + private static final Logger logger = LogsCenter.getLogger(ModelManager.class); + + private final ProgressChecker progressChecker; + private final FilteredList<Person> filteredPersons; + private final FilteredList<Exercise> filteredExercises; + private final FilteredList<Issue> filteredIssues; + /** + * Initializes a ModelManager with the given progressChecker and userPrefs. + */ + public ModelManager(ReadOnlyProgressChecker progressChecker, UserPrefs userPrefs) { + super(); + requireAllNonNull(progressChecker, userPrefs); + + logger.fine("Initializing with ProgressChecker: " + progressChecker + " and user prefs " + userPrefs); + + this.progressChecker = new ProgressChecker(progressChecker); + filteredPersons = new FilteredList<>(this.progressChecker.getPersonList()); + filteredExercises = new FilteredList<>(this.progressChecker.getExerciseList()); + updateFilteredExerciseList(exercise -> exercise.getQuestionIndex().getWeekNumber() == MAX_WEEK_NUMBER); + filteredIssues = new FilteredList<>(this.progressChecker.getIssueList()); + } + + public ModelManager() { + this(new ProgressChecker(), new UserPrefs()); + } + + @Override + public void resetData(ReadOnlyProgressChecker newData) { + progressChecker.resetData(newData); + indicateProgressCheckerChanged(); + } + + @Override + public ReadOnlyProgressChecker getProgressChecker() { + return progressChecker; + } + + /** Raises an event to indicate the model has changed */ + private void indicateProgressCheckerChanged() { + raise(new ProgressCheckerChangedEvent(progressChecker)); + } + + @Override + public synchronized void deletePerson(Person target) throws PersonNotFoundException { + progressChecker.removePerson(target); + indicateProgressCheckerChanged(); + } + + @Override + public synchronized void addPerson(Person person) throws DuplicatePersonException { + progressChecker.addPerson(person); + updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + indicateProgressCheckerChanged(); + } + + //@author adityaa1998 + @Override + public synchronized void loginGithub(GitDetails gitdetails) throws IOException, CommandException { + progressChecker.loginGithub(gitdetails); + indicateProgressCheckerChanged(); + } + @Override + public synchronized void closeIssueOnGithub(Index index) throws IOException, CommandException { + progressChecker.closeIssueOnGithub(index); + indicateProgressCheckerChanged(); + } + + @Override + public synchronized void createIssueOnGitHub(Issue issue) throws IOException, CommandException { + progressChecker.createIssueOnGitHub(issue); + updateFilteredIssueList(PREDICATE_SHOW_ALL_ISSUES); + indicateProgressCheckerChanged(); + } + @Override + public synchronized void logoutGithub() throws CommandException { + progressChecker.logoutGithub(); + indicateProgressCheckerChanged(); + } + + @Override + public synchronized void listIssues(String state) throws IllegalValueException, IOException, CommandException { + progressChecker.listIssues(state); + indicateProgressCheckerChanged(); + } + //@@author + + @Override + public synchronized void sort() { + progressChecker.sort(); + indicateProgressCheckerChanged(); + } + + //@@author adityaa1998 + @Override + public synchronized void reopenIssueOnGithub(Index index) throws IOException, CommandException { + progressChecker.reopenIssueOnGithub(index); + indicateProgressCheckerChanged(); + } + //@@author + + @Override + public void updatePerson(Person target, Person editedPerson) + throws DuplicatePersonException, PersonNotFoundException { + requireAllNonNull(target, editedPerson); + + progressChecker.updatePerson(target, editedPerson); + indicateProgressCheckerChanged(); + } + + //@@author iNekox3 + @Override + public void updateExercise(Exercise target, Exercise editedExercise) + throws ExerciseNotFoundException { + requireAllNonNull(target, editedExercise); + + progressChecker.updateExercise(target, editedExercise); + indicateProgressCheckerChanged(); + } + + //@@author adityaa1998 + @Override + public void updateIssue(Index index, Issue editedIssue) throws IOException, CommandException { + requireAllNonNull(index, editedIssue); + + progressChecker.updateIssue(index, editedIssue); + indicateProgressCheckerChanged(); + } + //@@author + + //=========== Filtered Person List Accessors ============================================================= + + /** + * Returns an unmodifiable view of the list of {@code Person} backed by the internal list of + * {@code progressChecker} + */ + @Override + public ObservableList<Person> getFilteredPersonList() { + return FXCollections.unmodifiableObservableList(filteredPersons); + } + + @Override + public void updateFilteredPersonList(Predicate<Person> predicate) { + requireNonNull(predicate); + filteredPersons.setPredicate(predicate); + } + + //@@author adityaa1998 + //=========== Filtered Issue List Accessors ============================================================= + + /** + * Returns an unmodifiable view of the list of {@code Issue} backed by the internal list of + * {@code progressChecker} + */ + @Override + public ObservableList<Issue> getFilteredIssueList() { + return FXCollections.unmodifiableObservableList(filteredIssues); + } + + @Override + public void updateFilteredIssueList(Predicate<Issue> predicate) { + requireNonNull(predicate); + filteredIssues.setPredicate(predicate); + } + //@@author + + //@@author Livian1107 + @Override + public void uploadPhoto(Person target, String path) + throws PersonNotFoundException, DuplicatePersonException { + progressChecker.uploadPhoto(target, path); + updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS); + indicateProgressCheckerChanged(); + } + + @Override + public void addPhoto(PhotoPath photoPath) throws DuplicatePhotoException { + progressChecker.addPhotoPath(photoPath); + indicateProgressCheckerChanged(); + } + //@@author + + @Override + public boolean equals(Object obj) { + // short circuit if same object + if (obj == this) { + return true; + } + + // instanceof handles nulls + if (!(obj instanceof ModelManager)) { + return false; + } + + // state check + ModelManager other = (ModelManager) obj; + return progressChecker.equals(other.progressChecker) + && filteredPersons.equals(other.filteredPersons); + } + + //@@author iNekox3 + //=========== Filtered Exercise List Accessors ============================================================= + + /** + * Returns an unmodifiable view of the list of {@code Exercise} backed by the internal list of + * {@code progressChecker} + */ + @Override + public ObservableList<Exercise> getFilteredExerciseList() { + return FXCollections.unmodifiableObservableList(filteredExercises); + } + + @Override + public void updateFilteredExerciseList(Predicate<Exercise> predicate) { + requireNonNull(predicate); + filteredExercises.setPredicate(predicate); + } +} diff --git a/src/main/java/seedu/progresschecker/model/ProgressChecker.java b/src/main/java/seedu/progresschecker/model/ProgressChecker.java new file mode 100644 index 000000000000..7ec44894107e --- /dev/null +++ b/src/main/java/seedu/progresschecker/model/ProgressChecker.java @@ -0,0 +1,364 @@ +package seedu.progresschecker.model; + +import static java.util.Objects.requireNonNull; + +import java.io.IOException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import javafx.collections.ObservableList; +import seedu.progresschecker.commons.core.index.Index; +import seedu.progresschecker.commons.exceptions.IllegalValueException; +import seedu.progresschecker.logic.commands.exceptions.CommandException; +import seedu.progresschecker.model.credentials.GitDetails; +import seedu.progresschecker.model.exercise.Exercise; +import seedu.progresschecker.model.exercise.UniqueExerciseList; +import seedu.progresschecker.model.exercise.exceptions.DuplicateExerciseException; +import seedu.progresschecker.model.exercise.exceptions.ExerciseNotFoundException; +import seedu.progresschecker.model.issues.GitIssueList; +import seedu.progresschecker.model.issues.Issue; +import seedu.progresschecker.model.person.Person; +import seedu.progresschecker.model.person.UniquePersonList; +import seedu.progresschecker.model.person.exceptions.DuplicatePersonException; +import seedu.progresschecker.model.person.exceptions.PersonNotFoundException; +import seedu.progresschecker.model.photo.PhotoPath; +import seedu.progresschecker.model.photo.UniquePhotoList; +import seedu.progresschecker.model.photo.exceptions.DuplicatePhotoException; +import seedu.progresschecker.model.tag.Tag; +import seedu.progresschecker.model.tag.UniqueTagList; + +/** + * Wraps all data at the progresschecker + * Duplicates are not allowed (by .equals comparison) + */ +public class ProgressChecker implements ReadOnlyProgressChecker { + + private final UniquePersonList persons; + private final UniquePhotoList photos; + private final UniqueTagList tags; + private final UniqueExerciseList exercises; + private final GitIssueList issues; + + /* + * The 'unusual' code block below is an non-static initialization block, sometimes used to avoid duplication + * between constructors. See https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html + * + * Note that non-static init blocks are not recommended to use. There are other ways to avoid duplication + * among constructors. + */ + { + persons = new UniquePersonList(); + tags = new UniqueTagList(); + photos = new UniquePhotoList(); + exercises = new UniqueExerciseList(); + issues = new GitIssueList(); + } + + public ProgressChecker() {} + + /** + * Creates an ProgressChecker using the Persons and Tags in the {@code toBeCopied} + */ + public ProgressChecker(ReadOnlyProgressChecker toBeCopied) { + this(); + resetData(toBeCopied); + } + + //// list overwrite operations + + public void setPersons(List<Person> persons) throws DuplicatePersonException { + this.persons.setPersons(persons); + } + + public void setTags(Set<Tag> tags) { + this.tags.setTags(tags); + } + + //@@author iNekox3 + public void setExercises(List<Exercise> exercises) throws DuplicateExerciseException { + this.exercises.setExercises(exercises); + } + + //@@author + /** + * Resets the existing data of this {@code ProgressChecker} with {@code newData}. + */ + public void resetData(ReadOnlyProgressChecker newData) { + requireNonNull(newData); + setTags(new HashSet<>(newData.getTagList())); + List<Person> syncedPersonList = newData.getPersonList().stream() + .map(this::syncWithMasterTagList) + .collect(Collectors.toList()); + + try { + setPersons(syncedPersonList); + setExercises(newData.getExerciseList()); + + } catch (DuplicatePersonException e) { + throw new AssertionError("ProgressChecker should not have duplicate persons"); + } catch (DuplicateExerciseException e) { + throw new AssertionError("ProgressChecker should not have duplicate exercises"); + } + } + + //@@author Livian1107 + /** + * Sorts the existing {@code UniquePersonList} of this {@code ProgressChecker} + * with their names in alphabetical order. + */ + public void sort() { + requireNonNull(persons); + persons.sort(); + } + + /** + * Adds a new uploaded photo path to the the list of profile photos + * @param photoPath of a new uploaded photo + * @throws DuplicatePhotoException if there already exists the same photo path + */ + public void addPhotoPath(PhotoPath photoPath) throws DuplicatePhotoException { + photos.add(photoPath); + } + //@@author + + //// person-level operations + + /** + * Adds a person to the ProgressChecker. + * Also checks the new person's tags and updates {@link #tags} with any new tags found, + * and updates the Tag objects in the person to point to those in {@link #tags}. + * + * @throws DuplicatePersonException if an equivalent person already exists. + */ + public void addPerson(Person p) throws DuplicatePersonException { + Person person = syncWithMasterTagList(p); + // TODO: the tags master list will be updated even though the below line fails. + // This can cause the tags master list to have additional tags that are not tagged to any person + // in the person list. + persons.add(person); + } + + /** + * Replaces the given person {@code target} in the list with {@code editedPerson}. + * {@code ProgressChecker}'s tag list will be updated with the tags of {@code editedPerson}. + * + * @throws DuplicatePersonException if updating the person's details causes the person to be equivalent to + * another existing person in the list. + * @throws PersonNotFoundException if {@code target} could not be found in the list. + * + * @see #syncWithMasterTagList(Person) + */ + public void updatePerson(Person target, Person editedPerson) + throws DuplicatePersonException, PersonNotFoundException { + requireNonNull(editedPerson); + + Person syncedEditedPerson = syncWithMasterTagList(editedPerson); + // TODO: the tags master list will be updated even though the below line fails. + // This can cause the tags master list to have additional tags that are not tagged to any person + // in the person list. + persons.setPerson(target, syncedEditedPerson); + } + + //@@author adityaa1998 + + //issue-level operations + + /** + * Login to github + * + * @throws IOException is there is any problem in authentication + * + */ + public void loginGithub(GitDetails gitdetails) throws IOException, CommandException { + issues.initialiseCredentials(gitdetails); + } + + /** + * Logout of github + */ + public void logoutGithub() throws CommandException { + issues.clearCredentials(); + } + + /** + * Creates issue on github + * + * @throws IOException if theres any fault in the input values or the authentication fails due to wrong input + */ + public void createIssueOnGitHub(Issue i) throws IOException, CommandException { + issues.createIssue(i); + } + + /** + * Replaces the given issue at {@code index} from github with {@code editedPerson}. + * reopens an issue on github + * + * @throws IOException if the index mentioned is not valid or he's closed + */ + public void reopenIssueOnGithub(Index index) throws IOException, CommandException { + issues.reopenIssue(index); + } + + /** + * closes an issue on github + * + * @throws IOException if the index mentioned is not valid or he's closed + */ + public void closeIssueOnGithub(Index index) throws IOException, CommandException { + issues.closeIssue(index); + } + + /** + * Replaces the given person {@code target} in the list with {@code editedPerson}. + * {@code ProgressChecker}'s tag list will be updated with the tags of {@code editedPerson}. + * + * @throws IOException if there is any problem in git authentication or parameter + * + */ + public void updateIssue(Index index, Issue editedIssue) throws IOException, CommandException { + requireNonNull(editedIssue); + issues.setIssue(index, editedIssue); + } + + /** + * Lists all the issues of the specified state + */ + public void listIssues(String state) throws IllegalValueException, IOException, CommandException { + requireNonNull(state); + issues.listIssue(state); + } + + //@@author + + /** + * Updates the master tag list to include tags in {@code person} that are not in the list. + * @return a copy of this {@code person} such that every tag in this person points to a Tag object in the master + * list. + */ + private Person syncWithMasterTagList(Person person) { + final UniqueTagList personTags = new UniqueTagList(person.getTags()); + tags.mergeFrom(personTags); + + // Create map with values = tag object references in the master list + // used for checking person tag references + final Map<Tag, Tag> masterTagObjects = new HashMap<>(); + tags.forEach(tag -> masterTagObjects.put(tag, tag)); + + // Rebuild the list of person tags to point to the relevant tags in the master tag list. + final Set<Tag> correctTagReferences = new HashSet<>(); + personTags.forEach(tag -> correctTagReferences.add(masterTagObjects.get(tag))); + return new Person( + person.getName(), person.getPhone(), person.getEmail(), person.getUsername(), person.getMajor(), + person.getYear(), correctTagReferences); + } + + /** + * Removes {@code key} from this {@code ProgressChecker}. + * @throws PersonNotFoundException if the {@code key} is not in this {@code ProgressChecker}. + */ + public boolean removePerson(Person key) throws PersonNotFoundException { + if (persons.remove(key)) { + return true; + } else { + throw new PersonNotFoundException(); + } + } + + //@@author Livian1107 + /** + * Uploads the profile photo path of target person + * @param target + * @param path + * @throws PersonNotFoundException + * @throws DuplicatePersonException + */ + public void uploadPhoto(Person target, String path) throws PersonNotFoundException, DuplicatePersonException { + Person tempPerson = target; + target.updatePhoto(path); + persons.setPerson(tempPerson, target); + } + //@@author + + //// tag-level operations + + public void addTag(Tag t) throws UniqueTagList.DuplicateTagException { + tags.add(t); + } + + //@@author iNekox3 + //// exercise-level operations + + /** + * Adds an exercise to the ProgressChecker. + * + * @throws DuplicateExerciseException if an equivalent exercise already exists. + */ + public void addExercise(Exercise e) throws DuplicateExerciseException { + Exercise exercise = new Exercise( + e.getQuestionIndex(), e.getQuestionType(), e.getQuestion(), + e.getStudentAnswer(), e.getModelAnswer()); + exercises.add(exercise); + } + + /** + * Replaces the given exercise {@code target} in the list with {@code editedExercise}. + * + * @throws ExerciseNotFoundException if {@code target} could not be found in the list. + */ + public void updateExercise(Exercise target, Exercise editedExercise) + throws ExerciseNotFoundException { + requireNonNull(editedExercise); + + exercises.setExercise(target, editedExercise); + } + + //@@author + //// util methods + + @Override + public String toString() { + return persons.asObservableList().size() + " persons, " + tags.asObservableList().size() + " tags"; + // TODO: refine later + } + + @Override + public ObservableList<Person> getPersonList() { + return persons.asObservableList(); + } + + @Override + public ObservableList<Tag> getTagList() { + return tags.asObservableList(); + } + + //@@author iNekox3 + @Override + public ObservableList<Exercise> getExerciseList() { + return exercises.asObservableList(); + } + //@@author + + @Override + public ObservableList<Issue> getIssueList() { + return issues.asObservableList(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof ProgressChecker // instanceof handles nulls + && this.persons.equals(((ProgressChecker) other).persons) + && this.tags.equalsOrderInsensitive(((ProgressChecker) other).tags)); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(persons, tags); + } +} diff --git a/src/main/java/seedu/progresschecker/model/ReadOnlyProgressChecker.java b/src/main/java/seedu/progresschecker/model/ReadOnlyProgressChecker.java new file mode 100644 index 000000000000..bfafdb4ffd0e --- /dev/null +++ b/src/main/java/seedu/progresschecker/model/ReadOnlyProgressChecker.java @@ -0,0 +1,37 @@ +package seedu.progresschecker.model; + +import javafx.collections.ObservableList; +import seedu.progresschecker.model.exercise.Exercise; +import seedu.progresschecker.model.issues.Issue; +import seedu.progresschecker.model.person.Person; +import seedu.progresschecker.model.tag.Tag; + +/** + * Unmodifiable view of an ProgressChecker + */ +public interface ReadOnlyProgressChecker { + + /** + * Returns an unmodifiable view of the persons list. + * This list will not contain any duplicate persons. + */ + ObservableList<Person> getPersonList(); + + /** + * Returns an unmodifiable view of the tags list. + * This list will not contain any duplicate tags. + */ + ObservableList<Tag> getTagList(); + + /** + * Returns an unmodifiable view of the exercises list. + * This list will not contain any duplicate exercises. + */ + ObservableList<Exercise> getExerciseList(); + + /** + * Returns an unmodifiable view of the excercises list. + */ + ObservableList<Issue> getIssueList(); + +} diff --git a/src/main/java/seedu/address/model/UserPrefs.java b/src/main/java/seedu/progresschecker/model/UserPrefs.java similarity index 52% rename from src/main/java/seedu/address/model/UserPrefs.java rename to src/main/java/seedu/progresschecker/model/UserPrefs.java index 8c8a071876eb..c750156d56ff 100644 --- a/src/main/java/seedu/address/model/UserPrefs.java +++ b/src/main/java/seedu/progresschecker/model/UserPrefs.java @@ -1,8 +1,8 @@ -package seedu.address.model; +package seedu.progresschecker.model; import java.util.Objects; -import seedu.address.commons.core.GuiSettings; +import seedu.progresschecker.commons.core.GuiSettings; /** * Represents User's preferences. @@ -10,8 +10,8 @@ public class UserPrefs { private GuiSettings guiSettings; - private String addressBookFilePath = "data/addressbook.xml"; - private String addressBookName = "MyAddressBook"; + private String progressCheckerFilePath = "data/progresschecker.xml"; + private String progressCheckerName = "MyProgressChecker"; public UserPrefs() { this.setGuiSettings(500, 500, 0, 0); @@ -29,20 +29,20 @@ public void setGuiSettings(double width, double height, int x, int y) { guiSettings = new GuiSettings(width, height, x, y); } - public String getAddressBookFilePath() { - return addressBookFilePath; + public String getProgressCheckerFilePath() { + return progressCheckerFilePath; } - public void setAddressBookFilePath(String addressBookFilePath) { - this.addressBookFilePath = addressBookFilePath; + public void setProgressCheckerFilePath(String progressCheckerFilePath) { + this.progressCheckerFilePath = progressCheckerFilePath; } - public String getAddressBookName() { - return addressBookName; + public String getProgressCheckerName() { + return progressCheckerName; } - public void setAddressBookName(String addressBookName) { - this.addressBookName = addressBookName; + public void setProgressCheckerName(String progressCheckerName) { + this.progressCheckerName = progressCheckerName; } @Override @@ -57,21 +57,21 @@ public boolean equals(Object other) { UserPrefs o = (UserPrefs) other; return Objects.equals(guiSettings, o.guiSettings) - && Objects.equals(addressBookFilePath, o.addressBookFilePath) - && Objects.equals(addressBookName, o.addressBookName); + && Objects.equals(progressCheckerFilePath, o.progressCheckerFilePath) + && Objects.equals(progressCheckerName, o.progressCheckerName); } @Override public int hashCode() { - return Objects.hash(guiSettings, addressBookFilePath, addressBookName); + return Objects.hash(guiSettings, progressCheckerFilePath, progressCheckerName); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("Gui Settings : " + guiSettings.toString()); - sb.append("\nLocal data file location : " + addressBookFilePath); - sb.append("\nAddressBook name : " + addressBookName); + sb.append("\nLocal data file location : " + progressCheckerFilePath); + sb.append("\nProgressChecker name : " + progressCheckerName); return sb.toString(); } diff --git a/src/main/java/seedu/progresschecker/model/credentials/GitDetails.java b/src/main/java/seedu/progresschecker/model/credentials/GitDetails.java new file mode 100644 index 000000000000..044bdcdb26b7 --- /dev/null +++ b/src/main/java/seedu/progresschecker/model/credentials/GitDetails.java @@ -0,0 +1,74 @@ +package seedu.progresschecker.model.credentials; + +import static seedu.progresschecker.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Objects; + +//@@author adityaa1998 +/** + * Represents an Issue. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class GitDetails { + + private final Username username; + private final Repository repository; + private final Passcode passcode; + + /** + * Every field must be present and not null. + */ + public GitDetails(Username username, Passcode passcode, Repository repository) { + requireAllNonNull(username, repository, passcode); + this.username = username; + this.repository = repository; + this.passcode = passcode; + } + + public Username getUsername() { + return username; + } + + public Repository getRepository() { + return repository; + } + + public Passcode getPasscode() { + return passcode; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof seedu.progresschecker.model.credentials.GitDetails)) { + return false; + } + + seedu.progresschecker.model.credentials.GitDetails otherGitDetails = + (seedu.progresschecker.model.credentials.GitDetails) other; + return otherGitDetails.getUsername().equals(this.getUsername()) + && otherGitDetails.getRepository().equals(this.getRepository()) + && otherGitDetails.getPasscode().equals(this.getPasscode()); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(username, repository, passcode); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append(" Username: ") + .append(getUsername()) + .append(" Repository: ") + .append(getRepository()); + return builder.toString(); + } + +} + diff --git a/src/main/java/seedu/progresschecker/model/credentials/Passcode.java b/src/main/java/seedu/progresschecker/model/credentials/Passcode.java new file mode 100644 index 000000000000..da4bf538dbcc --- /dev/null +++ b/src/main/java/seedu/progresschecker/model/credentials/Passcode.java @@ -0,0 +1,60 @@ +package seedu.progresschecker.model.credentials; + +import static java.util.Objects.requireNonNull; +import static seedu.progresschecker.commons.util.AppUtil.checkArgument; + +//@@author adityaa1998 +/** + * Represents a github passcode + */ +public class Passcode { + + public static final String MESSAGE_PASSCODE_CONSTRAINTS = + "Passcode must contain atleast one lower case character, one numeral " + + "and should be atleast 7 characters long"; + + /* + * Password must contain one lowercase character, + * one number and minimum 7 characters + */ + public static final String PASSCODE_VALIDATION_REGEX = "((?=.*\\d)(?=.*[a-z]).{7,100})"; + + public final String passcode; + + /** + * Constructs a {@code Passcode}. + * + * @param passcode A valid assignees. + */ + public Passcode(String passcode) { + requireNonNull(passcode); + checkArgument(isValidPasscode(passcode), MESSAGE_PASSCODE_CONSTRAINTS); + this.passcode = passcode; + } + + /** + * Returns true if a given string is a valid github passcode. + */ + public static boolean isValidPasscode(String test) { + return test.matches(PASSCODE_VALIDATION_REGEX); + } + + @Override + public String toString() { + return passcode; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof seedu.progresschecker.model.credentials.Passcode // instanceof handles nulls + && this.passcode.equals(((Passcode) other).passcode)); // state check + } + + @Override + public int hashCode() { + return passcode.hashCode(); + } + +} + diff --git a/src/main/java/seedu/progresschecker/model/credentials/Repository.java b/src/main/java/seedu/progresschecker/model/credentials/Repository.java new file mode 100644 index 000000000000..a892c9bc605d --- /dev/null +++ b/src/main/java/seedu/progresschecker/model/credentials/Repository.java @@ -0,0 +1,59 @@ +package seedu.progresschecker.model.credentials; + +import static java.util.Objects.requireNonNull; +import static seedu.progresschecker.commons.util.AppUtil.checkArgument; + +//@@author aditya1998 +/** + * Represents a github repository + */ +public class Repository { + + public static final String MESSAGE_REPOSITORY_CONSTRAINTS = + "Repository address cannot start from /"; + + /* + * The first character of the repository must not be a forward-slash, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String REPO_VALIDATION_REGEX = "^[-a-zA-Z0-9+&@#/%?=~_|!:,.;*]*[-a-zA-Z0-9+&@#/%=~_|*]"; + + + public final String gitRepo; + + /** + * Constructs a {@code Repository}. + * + * @param gitRepo A valid assignees. + */ + public Repository(String gitRepo) { + requireNonNull(gitRepo); + checkArgument(isValidRepository(gitRepo), MESSAGE_REPOSITORY_CONSTRAINTS); + this.gitRepo = gitRepo; + } + + /** + * Returns true if a given string is a valid github repository. + */ + public static boolean isValidRepository(String test) { + return test.matches(REPO_VALIDATION_REGEX); + } + + @Override + public String toString() { + return gitRepo; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof seedu.progresschecker.model.credentials.Repository // instanceof handles nulls + && this.gitRepo.equals(((Repository) other).gitRepo)); // state check + } + + @Override + public int hashCode() { + return gitRepo.hashCode(); + } + +} diff --git a/src/main/java/seedu/progresschecker/model/credentials/Username.java b/src/main/java/seedu/progresschecker/model/credentials/Username.java new file mode 100644 index 000000000000..234fb4a3c3da --- /dev/null +++ b/src/main/java/seedu/progresschecker/model/credentials/Username.java @@ -0,0 +1,57 @@ +package seedu.progresschecker.model.credentials; + +import static java.util.Objects.requireNonNull; +import static seedu.progresschecker.commons.util.AppUtil.checkArgument; + +//@@author adityaa1998 +/** + * Represents the username of a user on github + */ +public class Username { + public static final String MESSAGE_GITUSERNAME_CONSTRAINTS = + "Username should only contain alphanumeric characters, and it should not be blank"; + + /* + * The github username can only contain alphanumeric characters, + * with no continuous special characters. + */ + public static final String USERNAME_VALIDATION_REGEX = "^[-a-zA-Z0-9+&@#/%?=~_|!:,.;*]*[-a-zA-Z0-9+&@#/%=~_|*]"; + + public final String username; + + /** + * Constructs a {@code Username}. + * + * @param username A valid username. + */ + public Username(String username) { + requireNonNull(username); + checkArgument(isValidUsername(username), MESSAGE_GITUSERNAME_CONSTRAINTS); + this.username = username; + } + + /** + * Returns true if a given string is a valid github issue. + */ + public static boolean isValidUsername(String test) { + return test.matches(USERNAME_VALIDATION_REGEX); + } + + @Override + public String toString() { + return username; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof seedu.progresschecker.model.credentials.Username // instanceof handles nulls + && this.username.equals(((Username) other).username)); // state check + } + + @Override + public int hashCode() { + return username.hashCode(); + } + +} diff --git a/src/main/java/seedu/progresschecker/model/exercise/Exercise.java b/src/main/java/seedu/progresschecker/model/exercise/Exercise.java new file mode 100644 index 000000000000..bcb43b1f080a --- /dev/null +++ b/src/main/java/seedu/progresschecker/model/exercise/Exercise.java @@ -0,0 +1,57 @@ +package seedu.progresschecker.model.exercise; + +import static seedu.progresschecker.commons.util.CollectionUtil.requireAllNonNull; + +//@@author iNekox3 +/** + * Represents an Exercise in the ProgressChecker. + * Guarantees: details are present and not null, field values are validated. + */ +public class Exercise { + + private final QuestionIndex questionIndex; + private final QuestionType questionType; + private final Question question; + private final StudentAnswer studentAnswer; + private final ModelAnswer modelAnswer; + + /** + * Every field must be present and not null. + */ + public Exercise(QuestionIndex questionIndex, QuestionType questionType, Question question, + StudentAnswer studentAnswer, ModelAnswer modelAnswer) { + requireAllNonNull(questionIndex, questionType, question); + this.questionIndex = questionIndex; + this.questionType = questionType; + this.question = question; + this.studentAnswer = studentAnswer; + this.modelAnswer = modelAnswer; + } + + public QuestionIndex getQuestionIndex() { + return questionIndex; + } + + public QuestionType getQuestionType() { + return questionType; + } + + public Question getQuestion() { + return question; + } + + public StudentAnswer getStudentAnswer() { + return studentAnswer; + } + + public ModelAnswer getModelAnswer() { + return modelAnswer; + } + + @Override + public String toString() { + return "Q" + questionIndex + " " + question + "\n\n" + + "Your Answer: " + studentAnswer + "\n\n" + + "Suggested Answer: " + modelAnswer; + } +} diff --git a/src/main/java/seedu/progresschecker/model/exercise/ModelAnswer.java b/src/main/java/seedu/progresschecker/model/exercise/ModelAnswer.java new file mode 100644 index 000000000000..decba2166688 --- /dev/null +++ b/src/main/java/seedu/progresschecker/model/exercise/ModelAnswer.java @@ -0,0 +1,27 @@ +package seedu.progresschecker.model.exercise; + +import static java.util.Objects.requireNonNull; + +//@@author iNekox3 +/** + * Represents an Exercise's model answer in the ProgressChecker. + */ +public class ModelAnswer { + + public final String value; + + /** + * Constructs a {@code ModelAnswer}. + * + * @param answer An answer of any word and character. + */ + public ModelAnswer(String answer) { + requireNonNull(answer); + this.value = answer; + } + + @Override + public String toString() { + return value; + } +} diff --git a/src/main/java/seedu/progresschecker/model/exercise/Question.java b/src/main/java/seedu/progresschecker/model/exercise/Question.java new file mode 100644 index 000000000000..5ae11609f0e0 --- /dev/null +++ b/src/main/java/seedu/progresschecker/model/exercise/Question.java @@ -0,0 +1,27 @@ +package seedu.progresschecker.model.exercise; + +import static java.util.Objects.requireNonNull; + +//@@author iNekox3 +/** + * Represents an Exercise's question in the ProgressChecker. + */ +public class Question { + + public final String value; + + /** + * Constructs a {@code Question}. + * + * @param question A question of any word and character. + */ + public Question(String question) { + requireNonNull(question); + this.value = question; + } + + @Override + public String toString() { + return value; + } +} diff --git a/src/main/java/seedu/progresschecker/model/exercise/QuestionIndex.java b/src/main/java/seedu/progresschecker/model/exercise/QuestionIndex.java new file mode 100644 index 000000000000..f0ec9a4de673 --- /dev/null +++ b/src/main/java/seedu/progresschecker/model/exercise/QuestionIndex.java @@ -0,0 +1,51 @@ +package seedu.progresschecker.model.exercise; + +import static java.util.Objects.requireNonNull; +import static seedu.progresschecker.commons.util.AppUtil.checkArgument; + +import java.util.regex.Pattern; + +//@@author iNekox3 +/** + * Represents an Exercise's question index in the ProgressChecker. + */ +public class QuestionIndex { + + public static final int WEEK_NUMBER_INDEX = 0; + + public static final String MESSAGE_INDEX_CONSTRAINTS = + "Indices can only contain numbers, and should be in the format of " + + "SECTION NUMBER.PART NUMBER.QUESTION NUMBER"; + public static final String INDEX_VALIDATION_REGEX = "([2-9]|1[0-3])\\.([0-9]|[0-9]{2})\\.([0-9]|[0-9]{2})"; + public final String value; + + /** + * Constructs a {@code QuestionIndex}. + * + * @param index A valid index number. + */ + public QuestionIndex(String index) { + requireNonNull(index); + checkArgument(isValidIndex(index), MESSAGE_INDEX_CONSTRAINTS); + this.value = index; + } + + /** + * Returns true if a given string is a valid index number. + */ + public static boolean isValidIndex(String test) { + return test.matches(INDEX_VALIDATION_REGEX); + } + + /** + * Returns the question number in the whole question index. + */ + public int getWeekNumber() { + return Integer.parseInt(value.split(Pattern.quote("."))[WEEK_NUMBER_INDEX]); + } + + @Override + public String toString() { + return value; + } +} diff --git a/src/main/java/seedu/progresschecker/model/exercise/QuestionType.java b/src/main/java/seedu/progresschecker/model/exercise/QuestionType.java new file mode 100644 index 000000000000..c21d8ebd230e --- /dev/null +++ b/src/main/java/seedu/progresschecker/model/exercise/QuestionType.java @@ -0,0 +1,39 @@ +package seedu.progresschecker.model.exercise; + +import static java.util.Objects.requireNonNull; +import static seedu.progresschecker.commons.util.AppUtil.checkArgument; + +//@@author iNekox3 +/** + * Represents an Exercise's question type in the ProgressChecker. + */ +public class QuestionType { + + public static final String MESSAGE_TYPE_CONSTRAINTS = + "Type can only be 'text' or 'choice'"; + public static final String TYPE_VALIDATION_REGEX = "text|choice"; + public final String value; + + /** + * Constructs a {@code QuestionType}. + * + * @param type A valid type. + */ + public QuestionType(String type) { + requireNonNull(type); + checkArgument(isValidType(type), MESSAGE_TYPE_CONSTRAINTS); + this.value = type; + } + + /** + * Returns true if a given string is a valid type. + */ + public static boolean isValidType(String test) { + return test.matches(TYPE_VALIDATION_REGEX); + } + + @Override + public String toString() { + return value; + } +} diff --git a/src/main/java/seedu/progresschecker/model/exercise/StudentAnswer.java b/src/main/java/seedu/progresschecker/model/exercise/StudentAnswer.java new file mode 100644 index 000000000000..8bdb4f057211 --- /dev/null +++ b/src/main/java/seedu/progresschecker/model/exercise/StudentAnswer.java @@ -0,0 +1,27 @@ +package seedu.progresschecker.model.exercise; + +import static java.util.Objects.requireNonNull; + +//@@author iNekox3 +/** + * Represents an Exercise's student answer in the ProgressChecker. + */ +public class StudentAnswer { + + public final String value; + + /** + * Constructs a {@code StudentAnswer}. + * + * @param answer An answer of any word and character. + */ + public StudentAnswer(String answer) { + requireNonNull(answer); + this.value = answer; + } + + @Override + public String toString() { + return value; + } +} diff --git a/src/main/java/seedu/progresschecker/model/exercise/UniqueExerciseList.java b/src/main/java/seedu/progresschecker/model/exercise/UniqueExerciseList.java new file mode 100644 index 000000000000..e02df46cf4c7 --- /dev/null +++ b/src/main/java/seedu/progresschecker/model/exercise/UniqueExerciseList.java @@ -0,0 +1,98 @@ +package seedu.progresschecker.model.exercise; + +import static java.util.Objects.requireNonNull; +import static seedu.progresschecker.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.progresschecker.commons.util.CollectionUtil; +import seedu.progresschecker.model.exercise.exceptions.DuplicateExerciseException; +import seedu.progresschecker.model.exercise.exceptions.ExerciseNotFoundException; + +//@@author iNekox3 +/** + * A list of exercises that enforces uniqueness between its elements and does not allow nulls. + * + * Supports a minimal set of list operations. + * + * @see Exercise#equals(Object) + * @see CollectionUtil#elementsAreUnique(Collection) + */ +public class UniqueExerciseList implements Iterable<Exercise> { + + private final ObservableList<Exercise> internalList = FXCollections.observableArrayList(); + + /** + * Returns true if the list contains an equivalent exercise as the given argument. + */ + public boolean contains(Exercise toCheck) { + requireNonNull(toCheck); + return internalList.contains(toCheck); + } + + /** + * Adds an exercise to the list. + * + * @throws DuplicateExerciseException if the exercise to add is a duplicate of an existing exercise in the list. + */ + public void add(Exercise toAdd) throws DuplicateExerciseException { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicateExerciseException(); + } + internalList.add(toAdd); + } + + /** + * Replaces the exercise {@code target} in the list with {@code editedExercise}. + * + * @throws ExerciseNotFoundException if {@code target} could not be found in the list. + */ + public void setExercise(Exercise target, Exercise editedExercise) + throws ExerciseNotFoundException { + requireNonNull(editedExercise); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new ExerciseNotFoundException(); + } + + internalList.set(index, editedExercise); + } + + public void setExercises(UniqueExerciseList replacement) { + this.internalList.setAll(replacement.internalList); + } + + public void setExercises(List<Exercise> exercises) throws DuplicateExerciseException { + requireAllNonNull(exercises); + final UniqueExerciseList replacement = new UniqueExerciseList(); + for (final Exercise exercise : exercises) { + replacement.add(exercise); + } + setExercises(replacement); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList<Exercise> asObservableList() { + return FXCollections.unmodifiableObservableList(internalList); + } + + @Override + public Iterator<Exercise> iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniqueExerciseList // instanceof handles nulls + && this.internalList.equals(((UniqueExerciseList) other).internalList)); + } +} diff --git a/src/main/java/seedu/progresschecker/model/exercise/exceptions/DuplicateExerciseException.java b/src/main/java/seedu/progresschecker/model/exercise/exceptions/DuplicateExerciseException.java new file mode 100644 index 000000000000..4e2ed0f65c10 --- /dev/null +++ b/src/main/java/seedu/progresschecker/model/exercise/exceptions/DuplicateExerciseException.java @@ -0,0 +1,13 @@ +package seedu.progresschecker.model.exercise.exceptions; + +import seedu.progresschecker.commons.exceptions.DuplicateDataException; + +//@@author iNekox3 +/** + * Signals that the operation will result in duplicate Exercise objects. + */ +public class DuplicateExerciseException extends DuplicateDataException { + public DuplicateExerciseException() { + super("Operation would result in duplicate exercises"); + } +} diff --git a/src/main/java/seedu/progresschecker/model/exercise/exceptions/ExerciseNotFoundException.java b/src/main/java/seedu/progresschecker/model/exercise/exceptions/ExerciseNotFoundException.java new file mode 100644 index 000000000000..47f78069219f --- /dev/null +++ b/src/main/java/seedu/progresschecker/model/exercise/exceptions/ExerciseNotFoundException.java @@ -0,0 +1,7 @@ +package seedu.progresschecker.model.exercise.exceptions; + +//@@author iNekox3 +/** + * Signals that the operation is unable to find the specified exercise. + */ +public class ExerciseNotFoundException extends Exception {} diff --git a/src/main/java/seedu/progresschecker/model/issues/Assignees.java b/src/main/java/seedu/progresschecker/model/issues/Assignees.java new file mode 100644 index 000000000000..ea1eb64abe03 --- /dev/null +++ b/src/main/java/seedu/progresschecker/model/issues/Assignees.java @@ -0,0 +1,59 @@ +package seedu.progresschecker.model.issues; + +import static java.util.Objects.requireNonNull; +import static seedu.progresschecker.commons.util.AppUtil.checkArgument; + +//@@author adityaa1998 +/** + * Represents all the assignees to an issue + */ +public class Assignees { + + public static final String MESSAGE_ASSIGNEES_CONSTRAINTS = + "Assignees of the issue can be anything, but should not be blank space"; + + /* + * The first character of the Assignee must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String ASSIGNEE_VALIDATION_REGEX = ".*\\w.*|[$&+,:;=?@#|'<>.^*()%!-]"; + + public final String fullAssignees; + + /** + * Constructs a {@code Assignees}. + * + * @param assignees A valid assignees. + */ + public Assignees(String assignees) { + requireNonNull(assignees); + checkArgument(isValidAssignee(assignees), MESSAGE_ASSIGNEES_CONSTRAINTS); + this.fullAssignees = assignees; + } + + /** + * Returns true if a given string is a valid github issue. + */ + public static boolean isValidAssignee(String test) { + return test.matches(ASSIGNEE_VALIDATION_REGEX); + } + + + @Override + public String toString() { + return fullAssignees; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof seedu.progresschecker.model.issues.Assignees // instanceof handles nulls + && this.fullAssignees.equals(((Assignees) other).fullAssignees)); // state check + } + + @Override + public int hashCode() { + return fullAssignees.hashCode(); + } + +} diff --git a/src/main/java/seedu/progresschecker/model/issues/Body.java b/src/main/java/seedu/progresschecker/model/issues/Body.java new file mode 100644 index 000000000000..775a2a47027a --- /dev/null +++ b/src/main/java/seedu/progresschecker/model/issues/Body.java @@ -0,0 +1,50 @@ +package seedu.progresschecker.model.issues; + +import static java.util.Objects.requireNonNull; + +//@@author adityaa1998 +/** + * Represents an issue's name and description + */ +public class Body { + + public static final String MESSAGE_BODY_CONSTRAINTS = + "Issue should only contain non-null body"; + + public final String fullBody; + + /** + * Constructs a {@code Body}. + * + * @param body A valid issue description. + */ + public Body(String body) { + requireNonNull(body); + this.fullBody = body; + } + + /** + * Returns true if a given string is a valid github body. + */ + public static boolean isValidBody(String test) { + return (test != null) ? true : false; + } + + @Override + public String toString() { + return fullBody; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof seedu.progresschecker.model.issues.Body // instanceof handles nulls + && this.fullBody.equals(((Body) other).fullBody)); // state check + } + + @Override + public int hashCode() { + return fullBody.hashCode(); + } + +} diff --git a/src/main/java/seedu/progresschecker/model/issues/GitIssueList.java b/src/main/java/seedu/progresschecker/model/issues/GitIssueList.java new file mode 100644 index 000000000000..9bd7d7cc0148 --- /dev/null +++ b/src/main/java/seedu/progresschecker/model/issues/GitIssueList.java @@ -0,0 +1,321 @@ +package seedu.progresschecker.model.issues; + +import static java.util.Objects.requireNonNull; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; + +import org.kohsuke.github.GHIssue; +import org.kohsuke.github.GHIssueBuilder; +import org.kohsuke.github.GHIssueState; +import org.kohsuke.github.GHLabel; +import org.kohsuke.github.GHMilestone; +import org.kohsuke.github.GHRepository; +import org.kohsuke.github.GHUser; +import org.kohsuke.github.GitHub; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.progresschecker.commons.core.index.Index; +import seedu.progresschecker.commons.exceptions.IllegalValueException; +import seedu.progresschecker.commons.util.CollectionUtil; +import seedu.progresschecker.logic.commands.exceptions.CommandException; +import seedu.progresschecker.model.credentials.GitDetails; +import seedu.progresschecker.model.person.Person; + +//@@author adityaa1998 +/** + * A list of persons that enforces uniqueness between its elements and does not allow nulls. + * + * Supports a minimal set of list operations. + * + * @see Person#equals(Object) + * @see CollectionUtil#elementsAreUnique(Collection) + */ +public class GitIssueList implements Iterable<Issue> { + + private final ObservableList<Issue> internalList = FXCollections.observableArrayList(); + private String repoName; + private String userLogin; + private String userAuthentication; + private GitHub github; + private GHRepository repository; + private GHIssueBuilder issueBuilder; + private GHIssue issue; + private GHIssue toEdit; + private GHIssueState issueState; + + /** + * Initialises github credentials + */ + public void initialiseCredentials(GitDetails gitdetails) throws CommandException, IOException { + repoName = gitdetails.getRepository().toString(); + userLogin = gitdetails.getUsername().toString(); + userAuthentication = gitdetails.getPasscode().toString(); + authoriseGithub(); + } + + /** + * Authorises with github + */ + private void authoriseGithub () throws CommandException, IOException { + if (github != null) { + throw new CommandException("You have already logged in as " + userLogin + ". Please logout first."); + } + try { + github = GitHub.connectUsingPassword(userLogin, userAuthentication); + if (!github.isCredentialValid()) { + github = null; + throw new IOException(); + } + } catch (IOException ie) { + throw new CommandException("Enter correct username and password"); + } + try { + repository = github.getRepository(repoName); + } catch (IOException ie) { + throw new CommandException("Enter correct repository name"); + } + updateInternalList(); + } + + /** + * Updates the internal list by fetching data from github + */ + private void updateInternalList() throws IOException { + internalList.remove(0, internalList.size()); + List<GHIssue> gitIssues = repository.getIssues(issueState); + for (GHIssue issueOnGit : gitIssues) { + Issue toBeAdded = convertToIssue(issueOnGit); + internalList.add(toBeAdded); + } + } + + /** + * Converts GHIssue to issue + */ + private Issue convertToIssue(GHIssue i) throws IOException { + + List<GHUser> gitAssigneeList = i.getAssignees(); + ArrayList<GHLabel> gitLabelsList = new ArrayList<>(i.getLabels()); + List<Assignees> assigneesList = new ArrayList<>(); + List<Labels> labelsList = new ArrayList<>(); + Milestone existingMilestone = null; + Body existingBody = new Body(i.getBody()); + Title title = new Title(i.getTitle()); + Issue issue; + + if (i.getMilestone() == null) { + existingMilestone = new Milestone(""); + } else { + existingMilestone = new Milestone(i.getMilestone().getTitle()); + } + + for (GHUser assignee : gitAssigneeList) { + assigneesList.add(new Assignees(assignee.getLogin())); + } + + for (GHLabel label : gitLabelsList) { + labelsList.add(new Labels(label.getName())); + } + + issue = new Issue(title, assigneesList, existingMilestone, + existingBody, labelsList); + + issue.setIssueIndex(i.getNumber()); + + return issue; + } + + /** + * Creates an issue on github + * + * @throws IOException if there is any problem creating an issue on github; + */ + public void createIssue(Issue toAdd) throws IOException, CommandException { + checkGitAuthentication(); + issueBuilder = repository.createIssue(toAdd.getTitle().toString()); + issueBuilder.body(toAdd.getBody().toString()); + + List<Assignees> assigneesList = toAdd.getAssignees(); + List<Labels> labelsList = toAdd.getLabelsList(); + + ArrayList<GHUser> listOfUsers = new ArrayList<>(); + ArrayList<String> listOfLabels = new ArrayList<>(); + MilestoneMap obj = new MilestoneMap(); + obj.setRepository(getRepository()); + HashMap<String, GHMilestone> milestoneMap = obj.getMilestoneMap(); + GHMilestone check = null; + + for (int ct = 0; ct < assigneesList.size(); ct++) { + listOfUsers.add(github.getUser(assigneesList.get(ct).toString())); + } + + for (int ct = 0; ct < labelsList.size(); ct++) { + listOfLabels.add(labelsList.get(ct).toString()); + } + + if (toAdd.getMilestone() != null) { + if (milestoneMap.get(toAdd.getMilestone().toString()) == null) { + throw new CommandException("Milestone doesn't exist"); + } else { + check = milestoneMap.get(toAdd.getMilestone().toString()); + } + } + GHIssue createdIssue = issueBuilder.create(); + if (check != null) { + createdIssue.setMilestone(check); + } + createdIssue.setAssignees(listOfUsers); + createdIssue.setLabels(listOfLabels.toArray(new String[0])); + updateInternalList(); + } + + /** + * Reopens an issue on github + */ + public void reopenIssue(Index index) throws IOException, CommandException { + checkGitAuthentication(); + issue = repository.getIssue(index.getOneBased()); + if (issue.getState() == GHIssueState.OPEN) { + throw new CommandException("Issue #" + index.getOneBased() + " is already open"); + } + issue.reopen(); + updateInternalList(); + } + + /** + * Closes an issue on github + */ + public void closeIssue(Index index) throws IOException, CommandException { + + checkGitAuthentication(); + issue = repository.getIssue(index.getOneBased()); + if (issue.getState() == GHIssueState.CLOSED) { + throw new CommandException("Issue #" + index.getOneBased() + " is already closed"); + } + issue.close(); + updateInternalList(); + } + /** + * Authorises with github + */ + public void clearCredentials() throws CommandException { + if (github == null) { + throw new CommandException("No one has logged into github at the moment"); + } else { + internalList.remove(0, internalList.size()); + github = null; + } + } + + /** + * Check if the github credentials are authorised + */ + private void checkGitAuthentication() throws CommandException { + if (github == null) { + throw new CommandException("Github not authenticated. " + + "Use 'gitlogin' command to first authenticate your github account"); + } + } + + /** + * Updates the GHIssueState according to mentioned state and updates the list + */ + public void listIssue(String state) throws IllegalValueException, IOException, CommandException { + if (github == null) { + throw new CommandException(""); + } else if (state.equalsIgnoreCase("OPEN")) { + issueState = GHIssueState.OPEN; + } else if (state.equalsIgnoreCase("CLOSED")) { + issueState = GHIssueState.CLOSED; + } else { + throw new IllegalValueException("Enter correct state"); + } + updateInternalList(); + + } + /** + * Replaces the person {@code target} in the list with {@code editedPerson}. + * + * @throws IOException if the replacement is equivalent to another existing person in the list. + */ + public void setIssue(Index index, Issue editedIssue) + throws IOException, CommandException { + requireNonNull(editedIssue); + toEdit = repository.getIssue(index.getOneBased()); + + List<Assignees> assigneesList = editedIssue.getAssignees(); + List<Labels> labelsList = editedIssue.getLabelsList(); + + ArrayList<GHUser> listOfUsers = new ArrayList<>(); + ArrayList<String> listOfLabels = new ArrayList<>(); + MilestoneMap obj = new MilestoneMap(); + obj.setRepository(getRepository()); + HashMap<String, GHMilestone> milestoneMap = obj.getMilestoneMap(); + + for (Assignees assignee : assigneesList) { + listOfUsers.add(github.getUser(assignee.toString())); + } + + for (Labels label : labelsList) { + listOfLabels.add(label.toString()); + } + + if (editedIssue.getMilestone() != null) { + GHMilestone check = milestoneMap.get(editedIssue.getMilestone().toString()); + toEdit.setMilestone(check); + } + toEdit.setTitle(editedIssue.getTitle().toString()); + toEdit.setBody(editedIssue.getBody().toString()); + toEdit.setAssignees(listOfUsers); + if (listOfLabels.size() != 0) { + toEdit.setLabels(listOfLabels.toArray(new String[0])); + } + updateInternalList(); + + } + + /** + * Returns github object + */ + public GitHub getGithub() { + return github; + } + + /** + * Returns github repository + */ + public GHRepository getRepository() { + return repository; + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList<Issue> asObservableList() { + return FXCollections.unmodifiableObservableList(internalList); + } + + @Override + public Iterator<Issue> iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof GitIssueList // instanceof handles nulls + && this.internalList.equals(((GitIssueList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } +} + diff --git a/src/main/java/seedu/progresschecker/model/issues/Issue.java b/src/main/java/seedu/progresschecker/model/issues/Issue.java new file mode 100644 index 000000000000..341067bf820d --- /dev/null +++ b/src/main/java/seedu/progresschecker/model/issues/Issue.java @@ -0,0 +1,102 @@ +package seedu.progresschecker.model.issues; + +import static seedu.progresschecker.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.List; +import java.util.Objects; + +//@@author adityaa1998 +/** + * Represents an Issue. + * Guarantees: details are present and not null, field values are validated, immutable. + */ +public class Issue { + + private final Title title; + private final List<Assignees> assigneesList; + private final Milestone milestone; + private final Body body; + private final List<Labels> labelsList; + private int issueIndex; + + /** + * Every field must be present and not null. + */ + public Issue(Title title, List<Assignees> assigneesList, Milestone milestone, Body body, List<Labels> labelsList) { + requireAllNonNull(title); + this.title = title; + this.assigneesList = assigneesList; + this.milestone = milestone; + this.body = body; + this.labelsList = labelsList; + } + + public Title getTitle() { + return title; + } + + public List<Assignees> getAssignees() { + return assigneesList; + } + + public Milestone getMilestone() { + return milestone; + } + + public Body getBody() { + return body; + } + + public List<Labels> getLabelsList() { + return labelsList; + } + + public int getIssueIndex() { + return issueIndex; + } + + public void setIssueIndex(int issueIndex) { + this.issueIndex = issueIndex; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof seedu.progresschecker.model.issues.Issue)) { + return false; + } + + seedu.progresschecker.model.issues.Issue otherIssue = (seedu.progresschecker.model.issues.Issue) other; + return otherIssue.getTitle().equals(this.getTitle()) + && otherIssue.getAssignees().equals(this.getAssignees()) + && otherIssue.getMilestone().equals(this.getMilestone()) + && otherIssue.getBody().equals(this.getBody()) + && otherIssue.getLabelsList().equals(this.getLabelsList()); + } + + @Override + public int hashCode() { + // use this method for custom fields hashing instead of implementing your own + return Objects.hash(title, assigneesList, milestone, body, labelsList); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append(getTitle()) + .append(" Assignees: ") + .append(getAssignees()) + .append(" Milestone: ") + .append(getMilestone()) + .append(" Body: ") + .append(getBody()) + .append(" Labels: ") + .append(getLabelsList()); + return builder.toString(); + } + +} + diff --git a/src/main/java/seedu/progresschecker/model/issues/Labels.java b/src/main/java/seedu/progresschecker/model/issues/Labels.java new file mode 100644 index 000000000000..b08855f5e072 --- /dev/null +++ b/src/main/java/seedu/progresschecker/model/issues/Labels.java @@ -0,0 +1,58 @@ +package seedu.progresschecker.model.issues; + +import static java.util.Objects.requireNonNull; +import static seedu.progresschecker.commons.util.AppUtil.checkArgument; + +//@@author adityaa1998 +/** + * Represents all the Labels of an issue + */ +public class Labels { + + public static final String MESSAGE_LABEL_CONSTRAINTS = + "Labels should only contain alphanumeric characters and spaces, and it should not be blank"; + + /* + * The first character of the label must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String LABEL_VALIDATION_REGEX = ".*\\w.*|[$&+,:;=?@#|'<>.^*()%!-]"; + + public final String fullLabels; + + /** + * Constructs a {@code Labels}. + * + * @param labels valid labels. + */ + public Labels(String labels) { + requireNonNull(labels); + checkArgument(isValidLabel(labels), MESSAGE_LABEL_CONSTRAINTS); + this.fullLabels = labels; + } + + /** + * Returns true if a given string is a valid github label. + */ + public static boolean isValidLabel(String test) { + return test.matches(LABEL_VALIDATION_REGEX); + } + + @Override + public String toString() { + return fullLabels; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof seedu.progresschecker.model.issues.Labels // instanceof handles nulls + && this.fullLabels.equals(((Labels) other).fullLabels)); // state check + } + + @Override + public int hashCode() { + return fullLabels.hashCode(); + } + +} diff --git a/src/main/java/seedu/progresschecker/model/issues/Milestone.java b/src/main/java/seedu/progresschecker/model/issues/Milestone.java new file mode 100644 index 000000000000..299ebd886c47 --- /dev/null +++ b/src/main/java/seedu/progresschecker/model/issues/Milestone.java @@ -0,0 +1,38 @@ +package seedu.progresschecker.model.issues; + +//@@author adityaa1998 +/** + * Represents a milestone for an issue + */ +public class Milestone { + + public final String fullMilestone; + + /** + * Constructs a {@code Milestone}. + * + * @param milestone A valid milestone. + */ + public Milestone(String milestone) { + //requireNonNull(milestone); + this.fullMilestone = milestone; + } + + @Override + public String toString() { + return fullMilestone; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof seedu.progresschecker.model.issues.Milestone // instanceof handles nulls + && this.fullMilestone.equals(((Milestone) other).fullMilestone)); // state check + } + + @Override + public int hashCode() { + return fullMilestone.hashCode(); + } + +} diff --git a/src/main/java/seedu/progresschecker/model/issues/MilestoneMap.java b/src/main/java/seedu/progresschecker/model/issues/MilestoneMap.java new file mode 100644 index 000000000000..419f7b71a6bf --- /dev/null +++ b/src/main/java/seedu/progresschecker/model/issues/MilestoneMap.java @@ -0,0 +1,44 @@ +package seedu.progresschecker.model.issues; + +import java.util.HashMap; +import java.util.List; + +import org.kohsuke.github.GHIssueState; +import org.kohsuke.github.GHMilestone; +import org.kohsuke.github.GHRepository; + +import seedu.progresschecker.logic.commands.exceptions.CommandException; + +//@@author adityaa1998 +/** + * Initialises and returns a Hashmap of milestones + */ +public final class MilestoneMap { + + private static HashMap<String, GHMilestone> milestoneMap; + + private GHRepository repository; + + /** + * Returns a hashmap of milestones + */ + public HashMap<String, GHMilestone> getMilestoneMap() throws CommandException { + milestoneMap = new HashMap<>(); + createMilestoneHashMap(); + return milestoneMap; + } + + /** + * creates a map with the milestone values + */ + private void createMilestoneHashMap() { + List<GHMilestone> milestones = repository.listMilestones(GHIssueState.ALL).asList(); + for (int i = 0; i < milestones.size(); i++) { + milestoneMap.put(milestones.get(i).getTitle(), milestones.get(i)); + } + } + + public void setRepository(GHRepository repo) { + repository = repo; + } +} diff --git a/src/main/java/seedu/progresschecker/model/issues/Title.java b/src/main/java/seedu/progresschecker/model/issues/Title.java new file mode 100644 index 000000000000..147af557c17f --- /dev/null +++ b/src/main/java/seedu/progresschecker/model/issues/Title.java @@ -0,0 +1,59 @@ +package seedu.progresschecker.model.issues; + +//@@author adityaa1998 + +import static java.util.Objects.requireNonNull; +import static seedu.progresschecker.commons.util.AppUtil.checkArgument; + +/** + * Represents an issue's name and description + */ +public class Title { + + public static final String MESSAGE_TITLE_CONSTRAINTS = + "Issue should only contain alphanumeric characters and spaces, and it should not be blank"; + + /* + * The first character of the title must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String TITLE_VALIDATION_REGEX = ".*\\w.*|[$&+,:;=?@#|'<>.^*()%!-]"; + + public final String fullMessage; + + /** + * Constructs a {@code Title}. + * + * @param title A valid description. + */ + public Title(String title) { + requireNonNull(title); + checkArgument(isValidTitle(title), MESSAGE_TITLE_CONSTRAINTS); + this.fullMessage = title; + } + + /** + * Returns true if a given string is a valid github issue. + */ + public static boolean isValidTitle(String test) { + return test.matches(TITLE_VALIDATION_REGEX); + } + + @Override + public String toString() { + return fullMessage; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof seedu.progresschecker.model.issues.Title // instanceof handles nulls + && this.fullMessage.equals(((Title) other).fullMessage)); // state check + } + + @Override + public int hashCode() { + return fullMessage.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/person/Email.java b/src/main/java/seedu/progresschecker/model/person/Email.java similarity index 91% rename from src/main/java/seedu/address/model/person/Email.java rename to src/main/java/seedu/progresschecker/model/person/Email.java index 3759a577ec59..771279618c53 100644 --- a/src/main/java/seedu/address/model/person/Email.java +++ b/src/main/java/seedu/progresschecker/model/person/Email.java @@ -1,10 +1,10 @@ -package seedu.address.model.person; +package seedu.progresschecker.model.person; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; +import static seedu.progresschecker.commons.util.AppUtil.checkArgument; /** - * Represents a Person's email in the address book. + * Represents a Person's email in the ProgressChecker. * Guarantees: immutable; is valid as declared in {@link #isValidEmail(String)} */ public class Email { @@ -32,7 +32,7 @@ public class Email { /** * Constructs an {@code Email}. * - * @param email A valid email address. + * @param email A valid email progresschecker. */ public Email(String email) { requireNonNull(email); diff --git a/src/main/java/seedu/progresschecker/model/person/GithubUsername.java b/src/main/java/seedu/progresschecker/model/person/GithubUsername.java new file mode 100644 index 000000000000..dbced0f9e7f1 --- /dev/null +++ b/src/main/java/seedu/progresschecker/model/person/GithubUsername.java @@ -0,0 +1,61 @@ +package seedu.progresschecker.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.progresschecker.commons.util.AppUtil.checkArgument; + +//@@author EdwardKSG +/** + * Represents a Person's Github username in the ProgressChecker. + * Guarantees: immutable; is valid as declared in {@link #isValidUsername(String)} + */ +public class GithubUsername { + + public static final String MESSAGE_USERNAME_CONSTRAINTS = + "Person Github usernames should only contain alphanumeric characters and spaces, " + + "and it should not be blank"; + + /* + * The first character of the username must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String USERNAME_VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; + + public final String username; + + /** + * Constructs a {@code GithubUsername}. + * + * @param username A valid username. + */ + public GithubUsername(String username) { + requireNonNull(username); + checkArgument(isValidUsername(username), MESSAGE_USERNAME_CONSTRAINTS); + this.username = username; + } + + /** + * Returns true if a given string is a valid Github username. + */ + public static boolean isValidUsername(String test) { + return test.matches(USERNAME_VALIDATION_REGEX); + } + + + @Override + public String toString() { + return username; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof GithubUsername // instanceof handles nulls + && this.username.equals(((GithubUsername) other).username)); // state check + } + + @Override + public int hashCode() { + return username.hashCode(); + } + +} diff --git a/src/main/java/seedu/progresschecker/model/person/Major.java b/src/main/java/seedu/progresschecker/model/person/Major.java new file mode 100644 index 000000000000..2457772810d8 --- /dev/null +++ b/src/main/java/seedu/progresschecker/model/person/Major.java @@ -0,0 +1,59 @@ +package seedu.progresschecker.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.progresschecker.commons.util.AppUtil.checkArgument; + +//@@author EdwardKSG +/** + * Represents a Person's major in the ProgressChecker. + * Guarantees: immutable; is valid as declared in {@link #isValidMajor(String)} + */ +public class Major { + + public static final String MESSAGE_MAJOR_CONSTRAINTS = + "Person majors can take any values, and it should not be blank"; + + /* + * The first character of the major must not be a whitespace, + * otherwise " " (a blank string) becomes a valid input. + */ + public static final String MAJOR_VALIDATION_REGEX = "[^\\s].*"; + + public final String value; + + /** + * Constructs an {@code Major}. + * + * @param major A valid major. + */ + public Major(String major) { + requireNonNull(major); + checkArgument(isValidMajor(major), MESSAGE_MAJOR_CONSTRAINTS); + this.value = major; + } + + /** + * Returns true if a given string is a valid person major. + */ + public static boolean isValidMajor(String test) { + return test.matches(MAJOR_VALIDATION_REGEX); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Major // instanceof handles nulls + && this.value.equals(((Major) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/person/Name.java b/src/main/java/seedu/progresschecker/model/person/Name.java similarity index 85% rename from src/main/java/seedu/address/model/person/Name.java rename to src/main/java/seedu/progresschecker/model/person/Name.java index 8e632943c4cf..ed93c0cb53a2 100644 --- a/src/main/java/seedu/address/model/person/Name.java +++ b/src/main/java/seedu/progresschecker/model/person/Name.java @@ -1,10 +1,10 @@ -package seedu.address.model.person; +package seedu.progresschecker.model.person; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; +import static seedu.progresschecker.commons.util.AppUtil.checkArgument; /** - * Represents a Person's name in the address book. + * Represents a Person's name in the ProgressChecker. * Guarantees: immutable; is valid as declared in {@link #isValidName(String)} */ public class Name { @@ -13,7 +13,7 @@ public class Name { "Person names should only contain alphanumeric characters and spaces, and it should not be blank"; /* - * The first character of the address must not be a whitespace, + * The first character of the name must not be a whitespace, * otherwise " " (a blank string) becomes a valid input. */ public static final String NAME_VALIDATION_REGEX = "[\\p{Alnum}][\\p{Alnum} ]*"; diff --git a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java b/src/main/java/seedu/progresschecker/model/person/NameContainsKeywordsPredicate.java similarity index 90% rename from src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java rename to src/main/java/seedu/progresschecker/model/person/NameContainsKeywordsPredicate.java index 827e2cc106bd..df81b00464a8 100644 --- a/src/main/java/seedu/address/model/person/NameContainsKeywordsPredicate.java +++ b/src/main/java/seedu/progresschecker/model/person/NameContainsKeywordsPredicate.java @@ -1,9 +1,9 @@ -package seedu.address.model.person; +package seedu.progresschecker.model.person; import java.util.List; import java.util.function.Predicate; -import seedu.address.commons.util.StringUtil; +import seedu.progresschecker.commons.util.StringUtil; /** * Tests that a {@code Person}'s {@code Name} matches any of the keywords given. diff --git a/src/main/java/seedu/address/model/person/Person.java b/src/main/java/seedu/progresschecker/model/person/Person.java similarity index 52% rename from src/main/java/seedu/address/model/person/Person.java rename to src/main/java/seedu/progresschecker/model/person/Person.java index ec9f2aa5e919..721d99a0d6ce 100644 --- a/src/main/java/seedu/address/model/person/Person.java +++ b/src/main/java/seedu/progresschecker/model/person/Person.java @@ -1,38 +1,47 @@ -package seedu.address.model.person; +package seedu.progresschecker.model.person; -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.progresschecker.commons.util.CollectionUtil.requireAllNonNull; import java.util.Collections; import java.util.Objects; import java.util.Set; -import seedu.address.model.tag.Tag; -import seedu.address.model.tag.UniqueTagList; +import seedu.progresschecker.model.tag.Tag; +import seedu.progresschecker.model.tag.UniqueTagList; /** - * Represents a Person in the address book. + * Represents a Person in ProgressChecker. * Guarantees: details are present and not null, field values are validated, immutable. */ public class Person { + private static String defaultPath = "/images/profile_photo.jpg"; + private final Name name; private final Phone phone; private final Email email; - private final Address address; + private final GithubUsername username; + private final Major major; + private final Year year; + private String photoPath; private final UniqueTagList tags; /** * Every field must be present and not null. */ - public Person(Name name, Phone phone, Email email, Address address, Set<Tag> tags) { - requireAllNonNull(name, phone, email, address, tags); + public Person(Name name, Phone phone, Email email, GithubUsername username, Major major, Year year, + Set<Tag> tags) { + requireAllNonNull(name, phone, email, username, major, year, tags); this.name = name; this.phone = phone; this.email = email; - this.address = address; + this.username = username; + this.major = major; + this.year = year; // protect internal tags from changes in the arg list this.tags = new UniqueTagList(tags); + this.photoPath = defaultPath; } public Name getName() { @@ -47,8 +56,26 @@ public Email getEmail() { return email; } - public Address getAddress() { - return address; + //@@author EdwardKSG + public GithubUsername getUsername() { + return username; + } + + public Major getMajor() { + return major; + } + + public Year getYear() { + return year; + } + //@@author + + public String getPhotoPath() { + return photoPath; + } + + public String getDefaultPath() { + return defaultPath; } /** @@ -59,6 +86,10 @@ public Set<Tag> getTags() { return Collections.unmodifiableSet(tags.toSet()); } + public void updatePhoto(String path) { + this.photoPath = path; + } + @Override public boolean equals(Object other) { if (other == this) { @@ -73,13 +104,15 @@ public boolean equals(Object other) { return otherPerson.getName().equals(this.getName()) && otherPerson.getPhone().equals(this.getPhone()) && otherPerson.getEmail().equals(this.getEmail()) - && otherPerson.getAddress().equals(this.getAddress()); + && otherPerson.getUsername().equals(this.getUsername()) + && otherPerson.getMajor().equals(this.getMajor()) + && otherPerson.getYear().equals(this.getYear()); } @Override public int hashCode() { // use this method for custom fields hashing instead of implementing your own - return Objects.hash(name, phone, email, address, tags); + return Objects.hash(name, phone, email, username, major, year, tags); } @Override @@ -90,8 +123,12 @@ public String toString() { .append(getPhone()) .append(" Email: ") .append(getEmail()) - .append(" Address: ") - .append(getAddress()) + .append(" Github Username: ") + .append(getUsername()) + .append(" Major: ") + .append(getMajor()) + .append(" Year of Study: ") + .append(getYear()) .append(" Tags: "); getTags().forEach(builder::append); return builder.toString(); diff --git a/src/main/java/seedu/address/model/person/Phone.java b/src/main/java/seedu/progresschecker/model/person/Phone.java similarity index 88% rename from src/main/java/seedu/address/model/person/Phone.java rename to src/main/java/seedu/progresschecker/model/person/Phone.java index 11b5435ac247..ad51a4271e19 100644 --- a/src/main/java/seedu/address/model/person/Phone.java +++ b/src/main/java/seedu/progresschecker/model/person/Phone.java @@ -1,10 +1,10 @@ -package seedu.address.model.person; +package seedu.progresschecker.model.person; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; +import static seedu.progresschecker.commons.util.AppUtil.checkArgument; /** - * Represents a Person's phone number in the address book. + * Represents a Person's phone number in the ProgressChecker. * Guarantees: immutable; is valid as declared in {@link #isValidPhone(String)} */ public class Phone { diff --git a/src/main/java/seedu/address/model/person/UniquePersonList.java b/src/main/java/seedu/progresschecker/model/person/UniquePersonList.java similarity index 82% rename from src/main/java/seedu/address/model/person/UniquePersonList.java rename to src/main/java/seedu/progresschecker/model/person/UniquePersonList.java index f2c4c4c585e4..31e7ab2b9e2f 100644 --- a/src/main/java/seedu/address/model/person/UniquePersonList.java +++ b/src/main/java/seedu/progresschecker/model/person/UniquePersonList.java @@ -1,16 +1,19 @@ -package seedu.address.model.person; +package seedu.progresschecker.model.person; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.progresschecker.commons.util.CollectionUtil.requireAllNonNull; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; import java.util.Iterator; import java.util.List; import javafx.collections.FXCollections; import javafx.collections.ObservableList; -import seedu.address.commons.util.CollectionUtil; -import seedu.address.model.person.exceptions.DuplicatePersonException; -import seedu.address.model.person.exceptions.PersonNotFoundException; +import seedu.progresschecker.commons.util.CollectionUtil; +import seedu.progresschecker.model.person.exceptions.DuplicatePersonException; +import seedu.progresschecker.model.person.exceptions.PersonNotFoundException; /** * A list of persons that enforces uniqueness between its elements and does not allow nulls. @@ -81,6 +84,19 @@ public boolean remove(Person toRemove) throws PersonNotFoundException { return personFoundAndDeleted; } + /** + * Sort all persons from the list. + * + */ + public void sort() { + Collections.sort(internalList, new Comparator<Person>() { + @Override + public int compare(Person o1, Person o2) { + return (o1.getName().toString().toUpperCase()).compareTo(o2.getName().toString().toUpperCase()); + } + }); + } + public void setPersons(UniquePersonList replacement) { this.internalList.setAll(replacement.internalList); } diff --git a/src/main/java/seedu/progresschecker/model/person/Year.java b/src/main/java/seedu/progresschecker/model/person/Year.java new file mode 100644 index 000000000000..6b91cd465097 --- /dev/null +++ b/src/main/java/seedu/progresschecker/model/person/Year.java @@ -0,0 +1,59 @@ +package seedu.progresschecker.model.person; + +import static java.util.Objects.requireNonNull; +import static seedu.progresschecker.commons.util.AppUtil.checkArgument; + +//@@author EdwardKSG +/** + * Represents a Person's year of study in the ProgressChecker. + * Guarantees: immutable; is valid as declared in {@link #isValidYear(String)} + */ +public class Year { + + public static final String MESSAGE_YEAR_CONSTRAINTS = + "Person years of study can take digits ranging from 1 to 5, it can be left blank"; + + /* + * It accepts single digits ranging from 1 to 5. + * empty string will be accepted as well, as "year" is an optional field. + */ + public static final String YEAR_VALIDATION_REGEX = "(^$|^[1-5]$)"; + + public final String value; + + /** + * Constructs an {@code Year}. + * + * @param year A valid year. + */ + public Year(String year) { + requireNonNull(year); + checkArgument(isValidYear(year), MESSAGE_YEAR_CONSTRAINTS); + this.value = year; + } + + /** + * Returns true if a given string is a valid year of study. + */ + public static boolean isValidYear(String test) { + return test.matches(YEAR_VALIDATION_REGEX); + } + + @Override + public String toString() { + return value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof Year // instanceof handles nulls + && this.value.equals(((Year) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } + +} diff --git a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java b/src/main/java/seedu/progresschecker/model/person/exceptions/DuplicatePersonException.java similarity index 67% rename from src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java rename to src/main/java/seedu/progresschecker/model/person/exceptions/DuplicatePersonException.java index fce401885dc8..b92be1cbf724 100644 --- a/src/main/java/seedu/address/model/person/exceptions/DuplicatePersonException.java +++ b/src/main/java/seedu/progresschecker/model/person/exceptions/DuplicatePersonException.java @@ -1,6 +1,6 @@ -package seedu.address.model.person.exceptions; +package seedu.progresschecker.model.person.exceptions; -import seedu.address.commons.exceptions.DuplicateDataException; +import seedu.progresschecker.commons.exceptions.DuplicateDataException; /** * Signals that the operation will result in duplicate Person objects. diff --git a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java b/src/main/java/seedu/progresschecker/model/person/exceptions/PersonNotFoundException.java similarity index 71% rename from src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java rename to src/main/java/seedu/progresschecker/model/person/exceptions/PersonNotFoundException.java index f757e25f5566..2d842a4ac748 100644 --- a/src/main/java/seedu/address/model/person/exceptions/PersonNotFoundException.java +++ b/src/main/java/seedu/progresschecker/model/person/exceptions/PersonNotFoundException.java @@ -1,4 +1,4 @@ -package seedu.address.model.person.exceptions; +package seedu.progresschecker.model.person.exceptions; /** * Signals that the operation is unable to find the specified person. diff --git a/src/main/java/seedu/progresschecker/model/photo/PhotoPath.java b/src/main/java/seedu/progresschecker/model/photo/PhotoPath.java new file mode 100644 index 000000000000..6f712e83f7c5 --- /dev/null +++ b/src/main/java/seedu/progresschecker/model/photo/PhotoPath.java @@ -0,0 +1,65 @@ +package seedu.progresschecker.model.photo; + +import static java.util.Objects.requireNonNull; +import static seedu.progresschecker.commons.util.FileUtil.isUnderFolder; +import static seedu.progresschecker.commons.util.FileUtil.isValidImageFile; + +import seedu.progresschecker.commons.exceptions.IllegalValueException; + +//@@author Livian1107 +/** + * Represents a Path of Photo in ProgressChecker + */ +public class PhotoPath { + + public static final String PHOTO_SAVED_PATH = "src/main/resources/images/contact/"; + public static final String MESSAGE_PHOTOPATH_CONSTRAINTS = + "The path of the profile photo should start with '" + PHOTO_SAVED_PATH + + "'. The extensions of the file to upload should be 'jpg', 'jpeg' or 'png'."; + + public final String value; + + /** + * Builds the path of profile photo in the ProgressChecker + * Validates the given String of path + * @param path is the String of the profile photo path + * @trhows IllegalValueException if the String violates the constraints of photo path + */ + public PhotoPath(String path) throws IllegalValueException { + requireNonNull(path); + if (isValidPhotoPath(path)) { + this.value = path; + } else { + throw new IllegalValueException(MESSAGE_PHOTOPATH_CONSTRAINTS); + } + } + + /** + * Validates the given photo path + */ + public static boolean isValidPhotoPath (String path) { + if (path.isEmpty()) { //empty path + return true; + } + boolean isValidImage = isValidImageFile(path); + boolean isUnderFolder = isUnderFolder(path, PHOTO_SAVED_PATH); + return isValidImage && isUnderFolder; + } + + @Override + public String toString() { + return this.value; + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof PhotoPath // instanceof handles nulls + && this.value.equals(((PhotoPath) other).value)); // state check + } + + @Override + public int hashCode() { + return value.hashCode(); + } +} diff --git a/src/main/java/seedu/progresschecker/model/photo/UniquePhotoList.java b/src/main/java/seedu/progresschecker/model/photo/UniquePhotoList.java new file mode 100644 index 000000000000..b4d472446fa1 --- /dev/null +++ b/src/main/java/seedu/progresschecker/model/photo/UniquePhotoList.java @@ -0,0 +1,122 @@ +package seedu.progresschecker.model.photo; + +import static java.util.Objects.requireNonNull; +import static seedu.progresschecker.commons.util.CollectionUtil.requireAllNonNull; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import seedu.progresschecker.commons.util.CollectionUtil; +import seedu.progresschecker.model.photo.exceptions.DuplicatePhotoException; +import seedu.progresschecker.model.photo.exceptions.PhotoNotFoundException; + +//@@author Livian1107 +/** + * A list of photo paths that enforces uniqueness between its elements and does not allow nulls. + * + * Supports a minimal set of list operations. + * + * @see PhotoPath#equals(Object) + * @see CollectionUtil#elementsAreUnique(Collection) + */ +public class UniquePhotoList implements Iterable<PhotoPath> { + + private final ObservableList<PhotoPath> internalList = FXCollections.observableArrayList(); + + /** + * Returns true if the list contains an equivalent photo path as the given argument. + */ + public boolean contains(PhotoPath toCheck) { + requireNonNull(toCheck); + return internalList.contains(toCheck); + } + + /** + * Adds a photo path to the list. + * + * @throws DuplicatePhotoException if the photo path to add is a duplicate of an existing photo path in the list. + */ + public void add(PhotoPath toAdd) throws DuplicatePhotoException { + requireNonNull(toAdd); + if (contains(toAdd)) { + throw new DuplicatePhotoException(); + } + internalList.add(toAdd); + } + + /** + * Replaces the photo path {@code target} in the list with {@code editedPhoto}. + * + * @throws DuplicatePhotoException if the replacement is equivalent to another existing photo path in the list. + * @throws PhotoNotFoundException if {@code target} could not be found in the list. + */ + public void setPhoto(PhotoPath target, PhotoPath editedPhoto) + throws DuplicatePhotoException, PhotoNotFoundException { + requireNonNull(editedPhoto); + + int index = internalList.indexOf(target); + if (index == -1) { + throw new PhotoNotFoundException(); + } + + if (!target.equals(editedPhoto) && internalList.contains(editedPhoto)) { + throw new DuplicatePhotoException(); + } + + internalList.set(index, editedPhoto); + } + + /** + * Removes the equivalent photo path from the list. + * + * @throws PhotoNotFoundException if no such person could be found in the list. + */ + public boolean remove(PhotoPath toRemove) throws PhotoNotFoundException { + requireNonNull(toRemove); + final boolean photoFoundAndDeleted = internalList.remove(toRemove); + if (!photoFoundAndDeleted) { + throw new PhotoNotFoundException(); + } + return photoFoundAndDeleted; + } + + public void setPhotos(UniquePhotoList replacement) { + this.internalList.setAll(replacement.internalList); + } + + public void setPhotos(List<PhotoPath> photos) throws DuplicatePhotoException { + requireAllNonNull(photos); + final UniquePhotoList replacement = new UniquePhotoList(); + for (final PhotoPath photo : photos) { + replacement.add(photo); + } + setPhotos(replacement); + } + + /** + * Returns the backing list as an unmodifiable {@code ObservableList}. + */ + public ObservableList<PhotoPath> asObservableList() { + return FXCollections.unmodifiableObservableList(internalList); + } + + @Override + public Iterator<PhotoPath> iterator() { + return internalList.iterator(); + } + + @Override + public boolean equals(Object other) { + return other == this // short circuit if same object + || (other instanceof UniquePhotoList // instanceof handles nulls + && this.internalList.equals(((UniquePhotoList) other).internalList)); + } + + @Override + public int hashCode() { + return internalList.hashCode(); + } +} diff --git a/src/main/java/seedu/progresschecker/model/photo/exceptions/DuplicatePhotoException.java b/src/main/java/seedu/progresschecker/model/photo/exceptions/DuplicatePhotoException.java new file mode 100644 index 000000000000..34cc62eb0238 --- /dev/null +++ b/src/main/java/seedu/progresschecker/model/photo/exceptions/DuplicatePhotoException.java @@ -0,0 +1,12 @@ +package seedu.progresschecker.model.photo.exceptions; + +import seedu.progresschecker.commons.exceptions.DuplicateDataException; +//@@author Livian1107 +/** + * Signals that the operation will result in duplicate PhotoPath objects. + */ +public class DuplicatePhotoException extends DuplicateDataException { + public DuplicatePhotoException() { + super("Operation would result in duplicate photos"); + } +} diff --git a/src/main/java/seedu/progresschecker/model/photo/exceptions/PhotoNotFoundException.java b/src/main/java/seedu/progresschecker/model/photo/exceptions/PhotoNotFoundException.java new file mode 100644 index 000000000000..9b3fd93a29de --- /dev/null +++ b/src/main/java/seedu/progresschecker/model/photo/exceptions/PhotoNotFoundException.java @@ -0,0 +1,7 @@ +package seedu.progresschecker.model.photo.exceptions; + +//@@author Livian1107 +/** + * Signals that the operation is unable to find the specified photo. + */ +public class PhotoNotFoundException extends Exception {} diff --git a/src/main/java/seedu/address/model/tag/Tag.java b/src/main/java/seedu/progresschecker/model/tag/Tag.java similarity index 89% rename from src/main/java/seedu/address/model/tag/Tag.java rename to src/main/java/seedu/progresschecker/model/tag/Tag.java index 65bdd769995d..502284623c8b 100644 --- a/src/main/java/seedu/address/model/tag/Tag.java +++ b/src/main/java/seedu/progresschecker/model/tag/Tag.java @@ -1,10 +1,10 @@ -package seedu.address.model.tag; +package seedu.progresschecker.model.tag; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.AppUtil.checkArgument; +import static seedu.progresschecker.commons.util.AppUtil.checkArgument; /** - * Represents a Tag in the address book. + * Represents a Tag in the ProgressChecker. * Guarantees: immutable; name is valid as declared in {@link #isValidTagName(String)} */ public class Tag { diff --git a/src/main/java/seedu/address/model/tag/UniqueTagList.java b/src/main/java/seedu/progresschecker/model/tag/UniqueTagList.java similarity index 94% rename from src/main/java/seedu/address/model/tag/UniqueTagList.java rename to src/main/java/seedu/progresschecker/model/tag/UniqueTagList.java index e9a74947fc3f..1902e3f62f06 100644 --- a/src/main/java/seedu/address/model/tag/UniqueTagList.java +++ b/src/main/java/seedu/progresschecker/model/tag/UniqueTagList.java @@ -1,7 +1,7 @@ -package seedu.address.model.tag; +package seedu.progresschecker.model.tag; import static java.util.Objects.requireNonNull; -import static seedu.address.commons.util.CollectionUtil.requireAllNonNull; +import static seedu.progresschecker.commons.util.CollectionUtil.requireAllNonNull; import java.util.HashSet; import java.util.Iterator; @@ -9,8 +9,8 @@ import javafx.collections.FXCollections; import javafx.collections.ObservableList; -import seedu.address.commons.exceptions.DuplicateDataException; -import seedu.address.commons.util.CollectionUtil; +import seedu.progresschecker.commons.exceptions.DuplicateDataException; +import seedu.progresschecker.commons.util.CollectionUtil; /** * A list of tags that enforces no nulls and uniqueness between its elements. diff --git a/src/main/java/seedu/progresschecker/model/task/SimplifiedTask.java b/src/main/java/seedu/progresschecker/model/task/SimplifiedTask.java new file mode 100644 index 000000000000..cc6e32ce3fad --- /dev/null +++ b/src/main/java/seedu/progresschecker/model/task/SimplifiedTask.java @@ -0,0 +1,31 @@ +package seedu.progresschecker.model.task; + +//@@author EdwardKSG +/** + * Represents a simplified task in the ProgressChecker. + * It is called "simplified" because it extracts only the attributes we want to use from the Google Tasks + */ +public class SimplifiedTask { + public final String title; + public final String notes; + public final String due; + + public SimplifiedTask (String title, String notes, String due) { + this.title = title; + this.notes = notes; + this.due = due; + } + + public String getTitle () { + return this.title; + } + + public String getNotes () { + return this.notes; + } + + public String getDue () { + return this.due; + } + +} diff --git a/src/main/java/seedu/progresschecker/model/task/TaskListUtil.java b/src/main/java/seedu/progresschecker/model/task/TaskListUtil.java new file mode 100644 index 000000000000..4b992a66b640 --- /dev/null +++ b/src/main/java/seedu/progresschecker/model/task/TaskListUtil.java @@ -0,0 +1,219 @@ +package seedu.progresschecker.model.task; + +import java.io.IOException; +import java.util.List; + +import com.google.api.services.tasks.model.Task; +import com.google.api.services.tasks.model.TaskList; +import com.google.api.services.tasks.model.TaskLists; +import com.google.api.services.tasks.model.Tasks; + +import seedu.progresschecker.logic.apisetup.ConnectTasksApi; +import seedu.progresschecker.logic.commands.exceptions.CommandException; + +//@@author EdwardKSG +/** + * Include customized methods (based on Google Tasks API) to manipulate task lists. + */ +public class TaskListUtil { + + public static final String AUTHORIZE_FAILURE = "Failed to authorize tasks api client credentials"; + public static final String ADD_FAILURE = "Failed to add new task list to account"; + public static final String LOAD_FAILURE = "Failed to load this task list (might be wrong title)"; + + /** + * Creates a new task list with title {@code String} and adds to the current list of task lists + * + * @param listTitle title of the task list we intend to create + */ + public static void createTaskList(String listTitle) throws CommandException { + ConnectTasksApi connection = new ConnectTasksApi(); + + try { + connection.authorize(); + } catch (Exception e) { + throw new CommandException(AUTHORIZE_FAILURE); + } + + com.google.api.services.tasks.Tasks service = connection.getTasksService(); + + try { + service.tasklists().insert( + new TaskList().setTitle(listTitle) + ).execute(); + } catch (IOException ioe) { + throw new CommandException(ADD_FAILURE); + } + } + + /** + * Finds the task list with title {@code String} from the current list of task lists + * + * @param listTitle title of the task list we look for + * @return the List instances containing all tasks in the specified task list + */ + public static List<Task> searchTaskList(String listTitle) throws CommandException { + List<Task> list = null; + + ConnectTasksApi connection = new ConnectTasksApi(); + + try { + connection.authorize(); + } catch (Exception e) { + throw new CommandException(AUTHORIZE_FAILURE); + } + + com.google.api.services.tasks.Tasks service = connection.getTasksService(); + + try { + + TaskLists taskLists = service.tasklists().list().execute(); + TaskList taskList = taskLists.getItems().stream() + .filter(t -> t.getTitle().equals(listTitle)) + .findFirst() + .orElse(null); + String id = taskList.getId(); + Tasks tasks = service.tasks().list(id).execute(); + list = tasks.getItems(); + } catch (IOException ioe) { + throw new CommandException(LOAD_FAILURE); + } + + return list; + } + + /** + * Finds the task list with ID {@code String} from the current list of task lists + * + * @param listId title of the task list we look for + * @return the List instances containing all tasks in the specified task list + */ + public static List<Task> searchTaskListById(String listId) throws CommandException { + List<Task> list = null; + + ConnectTasksApi connection = new ConnectTasksApi(); + + try { + connection.authorize(); + } catch (Exception e) { + throw new CommandException(AUTHORIZE_FAILURE); + } + + com.google.api.services.tasks.Tasks service = connection.getTasksService(); + + try { + + Tasks tasks = service.tasks().list(listId).execute(); + list = tasks.getItems(); + } catch (IOException ioe) { + throw new CommandException(LOAD_FAILURE); + } + + return list; + } + + /** + * Changes the name of task list with id {@code String} to {@code String} + * + * @param listId identifier of the target task list whose name will be changed + * @param listTitle title of the task list we look for + */ + public static void setTaskListTitle(String listId, String listTitle) throws CommandException { + ConnectTasksApi connection = new ConnectTasksApi(); + + try { + connection.authorize(); + } catch (Exception e) { + throw new CommandException(AUTHORIZE_FAILURE); + } + + com.google.api.services.tasks.Tasks service = connection.getTasksService(); + + try { + + TaskList taskList = service.tasklists().get(listId).execute(); + + taskList.setTitle(listTitle); + + TaskList result = service.tasklists().update( + taskList.getId(), + taskList + ).execute(); + + } catch (IOException ioe) { + throw new CommandException(LOAD_FAILURE); + } + } + + /** + * Copies tasks in the task list with id {@code String} to the task list with title {@code String} + * + * @param listId identifier of the target task list whose name will be changed + * @param listTitle title of the task list we look for + */ + public static void copyTaskList(String listTitle, String listId) throws CommandException { + ConnectTasksApi connection = new ConnectTasksApi(); + + try { + connection.authorize(); + } catch (Exception e) { + throw new CommandException(AUTHORIZE_FAILURE); + } + + com.google.api.services.tasks.Tasks service = connection.getTasksService(); + + try { + + TaskLists taskLists = service.tasklists().list().execute(); + + Tasks baseTasks = service.tasks().list(listId).execute(); + + TaskList targetTaskList = taskLists.getItems().stream() + .filter(t -> t.getTitle().equals(listTitle)) + .findFirst() + .orElse(null); + String id = targetTaskList.getId(); + + for (Task task : baseTasks.getItems()) { + Task t = new Task(); + t.setTitle(task.getTitle()); + t.setStatus(task.getStatus()); + t.setDue(task.getDue()); + t.setNotes(task.getNotes()); + service.tasks().insert(id, t).execute(); + } + + + } catch (IOException ioe) { + throw new CommandException(LOAD_FAILURE); + } + } + + /** + * Removes all tasks in the task list with id {@code String} + * + * @param listId identifier of the target task list whose content will be removed + */ + public static void clearTaskList(String listId) throws CommandException { + ConnectTasksApi connection = new ConnectTasksApi(); + + try { + connection.authorize(); + } catch (Exception e) { + throw new CommandException(AUTHORIZE_FAILURE); + } + + com.google.api.services.tasks.Tasks service = connection.getTasksService(); + + try { + + Tasks tasks = service.tasks().list(listId).execute(); + for (Task task : tasks.getItems()) { + service.tasks().delete(listId, task.getId()).execute(); + } + + } catch (IOException ioe) { + throw new CommandException(LOAD_FAILURE); + } + } +} diff --git a/src/main/java/seedu/progresschecker/model/task/TaskUtil.java b/src/main/java/seedu/progresschecker/model/task/TaskUtil.java new file mode 100644 index 000000000000..0d299a614288 --- /dev/null +++ b/src/main/java/seedu/progresschecker/model/task/TaskUtil.java @@ -0,0 +1,305 @@ +package seedu.progresschecker.model.task; + +import java.io.IOException; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; + +import com.google.api.client.util.DateTime; +import com.google.api.services.tasks.model.Task; +import com.google.api.services.tasks.model.TaskList; +import com.google.api.services.tasks.model.TaskLists; +import com.google.api.services.tasks.model.Tasks; + +import javafx.util.Pair; +import seedu.progresschecker.logic.apisetup.ConnectTasksApi; +import seedu.progresschecker.logic.commands.exceptions.CommandException; + +//@@author EdwardKSG +/** + * Include customized methods (based on Google Tasks API) to manipulate tasks. + */ +public class TaskUtil { + + public static final String AUTHORIZE_FAILURE = "Failed to authorize tasks api client credentials"; + public static final String LOAD_FAILURE = "Failed to load this task list"; + public static final String INDEX_OUT_OF_BOUND = "The index is out of bound"; + public static final String DATE_FORMAT = "MM/dd/yyyy HH:mm"; + public static final String COMPLETED = "completed"; + public static final String NEEDS_ACTION = "needsAction"; + public static final int ERROR_NUMBER = -1; + public static final int TRUE = 1; + public static final int FALSE = 0; + public static final String ERROR_STRING = ""; + public static final String NOTE_TOKEN = "checkurl"; + + + /** + * Finds the task with title {@code String} in the tasklist with title {@code String} + * + * @param taskTitle title of the task we look for + * @param listTitle the title of the list to which the task belongs + * @return the Task instances + */ + public static Task searchTask(String taskTitle, String listTitle) throws CommandException { + Task task = null; + + ConnectTasksApi connection = new ConnectTasksApi(); + + try { + connection.authorize(); + } catch (Exception e) { + throw new CommandException(AUTHORIZE_FAILURE); + } + + com.google.api.services.tasks.Tasks service = connection.getTasksService(); + + try { + TaskLists taskLists = service.tasklists().list().execute(); + TaskList taskList = taskLists.getItems().stream() + .filter(t -> t.getTitle().equals(listTitle)) + .findFirst() + .orElse(null); + + Tasks tasks = service.tasks().list(taskList.getId()).execute(); + task = tasks.getItems().stream() + .filter(t -> t.getTitle().equals(taskTitle)) + .findFirst() + .orElse(null); + + } catch (IOException ioe) { + throw new CommandException(LOAD_FAILURE); + } + + return task; + } + + /** + * Creates a task with title {@code String} to the tasklist with ID {@code String} + * + * @param taskTitle title of the task we want to create + * @param listId the identifier of the list to which the task will be added + * @param notes description or relevant URL link to this task + * @param due the date and time of the deadline, in format "MM/dd/yyyy HH:mm" + */ + public static void createTask(String taskTitle, String listId, String notes, String due) + throws CommandException { + ConnectTasksApi connection = new ConnectTasksApi(); + + try { + connection.authorize(); + } catch (Exception e) { + throw new CommandException(AUTHORIZE_FAILURE); + } + + com.google.api.services.tasks.Tasks service = connection.getTasksService(); + + try { + TaskLists taskLists = service.tasklists().list().execute(); + + Task task = service.tasks().insert( + listId, + new Task().setTitle(taskTitle) + .setDue(getDate(due)) + .setNotes(notes) + .setStatus(NEEDS_ACTION) + ).execute(); + + } catch (IOException ioe) { + throw new CommandException(LOAD_FAILURE); + } + + } + + /** + * Adds a group of tasks to a Google Task List with Id {@code String listId} + * + * @param simpleList a list of {@code SimplifiedTask} + * @param listId the identifier of the list to which the task will be added + */ + public static void addMultipleTask(SimplifiedTask[] simpleList, String listId) + throws CommandException { + ConnectTasksApi connection = new ConnectTasksApi(); + + try { + connection.authorize(); + } catch (Exception e) { + throw new CommandException(AUTHORIZE_FAILURE); + } + + com.google.api.services.tasks.Tasks service = connection.getTasksService(); + + try { + for (SimplifiedTask simpleTask: simpleList) { + Task task = service.tasks().insert( + listId, + new Task().setTitle(simpleTask.getTitle()) + .setDue(getDate(simpleTask.getDue())) + .setNotes(simpleTask.getNotes()) + .setStatus(NEEDS_ACTION) + ).execute(); + } + + + } catch (IOException ioe) { + throw new CommandException(LOAD_FAILURE); + } + + } + + /** + * Converts a string in format "MM/dd/yyyy HH:mm" to a DateTime object + * + * @param s string in format "MM/dd/yyyy HH:mm", representing a date + * @return the DateTime instances, or null if encountered error when parsing + */ + public static DateTime getDate(String s) { + SimpleDateFormat simpleDateFormat = new SimpleDateFormat(DATE_FORMAT); + try { + Date date = simpleDateFormat.parse(s); + return new DateTime(date); + } catch (ParseException ex) { + return null; + } + } + + /** + * Marks the task with index {@code int index} in the tasklist with ID {@code String listId} as completed + * + * @param index title of the task we look for + * @param listId the identifier of the list to which the task belongs + * @return result whether this command made any change of the task list (0 means no change) and + * the title of the task with index {@code int} + */ + public static Pair<Integer, String> completeTask(int index, String listId) throws CommandException { + ConnectTasksApi connection = new ConnectTasksApi(); + + try { + connection.authorize(); + } catch (Exception e) { + throw new CommandException(AUTHORIZE_FAILURE); + } + + com.google.api.services.tasks.Tasks service = connection.getTasksService(); + + try { + int isChanged = FALSE; + Tasks tasks = service.tasks().list(listId).execute(); + List<Task> list = tasks.getItems(); + if (list.size() < index) { + Pair<Integer, String> result = new Pair<Integer, String>(ERROR_NUMBER, INDEX_OUT_OF_BOUND); + return result; + } + Task task = list.get(index - 1); + + if (!task.getStatus().equals(COMPLETED)) { + task.setStatus(COMPLETED); + isChanged = TRUE; + } + + task = service.tasks().update( + listId, + task.getId(), + task + ).execute(); + + Pair<Integer, String> result = new Pair<Integer, String>(isChanged, task.getTitle()); + + return result; + + } catch (IOException ioe) { + throw new CommandException(LOAD_FAILURE); + } + } + + /** + * Marks the task with index {@code int index} in the tasklist with ID {@code String listId} as incompleted + * + * @param index title of the task we look for + * @param listId the identifier of the list to which the task belongs + * @return result whether this command made any change of the task list (0 means no change) and + * the title of the task with index {@code int} + */ + public static Pair<Integer, String> undoTask(int index, String listId) throws CommandException { + ConnectTasksApi connection = new ConnectTasksApi(); + + try { + connection.authorize(); + } catch (Exception e) { + throw new CommandException(AUTHORIZE_FAILURE); + } + + com.google.api.services.tasks.Tasks service = connection.getTasksService(); + + try { + int isChanged = FALSE; + Tasks tasks = service.tasks().list(listId).execute(); + List<Task> list = tasks.getItems(); + if (list.size() < index) { + Pair<Integer, String> result = new Pair<Integer, String>(ERROR_NUMBER, INDEX_OUT_OF_BOUND); + return result; + } + Task task = list.get(index - 1); + + if (!task.getStatus().equals(NEEDS_ACTION)) { + task.setCompleted(null); + task.setStatus(NEEDS_ACTION); + isChanged = TRUE; + } + + task = service.tasks().update( + listId, + task.getId(), + task + ).execute(); + + Pair<Integer, String> result = new Pair<Integer, String>(isChanged, task.getTitle()); + + return result; + + } catch (IOException ioe) { + throw new CommandException(LOAD_FAILURE); + } + } + + /** + * Retrieve the URL of task with index {@code int index} in the tasklist with ID {@code String listId} + * + * @param index title of the task we look for + * @param listId the identifier of the list to which the task belongs + * @return the URL of task with index {@code int index} in the tasklist with ID {@code String listId} + * or error if index is out of bound. and the title of the task with index {@code int} + */ + public static Pair<String, String> getTaskUrl(int index, String listId) throws CommandException { + ConnectTasksApi connection = new ConnectTasksApi(); + + try { + connection.authorize(); + } catch (Exception e) { + throw new CommandException(AUTHORIZE_FAILURE); + } + + com.google.api.services.tasks.Tasks service = connection.getTasksService(); + + try { + Tasks tasks = service.tasks().list(listId).execute(); + List<Task> list = tasks.getItems(); + if (list.size() < index) { + Pair<String, String> result = new Pair<String, String>(ERROR_STRING, ERROR_STRING); + return result; + } + Task task = list.get(index - 1); + + String title = task.getTitle(); + String notesWithUrl = task.getNotes(); + String[] parts = notesWithUrl.split(NOTE_TOKEN); + + Pair<String, String> result = new Pair<String, String>(parts[1], title); + return result; + + } catch (IOException ioe) { + throw new CommandException(LOAD_FAILURE); + } + } +} diff --git a/src/main/java/seedu/progresschecker/model/util/SampleDataUtil.java b/src/main/java/seedu/progresschecker/model/util/SampleDataUtil.java new file mode 100644 index 000000000000..eb2072262d5c --- /dev/null +++ b/src/main/java/seedu/progresschecker/model/util/SampleDataUtil.java @@ -0,0 +1,924 @@ +package seedu.progresschecker.model.util; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import seedu.progresschecker.model.ProgressChecker; +import seedu.progresschecker.model.ReadOnlyProgressChecker; +import seedu.progresschecker.model.exercise.Exercise; +import seedu.progresschecker.model.exercise.ModelAnswer; +import seedu.progresschecker.model.exercise.Question; +import seedu.progresschecker.model.exercise.QuestionIndex; +import seedu.progresschecker.model.exercise.QuestionType; +import seedu.progresschecker.model.exercise.StudentAnswer; +import seedu.progresschecker.model.exercise.exceptions.DuplicateExerciseException; +import seedu.progresschecker.model.issues.Assignees; +import seedu.progresschecker.model.issues.Labels; +import seedu.progresschecker.model.person.Email; +import seedu.progresschecker.model.person.GithubUsername; +import seedu.progresschecker.model.person.Major; +import seedu.progresschecker.model.person.Name; +import seedu.progresschecker.model.person.Person; +import seedu.progresschecker.model.person.Phone; +import seedu.progresschecker.model.person.Year; +import seedu.progresschecker.model.person.exceptions.DuplicatePersonException; +import seedu.progresschecker.model.tag.Tag; + +/** + * Contains utility methods for populating {@code ProgressChecker} with sample data. + */ +public class SampleDataUtil { + public static Person[] getSamplePersons() { + return new Person[] { + new Person(new Name("Alex Yeoh"), new Phone("87438807"), new Email("alexyeoh@gmail.com"), + new GithubUsername("AlexGithub"), new Major("Computer Science"), new Year("2"), + getTagSet("friends")), + new Person(new Name("Bernice Yu"), new Phone("99272758"), new Email("berniceyu@gmail.com"), + new GithubUsername("BerniceGithub"), new Major("Computer Engineering"), new Year("2"), + getTagSet("colleagues", "friends")), + new Person(new Name("Charlotte Oliveiro"), new Phone("93210283"), new Email("charlotte@gmail.com"), + new GithubUsername("CharlotteGithub"), new Major("Information Security"), new Year("2"), + getTagSet("neighbours")), + new Person(new Name("David Li"), new Phone("91031282"), new Email("lidavid@gmail.com"), + new GithubUsername("DavidGithub"), new Major("Computer Engineering"), new Year("2"), + getTagSet("family")) + }; + } + + public static ReadOnlyProgressChecker getSampleProgressChecker() { + try { + ProgressChecker sampleAb = new ProgressChecker(); + for (Person samplePerson : getSamplePersons()) { + sampleAb.addPerson(samplePerson); + } + for (Exercise sampleExercise : getSampleExercises()) { + sampleAb.addExercise(sampleExercise); + } + /*for (Issue sampleIssue : getSampleIssues()) { + sampleAb.createIssueOnGitHub(sampleIssue); + }*/ + return sampleAb; + } catch (DuplicatePersonException e) { + throw new AssertionError("sample data cannot contain duplicate persons", e); + } catch (DuplicateExerciseException e) { + throw new AssertionError("sample data cannot contain duplicate exercises", e); + } /* catch (IOException ie) { + throw new AssertionError("first login to github", ie); + } */ + } + + + /** + * Returns a tag set containing the list of strings given. + */ + public static Set<Tag> getTagSet(String... strings) { + HashSet<Tag> tags = new HashSet<>(); + for (String s : strings) { + tags.add(new Tag(s)); + } + + return tags; + } + + //@@author adityaa1998 + /** + * Returns an label list containing the list of strings given. + */ + public static List<Labels> getLabelsList(String... strings) { + ArrayList<Labels> labels = new ArrayList<>(); + for (String s : strings) { + labels.add(new Labels(s)); + } + + return labels; + } + + /** + * Returns an Assignee list containing the list of strings given. + */ + public static List<Assignees> getAssigneeList(String... strings) { + ArrayList<Assignees> assignees = new ArrayList<>(); + for (String s : strings) { + assignees.add(new Assignees(s)); + } + + return assignees; + } + //@@author + + //@@author iNekox3 + public static Exercise[] getSampleExercises() { + return new Exercise[] { + // week 2 + new Exercise(new QuestionIndex("2.2.1"), new QuestionType("choice"), + new Question("Which one of these is not a feature available in IDEs?\n" + + "\n" + + "a. Compiling.\n" + + "b. Syntax error highlighting.\n" + + "c. Debugging.\n" + + "d. Code navigation e.g., to navigate from a method call to the method implementation.\n" + + "e. Simulation e.g., run a mobile app in a simulator.\n" + + "f. Code analysis e.g. to find unreachable code.\n" + + "g. Reverse engineering design/documentation e.g. generate diagrams from code\n" + + "h. Visual programming e.g. Write programs using ‘drag and drop’ actions " + + "instead of typing code.\n" + + "i. Syntax assistance e.g., show hints as you type.\n" + + "j. Code generation e.g., to generate the code required " + + "by simply specifying which component/structure you want to implement.\n" + + "k. Extension. i.e. ability add more functionality to the IDE using plugins."), + new StudentAnswer(""), + new ModelAnswer("All. While all of these features may not be present in some IDEs, " + + "most do have these features in some form or other.")), + new Exercise(new QuestionIndex("2.5.1"), new QuestionType("text"), + new Question("Explain how the concepts of testing, test case, test failure, " + + "and defect are related to each other."), + new StudentAnswer(""), + new ModelAnswer("No suggested answer.")), + new Exercise(new QuestionIndex("2.5.2"), new QuestionType("choice"), + new Question("Regression testing is the automated re-testing of a software " + + "after it has been modified.\n" + + "\n" + + "a. True\n" + + "b. False\n" + + "c. Partially true"), + new StudentAnswer(""), + new ModelAnswer("c. Regression testing need not be automated but automation is highly recommended.")), + new Exercise(new QuestionIndex("2.5.3"), new QuestionType("text"), + new Question("Explain why and when you would do regression testing in a software project."), + new StudentAnswer(""), + new ModelAnswer("No suggested answer.")), + new Exercise(new QuestionIndex("2.6.1"), new QuestionType("text"), + new Question("What does RCS stand for?"), + new StudentAnswer(""), + new ModelAnswer("Revision Control Software.")), + new Exercise(new QuestionIndex("2.6.2"), new QuestionType("text"), + new Question("In the context of RCS, what is a Revision? Give an example."), + new StudentAnswer(""), + new ModelAnswer("Versions of a piece of information. For example, " + + "take a file containing program code. " + + "If you modify the code and save the file, " + + "you have a new version of that file.")), + new Exercise(new QuestionIndex("2.6.3"), new QuestionType("choice"), + new Question("Which of these is not considered a benefit of a typical RCS?\n" + + "a. Help a single user manage revisions of a single file\n" + + "b. Help a developer recover from a incorrect modification to a code file\n" + + "c. Makes it easier for a group of developers to collaborate on a project\n" + + "d. Manage the drift between multiple versions of your project\n" + + "e. Detect when multiple developers make incompatible changes to the same file\n" + + "f. All of them are benefits of RCS"), + new StudentAnswer(""), + new ModelAnswer("f.")), + new Exercise(new QuestionIndex("2.6.4"), new QuestionType("text"), + new Question("Suppose You are doing a team project with Tom, Dick, and Harry " + + "but those three have not even heard the term RCS. " + + "How do you explain RCS to them as briefly as possible, " + + "using the project as an example?"), + new StudentAnswer(""), + new ModelAnswer("No suggested answer.")), + new Exercise(new QuestionIndex("2.6.5"), new QuestionType("text"), + new Question("In the context of RCS, what is a repo?"), + new StudentAnswer(""), + new ModelAnswer("No suggested answer.")), + + // week 3 + new Exercise(new QuestionIndex("3.1.1"), new QuestionType("choice"), + new Question("Choose the correct statements\n" + + "\n" + + " a. Refactoring can improve understandability\n" + + " b. Refactoring can uncover bugs\n" + + " c. Refactoring can result in better performance\n" + + " d. Refactoring can change the number of methods/classes"), + new StudentAnswer(""), + new ModelAnswer("a b c d. (a, b, c) Although the primary aim of refactoring " + + "is to improve internal code structure, there are other secondary benefits. " + + "(d) Some refactorings result in adding/removing methods/classes.")), + new Exercise(new QuestionIndex("3.1.2"), new QuestionType("text"), + new Question("Do you agree with the following statement? Justify your answer.\n" + + "\n" + + "Statement: Whenever we refactor code to fix bugs, " + + "we need not do regression testing if the bug fix was minor."), + new StudentAnswer(""), + new ModelAnswer("DISAGREE. Even a minor change can have major repercussions on the system. " + + "We MUST do regression testing after each change, no matter how minor it is. " + + "Fixing bugs is technically not refactoring.")), + new Exercise(new QuestionIndex("3.1.3"), new QuestionType("text"), + new Question("Explain what is refactoring and why it is not the same as rewriting, " + + "bug fixing, or adding features."), + new StudentAnswer(""), + new ModelAnswer("No suggested answer.")), + new Exercise(new QuestionIndex("3.1.4"), new QuestionType("choice"), + new Question("‘Extract method’ and ‘Inline method’ refactorings\n" + + "\n" + + "a. are opposites of each other.\n" + + "b. sounds like opposites but they are not."), + new StudentAnswer(""), + new ModelAnswer("a.")), + new Exercise(new QuestionIndex("3.2.1"), new QuestionType("choice"), + new Question("What is the recommended approach regarding coding standards?\n" + + "\n" + + "a. Each developer should find a suitable coding standard and follow it in their coding.\n" + + "b. A developer should understand the importance of following a coding standard. " + + "However, there is no need to follow one.\n" + + "c. A developer should find out the coding standards currently used by the project " + + "and follow that closely.\n" + + "d. Coding standards are lame. Real programmers develop their own individual styles."), + new StudentAnswer(""), + new ModelAnswer("c.")), + new Exercise(new QuestionIndex("3.2.2"), new QuestionType("text"), + new Question("What is the aim of using a coding standard? How does it help?"), + new StudentAnswer(""), + new ModelAnswer("No suggested answer.")), + new Exercise(new QuestionIndex("3.2.3"), new QuestionType("choice"), + new Question("According to the given Java coding standard, which one of these is not a good name?\n" + + "\n" + + "a. integer variable name: totalPeople\n" + + "b. boolean variable name: checkWeight\n" + + "c. method name (returns integer): getPeopleCount\n" + + "d. method name (returns boolean): isValidAddress\n" + + "e. String variable name: description"), + new StudentAnswer(""), + new ModelAnswer("b. checkWeight is an action. " + + "Naming variables as actions makes the code harder to follow. " + + "isWeightValid may be a better name.")), + new Exercise(new QuestionIndex("3.3.1"), new QuestionType("choice"), + new Question("Putting all details in one place can create lengthy methods, " + + "but it is preferred over creating many small methods " + + "because it makes the code easier to understand.\n" + + "\n" + + "a. True\n" + + "b. False"), + new StudentAnswer(""), + new ModelAnswer("b. False. If you are using abstraction properly, " + + "you DON’T need to see all details to understand something. " + + "The whole point of using abstraction is to be able to understand things " + + "without knowing as little details as possible. " + + "This is why we recommend single level of abstraction per method and top-down coding.")), + new Exercise(new QuestionIndex("3.3.2"), new QuestionType("choice"), + new Question("What are the drawbacks of trying to optimize code too soon?\n" + + "\n" + + "a. We may not know which parts are the real performance bottleneck\n" + + "b. When we optimize code manually, it becomes harder for the compiler to optimize\n" + + "c. Optimizing can complicate code\n" + + "d. Optimizing can lead to more error-prone code"), + new StudentAnswer(""), + new ModelAnswer("All.")), + new Exercise(new QuestionIndex("3.3.3"), new QuestionType("choice"), + new Question("This is a common saying among programmers\n" + + "\n" + + "a. Make it fast, make it right, make it work\n" + + "b. Make it work, make it right, make it fast\n" + + "c. Make it fast, make it right, now make it faster"), + new StudentAnswer(""), + new ModelAnswer("b.")), + new Exercise(new QuestionIndex("3.6.1"), new QuestionType("choice"), + new Question("In general, comments should describe,\n" + + "\n" + + "a. WHAT the code does\n" + + "b. WHY the code does something\n" + + "c. HOW the code does something"), + new StudentAnswer(""), + new ModelAnswer("a b. How the code does something should be apparent from the code itself. " + + "However, comments can help the reader in describing WHAT and WHY aspects of the code.")), + + // week 4 + new Exercise(new QuestionIndex("4.1.1"), new QuestionType("choice"), + new Question("Choose the correct statements about models.\n" + + "\n" + + "a. Models are abstractions.\n" + + "b. Models can be used for communication.\n" + + "c. Models can be used for analysis of a problem.\n" + + "d. Generating models from code is useless.\n" + + "e. Models can be used as blueprints for generating code."), + new StudentAnswer(""), + new ModelAnswer("a b c e. Models generated from code can be used for understanding, analysing, " + + "and communicating about the code.")), + new Exercise(new QuestionIndex("4.1.2"), new QuestionType("text"), + new Question("Explain how models (e.g. UML diagrams) can be used in a class project."), + new StudentAnswer(""), + new ModelAnswer("No suggested answer.")), + new Exercise(new QuestionIndex("4.2.1"), new QuestionType("choice"), + new Question("A) Choose the correct statements\n" + + "\n" + + "a. OO is a programming paradigm\n" + + "b. OO guides us in how to structure the solution\n" + + "c. OO is mainly an abstraction mechanism\n" + + "d. OO is a programming language\n" + + "e. OO is modeled after how the objects in real world work"), + new StudentAnswer(""), + new ModelAnswer("a b c e. While many languages support the OO paradigm, OO is not a language itself.")), + new Exercise(new QuestionIndex("4.2.2"), new QuestionType("choice"), + new Question("Choose the correct statements\n" + + "\n" + + "a. Java and C++ are OO languages\n" + + "b. C language follows the Functional Programming paradigm\n" + + "c. Java can be used to write procedural code\n" + + "d. Prolog follows the Logic Programming paradigm"), + new StudentAnswer(""), + new ModelAnswer("a c d. C follows the procedural paradigm. " + + "Yes, we can write procedural code using OO languages e.g., AddressBook-level1.")), + new Exercise(new QuestionIndex("4.2.3"), new QuestionType("choice"), + new Question("OO is a higher level mechanism than the procedural paradigm.\n" + + "\n" + + "a. True\n" + + "b. False"), + new StudentAnswer(""), + new ModelAnswer("a. True. Procedural languages work at simple data structures (e.g., integers, arrays) " + + "and functions level. Because an object is an abstraction over data+related functions, " + + "OO works at a higher level.")), + new Exercise(new QuestionIndex("4.2.4"), new QuestionType("choice"), + new Question("Choose the correct statement\n" + + "\n" + + "a. An object is an encapsulation because it packages data and behavior into one bundle.\n" + + "b. An object is an encapsulation because it lets us think in terms of higher level concepts " + + "such as Students rather than student-related functions and data separately."), + new StudentAnswer(""), + new ModelAnswer("a. The second statement should be: An object is an abstraction encapsulation " + + "because it lets ...")), + new Exercise(new QuestionIndex("4.5.1"), new QuestionType("choice"), + new Question("Which are benefits of exceptions?\n" + + "+\n" + + " a. Exceptions allow us to separate normal code from error handling code.\n" + + " b. Exceptions can prevent problems that happen in the environment.\n" + + " c. Exceptions allow us to handle in one location an error raised in another location."), + new StudentAnswer(""), + new ModelAnswer("a c. Exceptions cannot prevent problems in the environment. " + + "They can only be used to handle and recover from such problems.")), + new Exercise(new QuestionIndex("4.6.1"), new QuestionType("text"), + new Question("Show (in UML notation) an enumeration called WeekDay " + + "to use when the value can only be Monday ... Friday."), + new StudentAnswer(""), + new ModelAnswer("No suggested answer.")), + new Exercise(new QuestionIndex("4.7.1"), new QuestionType("text"), + new Question("In the context of RCS, what is the branching? What is the need for branching?"), + new StudentAnswer(""), + new ModelAnswer("No suggested answer.")), + new Exercise(new QuestionIndex("4.7.2"), new QuestionType("text"), + new Question("In the context of RCS, what is the merging branches? " + + "How can it lead to merge conflicts?"), + new StudentAnswer(""), + new ModelAnswer("No suggested answer.")), + + // week 5 + new Exercise(new QuestionIndex("5.4.1"), new QuestionType("choice"), + new Question("Which of these are suitable as class-level variables?\n" + + "\n" + + "a. system: multi-player Pac Man game, Class: Player, variable: totalScore\n" + + "b. system: eLearning system, class: Course, variable: totalStudents\n" + + "c. system: ToDo manager, class: Task, variable: totalPendingTasks\n" + + "d. system: any, class: ArrayList, variable: total " + + "(i.e., total items in a given ArrayList object)"), + new StudentAnswer(""), + new ModelAnswer("c. totalPendingTasks should not be managed by individual Task objects " + + "and therefore suitable to be maintained as a class-level variable. " + + "The other variables should be managed at instance level " + + "as their value varies from instance to instance. " + + "e.g., totalStudents for one Course object will differ from totalStudents of another.")), + new Exercise(new QuestionIndex("5.6.1"), new QuestionType("choice"), + new Question("Which one of these is recommended not to use in UML diagrams " + + "because it adds more confusion than clarity?\n" + + "\n" + + "a. Composition symbol\n" + + "b. Aggregation symbol"), + new StudentAnswer(""), + new ModelAnswer("b.")), + new Exercise(new QuestionIndex("5.8.1"), new QuestionType("choice"), + new Question("Given below are some requirements of TEAMMATES " + + "(an online peer evaluation system for education). " + + "Which one of these are non-functional requirements?\n" + + "\n" + + "a. The response to any use action should become visible within 5 seconds.\n" + + "b. The application admin should be able to view a log of user activities.\n" + + "c. The source code should be open source.\n" + + "d. A course should be able to have up to 2000 students.\n" + + "e. As a student user, I can view details of my team members " + + "so that I can know who they are.\n" + + "f. The user interface should be intuitive enough for users who are not IT-savvy.\n" + + "g. The product is offered as a free online service."), + new StudentAnswer(""), + new ModelAnswer("a c d f g. (b) are (e) are functions available for a specific user types. " + + "Therefore, they are functional requirements. " + + "(a), (c), (d), (f) and (g) are either constraints on functionality " + + "or constraints on how the project is done, " + + "both of which are considered non-functional requirements.")), + new Exercise(new QuestionIndex("5.9.1"), new QuestionType("choice"), + new Question("What is the key characteristic about brainstorming?\n" + + "\n" + + " a. There should be at least 5 participants.\n" + + " b. All ideas are welcome. There are no bad ideas.\n" + + " c. Only the best people in the team should take part.\n" + + " d. They are a good way to eliminate bad ideas."), + new StudentAnswer(""), + new ModelAnswer("b.")), + + // week 6 + new Exercise(new QuestionIndex("6.1.1"), new QuestionType("text"), + new Question("Discuss pros and cons of developers testing their own code."), + new StudentAnswer(""), + new ModelAnswer("Pros:\n" + + "\n" + + "Can be done early (the earlier we find a bug, the cheaper it is to fix).\n" + + "Can be done at lower levels, for examples, at operation and class level " + + "(testers usually test the system at UI level).\n" + + "It is possible to do more thorough testing because " + + "developers know the expected external behavior " + + "as well as the internal structure of the component.\n" + + "It forces developers to take responsibility for their own work " + + "(they cannot claim that \"testing is the job of the testers\").\n" + + "\n" + + "Cons:\n" + + "\n" + + "A developer may unconsciously test only situations that he knows to work " + + "(i.e. test it too 'gently').\n" + + "A developer may be blind to his own mistakes" + + "(if he did not consider a certain combination of input while writing code, " + + "it is possible for him to miss it again during testing).\n" + + "A developer may have misunderstood what the SUT is supposed to do in the first place.\n" + + "A developer may lack the testing expertise.")), + new Exercise(new QuestionIndex("6.1.2"), new QuestionType("choice"), + new Question("The cost of fixing a bug goes down as we reach the product release.\n" + + "\n" + + "a. True\n" + + "b. False"), + new StudentAnswer(""), + new ModelAnswer("b. False. The cost goes up over time.")), + new Exercise(new QuestionIndex("6.1.3"), new QuestionType("text"), + new Question("Explain why early testing by developers is important."), + new StudentAnswer(""), + new ModelAnswer("No suggested answer.")), + new Exercise(new QuestionIndex("6.4.1"), new QuestionType("choice"), + new Question("Choose the correct statements about abstract classes and concrete classes.\n" + + "\n" + + "a. A concrete class can contain an abstract method.\n" + + "b. An abstract class can contain concrete methods.\n" + + "c. An abstract class need not contain any concrete methods.\n" + + "d. An abstract class cannot be instantiated."), + new StudentAnswer(""), + new ModelAnswer("b c d. A concrete class cannot contain even a single abstract method.")), + + // week 7 + new Exercise(new QuestionIndex("7.2.1"), new QuestionType("choice"), + new Question("Choose the correct statement\n" + + "\n" + + "a. The architecture of a system should be simple enough " + + "for all team members to understand it.\n" + + "b. The architecture is primarily a high-level design of the system.\n" + + "c. The architecture is usually decided by the project manager.\n" + + "d. The architecture can contain details private to a component."), + new StudentAnswer(""), + new ModelAnswer("a b. Not (c) because architecture is usually designed by the Architect. " + + "Not (d) because ... private details of elements—details having to do solely " + + "with internal implementation—are not architectural.")), + new Exercise(new QuestionIndex("7.3.1"), new QuestionType("choice"), + new Question("Choose the correct statements\n" + + "\n" + + "a. A software component can have an API.\n" + + "b. Any method of a class is part of its API.\n" + + "c. Private methods of a class are not part of its API.\n" + + "d. The API forms the contract between the component developer and the component user.\n" + + "e. Sequence diagrams can be used to show how components interact " + + "with each other via APIs."), + new StudentAnswer(""), + new ModelAnswer("a c d e. (b) is incorrect because private methods cannot be a part of the API.")), + new Exercise(new QuestionIndex("7.3.2"), new QuestionType("choice"), + new Question("Defining component APIs early is useful for developing components in parallel.\n" + + "\n" + + "a. True\n" + + "b. False"), + new StudentAnswer(""), + new ModelAnswer("a. True. Yes, once we know the precise behavior expected of each component, " + + "we can start developing them in parallel.\n")), + new Exercise(new QuestionIndex("7.6.1"), new QuestionType("choice"), + new Question("A Calculator program crashes with an ‘assertion failure’ message " + + "when you try to find the square root of a negative number.\n" + + "\n" + + "a. This is a correct use of assertions.\n" + + "b. The application should have terminated with an exception instead.\n" + + "c. The program has a bug.\n" + + "d. All statements above are incorrect."), + new StudentAnswer(""), + new ModelAnswer("c. An assertion failure indicates a bug in the code. " + + "(b) is not acceptable because of the word \"terminated\". " + + "The application should not fail at all for this input. " + + "But it could have used an exception to handle the situation internally.")), + new Exercise(new QuestionIndex("7.6.2"), new QuestionType("choice"), + new Question("Which statements are correct?\n" + + "\n" + + " a. Use assertions to indicate the programmer messed up; " + + "Use exceptions to indicate the user or the environment messed up.\n" + + " b. Use exceptions to indicate the programmer messed up; " + + "Use assertions to indicate the user or the environment messed up."), + new StudentAnswer(""), + new ModelAnswer("a.")), + new Exercise(new QuestionIndex("7.8.1"), new QuestionType("choice"), + new Question("Gradle_is used used for,\n" + + "\n" + + "a. better revision control\n" + + "b. build automation\n" + + "c. UML diagramming\n" + + "d. project collaboration"), + new StudentAnswer(""), + new ModelAnswer("b.")), + + // week 8 + new Exercise(new QuestionIndex("8.4.1"), new QuestionType("text"), + new Question("Explain the link (if any) between regressions and coupling."), + new StudentAnswer(""), + new ModelAnswer("When the system is highly-coupled, the risk of regressions is higher too " + + "e.g. when component A is modified, all components ‘coupled’ to component A " + + "risk ‘unintended behavioral changes’.\n")), + new Exercise(new QuestionIndex("8.4.2"), new QuestionType("text"), + new Question("Discuss the relationship between coupling and testability.\n"), + new StudentAnswer(""), + new ModelAnswer("Coupling decreases testability because if the SUT is coupled to many other components " + + "it becomes difficult to test the SUI in isolation of its dependencies.")), + new Exercise(new QuestionIndex("8.4.3"), new QuestionType("choice"), + new Question("Choose the correct statements.\n" + + "\n" + + "a. As coupling increases, testability decreases.\n" + + "b. As coupling increases, the risk of regression increases.\n" + + "c. As coupling increases, the value of automated regression testing increases.\n" + + "d. As coupling increases, integration becomes easier as everything is connected together.\n" + + "e. As coupling increases, maintainability decreases."), + new StudentAnswer(""), + new ModelAnswer("a b c e. High coupling means either more components require to be integrated at once " + + "in a big-bang fashion (increasing the risk of things going wrong) or more drivers " + + "and stubs are required when integrating incrementally.")), + new Exercise(new QuestionIndex("8.4.4"), new QuestionType("choice"), + new Question("Which of these indicate a coupling between components A and B?\n" + + "\n" + + "a. component A has access to internal structure of component B.\n" + + "b. component A and B are written by the same developer.\n" + + "c. component A calls component B.\n" + + "d. component A receives an object of component B as a parameter.\n" + + "e. component A inherits from component B.\n" + + "f. components A and B have to follow the same data format or communication protocol."), + new StudentAnswer(""), + new ModelAnswer("a c d e f. Being written by the same developer does not imply a coupling.")), + new Exercise(new QuestionIndex("8.4.5"), new QuestionType("choice"), + new Question("“Only the GUI class should interact with the user. " + + "The GUI class should only concern itself with user interactionsâ€. " + + "This statement follows from,\n" + + "\n" + + "a. A software design should promote separation of concerns in a design.\n" + + "b. A software design should increase cohesion of its components.\n" + + "c. A software design should follow single responsibility principle."), + new StudentAnswer(""), + new ModelAnswer("a b c. By making ‘user interaction’ GUI class’ sole responsibility, " + + "we increase its cohesion. This is also in line with separation of concerns " + + "(i.e., we separated the concern of user interaction) " + + "and single responsibility principle (GUI class has only one responsibility).")), + new Exercise(new QuestionIndex("8.4.6"), new QuestionType("choice"), + new Question("Which of these is closest to the meaning of the open-closed principle?\n" + + "\n" + + "a. We should be able to change a software module’s behavior without modifying its code.\n" + + "b. A software module should remain open to modification as long as possible.\n" + + "c. A software module should be open to modification and closed to extension.\n" + + "d. Open source software rocks. Closed source software sucks."), + new StudentAnswer(""), + new ModelAnswer("a. Please refer the handout for the definition of OCP.")), + new Exercise(new QuestionIndex("8.7.1"), new QuestionType("choice"), + new Question("Stubs help us to test a component in isolation from its dependencies.\n" + + "\n" + + "a. True\n" + + "b. False"), + new StudentAnswer(""), + new ModelAnswer("a. True.")), + new Exercise(new QuestionIndex("8.7.2"), new QuestionType("choice"), + new Question("Choose correct statement about dependency injection\n" + + "\n" + + "a. It is a technique for increasing dependencies\n" + + "b. It is useful for unit testing\n" + + "c. It can be done using polymorphism\n" + + "d. It can be used to substitute a component with a stub"), + new StudentAnswer(""), + new ModelAnswer("b c d. " + + "It is a technique we can use to substitute an existing dependency with another, " + + "not increase dependencies. It is useful when you want to test a component in isolation " + + "but the SUT depends on other components. Using dependency injection, " + + "we can substitute those other components with test-friendly stubs. " + + "This is often done using polymorphism.")), + + // week 9 + new Exercise(new QuestionIndex("9.2.1"), new QuestionType("choice"), + new Question("Which one of these is least related to how OO programs achieve polymorphism?\n" + + "\n" + + "a. substitutability\n" + + "b. dynamic binding\n" + + "c. operation overloading\n" + + "d. interfaces\n" + + "e. abstract classes"), + new StudentAnswer(""), + new ModelAnswer("c. Operation overriding is the one that is related, not operation overloading. " + + "Interfaces and abstract classes, although not required, " + + "can be used in achieving polymorphism.")), + new Exercise(new QuestionIndex("9.2.2"), new QuestionType("choice"), + new Question("Top-down design is better than bottom-up design.\n" + + "\n" + + "a. True\n" + + "b. False"), + new StudentAnswer(""), + new ModelAnswer("b. False. Not necessarily. It depends on the situation. " + + "Bottom-up design may be preferable when there are lot of existing components " + + "we want to reuse.")), + new Exercise(new QuestionIndex("9.2.3"), new QuestionType("choice"), + new Question("Agile design camp expects the design to change over the product’s lifetime.\n" + + "\n" + + "a. True\n" + + "b. False"), + new StudentAnswer(""), + new ModelAnswer("a. True. Yes, that is why they do not believe in spending too much time " + + "creating a detailed and full design at the very beginning. " + + "However, the architecture is expected to remain relatively stable " + + "even in the agile design approach.")), + new Exercise(new QuestionIndex("9.2.4"), new QuestionType("choice"), + new Question("If a subclass imposes more restrictive conditions than its parent class, " + + "it violates Liskov Substitution Principle.\n" + + "\n" + + "a. True\n" + + "b. False"), + new StudentAnswer(""), + new ModelAnswer("a. True. If the subclass is more restrictive than the parent class, " + + "code that worked with the parent class may not work with the child class. " + + "Hence, the substitutability does not exist and LSP has been violated.")), + new Exercise(new QuestionIndex("9.2.5"), new QuestionType("choice"), + new Question("Which of these statements is true about the Dependency Inversion Principle.\n" + + "\n" + + "a. It can complicate the design/implementation by introducing extra abstractions, " + + "but it has some benefits.\n" + + "b. It is often used during testing, to replace dependencies with mocks.\n" + + "c. It reduces dependencies in a design.\n" + + "d. It advocates making higher level classes to depend on lower level classes."), + new StudentAnswer(""), + new ModelAnswer("a. Replacing dependencies with mocks is Dependency Injection, not DIP. " + + "DIP does not reduce dependencies, rather, it changes the direction of dependencies. " + + "Yes, it can introduce extra abstractions " + + "but often the benefit can outweigh the extra complications.")), + new Exercise(new QuestionIndex("9.3.1"), new QuestionType("choice"), + new Question("Bidirectional associations, if not implemented properly, " + + "can result in referential integrity violations.\n" + + "\n" + + "a. True\n" + + "b. False"), + new StudentAnswer(""), + new ModelAnswer("a. True. Bidirectional associations require two objects to link to each other. " + + "When one of these links is not consistent with the other, " + + "we have a referential integrity violation.")), + new Exercise(new QuestionIndex("9.3.2"), new QuestionType("choice"), + new Question("Defensive programming,\n" + + "\n" + + "a. can make the program slower.\n" + + "b. can make the code longer.\n" + + "c. can make the code more complex.\n" + + "d. can make the code less susceptible to misuse.\n" + + "e. can require extra effort."), + new StudentAnswer(""), + new ModelAnswer("All. Defensive programming requires a more checks, " + + "possibly making the code longer, more complex, and possibly slower. " + + "Use it only when benefits outweigh costs, which is often.")), + new Exercise(new QuestionIndex("9.3.3"), new QuestionType("choice"), + new Question("Which statements are correct?\n" + + "\n" + + "a. It is not natively supported by Java and C++.\n" + + "b. It is an alternative to OOP.\n" + + "c. It assumes the caller of a method is responsible for " + + "ensuring all preconditions are met."), + new StudentAnswer(""), + new ModelAnswer("a c. DbC is not an alternative to OOP. We can use DbC in an OOP solution.")), + new Exercise(new QuestionIndex("9.4.1"), new QuestionType("choice"), + new Question("Choose correct statements about API documentation.\n" + + "\n" + + "a. They are useful for both developers who use the API " + + "and developers who maintain the API implementation.\n" + + "b. There are tools that can generate API documents from code comments.\n" + + "d. API documentation may contain code examples."), + new StudentAnswer(""), + new ModelAnswer("All.")), + new Exercise(new QuestionIndex("9.4.2"), new QuestionType("choice"), + new Question("It is recommended for developer documents,\n" + + "\n" + + "a. to have separate sections for each type of diagrams " + + "such as class diagrams, sequence diagrams, use case diagrams etc.\n" + + "b. to give a high priority to comprehension too, not stop at comprehensiveness only."), + new StudentAnswer(""), + new ModelAnswer("b. (a) Use diagrams when they help to understand the text descriptions. " + + "Text and diagrams should be used in tandem. " + + "Having separate sections for each diagram type is a sign of generating diagrams " + + "for the sake of having them.\n" + + "\n" + + "(b) Both are important, but lengthy, complete, accurate yet hard to understand documents " + + "are not that useful.")), + new Exercise(new QuestionIndex("9.5.1"), new QuestionType("choice"), + new Question("Which of these gives us the highest intensity of testing?\n" + + "\n" + + " a. 100% statement coverage\n" + + " b. 100% path coverage\n" + + " c. 100% branch coverage\n" + + " d. 100% condition coverage"), + new StudentAnswer(""), + new ModelAnswer("b. 100% path coverage implies all possible execution paths " + + "through the SUT have been tested. This is essentially ‘exhaustive testing’. " + + "While this is very hard to achieve for a non-trivial SUT, " + + "it technically gives us the highest intensity of testing. " + + "If all tests pass at 100% path coverage, the SUT code can be considered ‘bug free’. " + + "However, note that path coverage does not include paths that are missing from the code " + + "altogether because the programmer left them out by mistake.")), + new Exercise(new QuestionIndex("9.5.2"), new QuestionType("choice"), + new Question("In TDD, we write all the test cases before we start writing functional code.\n" + + "\n" + + "a. True\n" + + "b. False"), + new StudentAnswer(""), + new ModelAnswer("b. False. No, not all. We proceed in small steps, " + + "writing tests and functional code in tandem, " + + "but writing the test before we write the corresponding functional code.")), + new Exercise(new QuestionIndex("9.5.3"), new QuestionType("choice"), + new Question("Testing tools such as Junit require us to follow TDD.\n" + + "\n" + + "a. True\n" + + "b. False"), + new StudentAnswer(""), + new ModelAnswer("b. False. They can be used for TDD, but they can be used without TDD too.")), + new Exercise(new QuestionIndex("9.6.1"), new QuestionType("choice"), + new Question("GUI testing is usually easier than API testing because " + + "it doesn’t require any extra coding.\n" + + "\n" + + "a. True\n" + + "b. False"), + new StudentAnswer(""), + new ModelAnswer("b. False.")), + new Exercise(new QuestionIndex("9.6.2"), new QuestionType("choice"), + new Question("Choose the correct statements about system testing and acceptance testing.\n" + + "\n" + + "a. Both system testing and acceptance testing typically involve the whole system.\n" + + "b. System testing is typically more extensive than acceptance testing.\n" + + "c. System testing can include testing for non-functional qualities.\n" + + "d. Acceptance testing typically has more user involvement than system testing.\n" + + "e. In smaller projects, the developers may do system testing as well, " + + "in addition to developer testing.\n" + + "f. If system testing is adequately done, we need not do acceptance testing."), + new StudentAnswer(""), + new ModelAnswer("a b c d e. (b) is correct because system testing can aim to cover all " + + "specified behaviors and can even go beyond the system specification. " + + "Therefore, system testing is typically more extensive than acceptance testing.\n" + + "\n" + + "(f) is incorrect because it is possible for a system to pass system tests " + + "but fail acceptance tests.")), + + // week 10 + new Exercise(new QuestionIndex("10.1.1"), new QuestionType("choice"), + new Question("Pick the odd one out.\n" + + "\n" + + "a. Law of Demeter.\n" + + "b. Don’t add people to a late project.\n" + + "c. Don’t talk to strangers.\n" + + "d. Principle of least knowledge.\n" + + "e. Coupling."), + new StudentAnswer(""), + new ModelAnswer("b. Law of Demeter, which aims to reduce coupling, " + + "is also known as ‘Don’t talk to strangers’ and ‘Principle of least knowledge’.")), + new Exercise(new QuestionIndex("10.1.2"), new QuestionType("text"), + new Question("Do the Brook’s Law apply to a school project? Justify."), + new StudentAnswer(""), + new ModelAnswer("Yes. Adding a new student to a project team " + + "can result in a slow-down of the project for a short period. " + + "This is because the new member needs time to learn the project " + + "and existing members will have to spend time helping the new guy get up to speed. " + + "If the project is already behind schedule and near a deadline, " + + "this could delay the delivery even further.")), + new Exercise(new QuestionIndex("10.1.3"), new QuestionType("choice"), + new Question("Which one of these (all attributed to Fred Brooks, " + + "the author of the famous SE book The Mythical Man-Month), is called the Brook’s law?\n" + + "\n" + + " a. All programmers are optimists.\n" + + " b. Good judgement comes from experience, and experience comes from bad judgement.\n" + + " c. The bearing of a child takes nine months, no matter how many women are assigned.\n" + + " d. Adding more manpower to an already late project makes it even later."), + new StudentAnswer(""), + new ModelAnswer("d.")), + new Exercise(new QuestionIndex("10.3.1"), new QuestionType("choice"), + new Question("Which one of these describes the ‘software design patterns’ concept best?\n" + + "\n" + + " a. Designs that appear repetitively in software.\n" + + " b. Elegant solutions to recurring problems in software design.\n" + + " c. Architectural styles used in applications.\n" + + " d. Some good design techniques proposed by the Gang of Four"), + new StudentAnswer(""), + new ModelAnswer("b.")), + new Exercise(new QuestionIndex("10.3.2"), new QuestionType("choice"), + new Question("When we describe a pattern, we must also specify anti-patterns.\n" + + "\n" + + "a. True\n" + + "b. False"), + new StudentAnswer(""), + new ModelAnswer("b. False. Anti-patterns are related to patterns, " + + "but they are not a ‘must have’ component of a pattern description.")), + new Exercise(new QuestionIndex("10.3.3"), new QuestionType("choice"), + new Question("We use the Singleton pattern when\n" + + "\n" + + "a. we want an a class with a private constructor.\n" + + "b. we want a single class to hold all functionality of the system.\n" + + "c. we want a class with no more than one instance.\n" + + "d. we want to hide internal structure of a component from its clients."), + new StudentAnswer(""), + new ModelAnswer("c.")), + new Exercise(new QuestionIndex("10.4.1"), new QuestionType("choice"), + new Question("Choose correct statements about software frameworks.\n" + + "\n" + + "a. They follow the hollywood principle, otherwise known as ‘inversion of control’\n" + + "b. They come with full or partial implementation.\n" + + "c. They are more concrete than patterns or principles.\n" + + "d. They are often configurable.\n" + + "e. They are reuse mechanisms.\n" + + "f. They are similar to reusable libraries but bigger."), + new StudentAnswer(""), + new ModelAnswer("a b c d e. While both libraries and frameworks are reuse mechanisms, " + + "and both more concrete than principles and patterns, " + + "libraries differ from frameworks in some key ways. " + + "One of them is the ‘inversion of control’ used by frameworks but not libraries. " + + "Furthermore, frameworks do not have to be bigger than libraries all the time.")), + new Exercise(new QuestionIndex("10.4.2"), new QuestionType("choice"), + new Question("Which one of these are frameworks ?\n" + + "\n" + + "a. JUnit\n" + + "b. Eclipse\n" + + "c. Drupal\n" + + "d. Ruby on Rails"), + new StudentAnswer(""), + new ModelAnswer("All. These are frameworks.")), + + // week 11 + new Exercise(new QuestionIndex("11.1.1"), new QuestionType("choice"), + new Question("What is the main difference between a class diagram and and an OO domain model?\n" + + "\n" + + "a. One is about the problem domain while the other is about the solution domain.\n" + + "b. One has more classes than the other.\n" + + "c. One shows more details than the other.\n" + + "d. One is a UML diagram, while the other is not a UML diagram."), + new StudentAnswer(""), + new ModelAnswer("a. Both are UML diagrams, and use the class diagram notation. " + + "While it is true that often a class diagram may have more classes and more details, " + + "the main difference is that the OO domain model describes the problem domain " + + "while the class diagram describes the solution.")), + new Exercise(new QuestionIndex("11.3.1"), new QuestionType("text"), + new Question("Here are some common elements of a design pattern: " + + "Name, Context, Problem, Solution, Anti-patterns (optional), Consequences (optional), " + + "other useful information (optional).\n" + + "\n" + + "Using similar elements, describe a pattern that is not a design pattern. " + + "It must be a pattern you have noticed, not a pattern already documented by others. " + + "You may also give a pattern not related to software.\n" + + "\n" + + "Some examples:\n" + + "- A pattern for testing textual UIs.\n" + + "- A pattern for striking a good bargain at a mall such as Sim-Lim Square."), + new StudentAnswer(""), + new ModelAnswer("No suggested answer.")), + new Exercise(new QuestionIndex("11.4.1"), new QuestionType("choice"), + new Question("Applying the heuristics covered so far, we can determine the precise number of " + + "test cases required to test any given SUT effectively.\n" + + "\n" + + "a. True\n" + + "b. False"), + new StudentAnswer(""), + new ModelAnswer("b. False. These heuristics are, well, heuristics only. " + + "They will help you to make better decisions about test case design. " + + "However, they are speculative in nature (especially, when testing in black-box fashion) " + + "and cannot give you precise number of test cases.")), + new Exercise(new QuestionIndex("11.4.2"), new QuestionType("choice"), + new Question("Which of these contradict the heuristics recommended " + + "when creating test cases with multiple inputs?\n" + + "\n" + + "a. All invalid test inputs must be tested together.\n" + + "b. It is ok to combine valid values for different inputs.\n" + + "c. No more than one invalid test input should be in a given test case.\n" + + "d. Each valid test input should appear at least once " + + "in a test case that doesn’t have any invalid inputs."), + new StudentAnswer(""), + new ModelAnswer("a. If you test all invalid test inputs together, " + + "you will not know if each one of the invalid inputs are handled correctly by the SUT. " + + "This is because most SUTs return an error message " + + "upon encountering the first invalid input.")), + new Exercise(new QuestionIndex("11.6.1"), new QuestionType("choice"), + new Question("Choose the correct statements about agile processes.\n" + + "\n" + + "a. They value working software over comprehensive documentation.\n" + + "b. They value responding to change over following a plan.\n" + + "c. They may not be suitable for some type of projects.\n" + + "d. XP and Scrum are agile processes."), + new StudentAnswer(""), + new ModelAnswer("a b c d.")), + new Exercise(new QuestionIndex("11.7.1"), new QuestionType("choice"), + new Question("Choose the correct statements about the unified process.\n" + + "\n" + + "a. It was conceived by the three amigos who also created UML.\n" + + "b. The Unified process requires the use of UML.\n" + + "c. The Unified process is actually a process framework rather than a fixed process.\n" + + "d. The Unified process can be iterative and incremental"), + new StudentAnswer(""), + new ModelAnswer("a c d. Although UP was created by the same three amigos who created UML, ")) + }; + } + +} diff --git a/src/main/java/seedu/progresschecker/storage/DefaultTasks.java b/src/main/java/seedu/progresschecker/storage/DefaultTasks.java new file mode 100644 index 000000000000..765a38e395a2 --- /dev/null +++ b/src/main/java/seedu/progresschecker/storage/DefaultTasks.java @@ -0,0 +1,233 @@ +package seedu.progresschecker.storage; + +import seedu.progresschecker.model.task.SimplifiedTask; + +//@@author EdwardKSG +/** + * Contains information of the default tasks. + * Using an object to save data to reduce the cost of file I/O. + */ +public class DefaultTasks { + private static final String SUB = "[Submission]"; + private static final String COM = "[Compulsory]"; + private static final String STAR = "★"; + public static SimplifiedTask[] getDefaultTasks() { + return new SimplifiedTask[] { + // WEEK 2 + new SimplifiedTask("LO[W2.2]" + STAR, + "Can use basic features of an IDE (2.2 a~c): checkurlhttps:" + + "//nus-cs2103-ay1718s2.github.io/website/schedule/week2/outcomes.html", + "01/25/2018 23:59"), + new SimplifiedTask("LO[W2.3]" + SUB + STAR + STAR, + "Can use Java Collections: checkurlhttps:" + + "//github.com/nus-cs2103-AY1718S2/addressbook-level1#use-collections-lo-collections", + "01/25/2018 23:59"), + new SimplifiedTask("LO[W2.4]" + SUB + STAR + STAR + STAR, + "Can use Java varargs feature: checkurlhttps:" + + "//github.com/nus-cs2103-AY1718S2/addressbook-level1#use-varargs-lo-varargss", + "01/25/2018 23:59"), + new SimplifiedTask("LO[W2.5]" + STAR + STAR, + "Can automate simple regression testing of text UIs (2.5 a~d): checkurlhttps:" + + "//nus-cs2103-ay1718s2.github.io/website/schedule/week2/outcomes.html", + "01/25/2018 23:59"), + new SimplifiedTask("LO[W2.6]" + SUB + STAR, + "Can use Git to save history (2.6 a~f): checkurlhttps://www.sourcetreeapp.com/", + "01/25/2018 23:59"), + + // WEEK 3 + new SimplifiedTask("LO[W3.1]" + STAR + STAR, + "Can refactor code at a basic level (3.1 a~d, c needs submission): checkurlhttps:" + + "//nus-cs2103-ay1718s2.github.io/website/schedule/week3/outcomes.html", + "02/01/2018 23:59"), + new SimplifiedTask("LO[W3.2]" + SUB + STAR + STAR, + "Can follow a simple style guide (3.2 a~d, c needs submission): checkurlhttps:" + + "//nus-cs2103-ay1718s2.github.io/website/schedule/week3/outcomes.html", + "02/01/2018 23:59"), + new SimplifiedTask("LO[W3.3]" + STAR + STAR, + "Can improve code readability (3.3 a~d): checkurlhttps:" + + "//nus-cs2103-ay1718s2.github.io/website/schedule/week3/outcomes.html", + "02/01/2018 23:59"), + new SimplifiedTask("LO[W3.4]" + STAR + STAR, + "Can use good naming (3.4 a~b): checkurlhttps:" + + "//nus-cs2103-ay1718s2.github.io/website/schedule/week3/outcomes.html", + "02/01/2018 23:59"), + new SimplifiedTask("LO[W3.5]" + STAR + STAR, + "Can avoid unsafe coding practices (3.5 a~c): checkurlhttps:" + + "//nus-cs2103-ay1718s2.github.io/website/schedule/week3/outcomes.html", + "02/01/2018 23:59"), + new SimplifiedTask("LO[W3.6]" + STAR + STAR + STAR, + "Can write good code comments (3.6 a~c): checkurlhttps:" + + "//nus-cs2103-ay1718s2.github.io/website/schedule/week3/outcomes.html", + "02/01/2018 23:59"), + new SimplifiedTask("LO[W3.7]" + STAR + STAR + STAR, + "Can use intermediate level features of an IDE (3.7 a~c): checkurlhttps:" + + "//nus-cs2103-ay1718s2.github.io/website/schedule/week3/outcomes.html", + "02/01/2018 23:59"), + new SimplifiedTask("LO[W3.8]" + SUB + STAR, + "Can communicate with a remote repo (3.8 a~d, c&d need submission): checkurlhttps:" + + "//github.com/nus-cs2103-AY1718S2/samplerepo-things", + "02/01/2018 23:59"), + new SimplifiedTask("LO[W3.9]" + STAR + STAR + STAR, + "Can traverse Git history: checkurlhttps (3.9 a~d):" + + "//nus-cs2103-ay1718s2.github.io/website/schedule/week3/outcomes.html", + "02/01/2018 23:59"), + new SimplifiedTask("LO[W3.10]" + COM + SUB + STAR, + "Can work with a 1KLoC code base: checkurlhttps:" + + "//github.com/nus-cs2103-AY1718S2/addressbook-level1", + "02/01/2018 23:59"), + + // WEEK 4 + new SimplifiedTask("LO[W4.1]" + STAR + STAR + STAR, + "Can explain models (4.1 a~b): checkurlhttps:" + + "//nus-cs2103-ay1718s2.github.io/website/schedule/week4/outcomes.html", + "02/08/2018 23:59"), + new SimplifiedTask("LO[W4.2]" + STAR, + "Can explain OOP (4.2 a~e): checkurlhttps:" + + "//nus-cs2103-ay1718s2.github.io/website/schedule/week4/outcomes.html", + "02/08/2018 23:59"), + new SimplifiedTask("LO[W4.3]" + STAR, + "Can explain basic object/class structures (4.3 a~c): checkurlhttps:" + + "//nus-cs2103-ay1718s2.github.io/website/schedule/week4/outcomes.html", + "02/08/2018 23:59"), + new SimplifiedTask("LO[W4.4]" + STAR, + "Can implement (4.4 a~b): checkurlhttps:" + + "//nus-cs2103-ay1718s2.github.io/website/schedule/week4/outcomes.html", + "02/08/2018 23:59"), + new SimplifiedTask("LO[W4.5]" + SUB + STAR + STAR, + "Can do exception handling in code (4.5 a~d, c needs submission): checkurlhttps:" + + "//github.com/nus-cs2103-AY1718S2/addressbook-level2/blob/master/doc" + + "/LearningOutcomes.md#handle-exceptions-lo-exceptions", + "02/08/2018 23:59"), + new SimplifiedTask("LO[W4.6]" + SUB + STAR + STAR + STAR, + "Can use Java enumerations (4.6 a~b, b needs submission): checkurlhttps:" + + "//github.com/nus-cs2103-AY1718S2/addressbook-level1/#use-enums-lo-enums", + "02/08/2018 23:59"), + new SimplifiedTask("LO[W4.7]" + SUB + STAR, + "Can create PRs on GitHub (4.7 a~e, all need submission: checkurlhttps:" + + "//github.com/nus-cs2103-AY1718S2/samplerepo-pr-practice", + "02/08/2018 23:59"), + + // WEEK 5 + new SimplifiedTask("LO[W5.1]" + STAR + STAR + STAR, + "Can use intermediate-level class diagrams (5.1 a~e): checkurlhttps:" + + "//nus-cs2103-ay1718s2.github.io/website/schedule/week5/outcomes.html", + "02/15/2018 23:59"), + new SimplifiedTask("LO[W5.2]" + STAR + STAR + STAR, + "Can explain single responsibility principle: checkurlhttps:" + + "//github.com/nus-cs2103-AY1718S2/addressbook-level2/blob/master/doc" + + "/LearningOutcomes.md#follow-the-single-responsibility-principle-lo-srp", + "02/15/2018 23:59"), + new SimplifiedTask("LO[W5.3]" + SUB + STAR, + "Can implement inheritance (5.3 a~b, b needs submission): checkurlhttps:" + + "//github.com/nus-cs2103-AY1718S2/addressbook-level2/blob/master/doc" + + "/LearningOutcomes.md#use-inheritance-to-achieve-code-reuse-lo-inheritance", + "02/15/2018 23:59"), + new SimplifiedTask("LO[W5.4]" + SUB + STAR + STAR, + "Can implement class-level members (5.4 a~b, b needs submission): checkurlhttps:" + + "//github.com/nus-cs2103-AY1718S2/addressbook-level2/blob/master/doc" + + "/LearningOutcomes.md#use-class-level-members-lo-classlevel", + "02/15/2018 23:59"), + new SimplifiedTask("LO[W5.5]" + SUB + STAR + STAR + STAR, + "Can implement composition (5.5 a~b, b needs submission): checkurlhttps:" + + "//github.com/nus-cs2103-AY1718S2/addressbook-level2/blob/master/doc" + + "/LearningOutcomes.md#implement-a-class-lo-implementclass", + "02/15/2018 23:59"), + new SimplifiedTask("LO[W5.6]" + STAR + STAR + STAR, + "Can implement aggregation (5.6 a~b): checkurlhttps:" + + "//nus-cs2103-ay1718s2.github.io/website/schedule/week5/outcomes.html", + "02/15/2018 23:59"), + new SimplifiedTask("LO[W5.7]" + STAR + STAR + STAR, + "Can implement overloading (5.7 a~b): checkurlhttps:" + + "//nus-cs2103-ay1718s2.github.io/website/schedule/week5/outcomes.html", + "02/15/2018 23:59"), + new SimplifiedTask("LO[W5.8]" + STAR + STAR, + "Can explain requirements (5.8 a~d, b needs submission): checkurlhttps:" + + "//github.com/nus-cs2103-AY1718S2/addressbook-level3/blob/master/doc" + + "/LearningOutcomes.md#use-non-functional-requirements-lo-nfr", + "02/15/2018 23:59"), + new SimplifiedTask("LO[W5.9]" + STAR + STAR + STAR, + "Can explain some techniques for gathering requirements (5.9 a~g): checkurlhttps:" + + "//nus-cs2103-ay1718s2.github.io/website/schedule/week5/outcomes.html", + "02/15/2018 23:59"), + new SimplifiedTask("LO[W5.10]" + SUB + STAR, + "Can use some techniques for specifying requirements (5.10 a~k, c needs submission): " + + "checkurlhttps://github.com/nus-cs2103-AY1718S2/addressbook-level3/blob/master/doc" + + "/LearningOutcomes.md#utilize-user-stories-lo-userstories", + "02/15/2018 23:59"), + new SimplifiedTask("LO[W5.11]" + COM + SUB + STAR, + "Can work with a 2KLoC code base: checkurlhttps:" + + "//github.com/nus-cs2103-AY1718S2/addressbook-level2", + "02/15/2018 23:59"), + + // WEEK 6 + new SimplifiedTask("LO[W6.1]" + SUB + STAR, + "Can use simple JUnit tests (6.1 a~e, e needs submission): checkurlhttps:" + + "//github.com/nus-cs2103-AY1718S2/addressbook-level2/blob/master/doc" + + "/LearningOutcomes.md#use-junit-to-implement-unit-tests-lo-junit", + "02/22/2018 23:59"), + new SimplifiedTask("LO[W6.2]" + STAR, + "Can follow Forking Workflow (6.2 a~e): checkurlhttps:" + + "//nus-cs2103-ay1718s2.github.io/website/schedule/week6/outcomes.html", + "02/22/2018 23:59"), + new SimplifiedTask("LO[W6.3]" + STAR, + "Can interpret basic sequence diagrams (6.3 a~f): checkurlhttps:" + + "//nus-cs2103-ay1718s2.github.io/website/schedule/week6/outcomes.html", + "02/22/2018 23:59"), + new SimplifiedTask("LO[W6.4]" + SUB + STAR, + "Can implement polymorphism (6.4 a~h, d~h need submission): checkurlhttps:" + + "//github.com/nus-cs2103-AY1718S2/addressbook-level3/blob/master/doc" + + "/LearningOutcomes.md#use-polymorphism-lo-polymorphism", + "02/22/2018 23:59"), + new SimplifiedTask("LO[W6.5]" + SUB + STAR + STAR + STAR, + "Can use JavaFX to build a simple GUI: checkurlhttps:" + + "//github.com/nus-cs2103-AY1718S2/addressbook-level3/blob/master/doc" + + "/LearningOutcomes.md#use-java-fx-for-gui-programming-lo-javafx", + "02/22/2018 23:59"), + + // RECESS + + // WEEK 7 + new SimplifiedTask("LO[W7.1]" + STAR, + "Can record requirements of a product: checkurlhttps:" + + "//nus-cs2103-ay1718s2.github.io/website/schedule/week7/outcomes.html", + "03/08/2018 23:59"), + + // WEEK 8 + new SimplifiedTask("LO[W8.1]" + STAR + STAR + STAR, + "Can apply basic product design guidelines: checkurlhttps:" + + "//nus-cs2103-ay1718s2.github.io/website/schedule/week8/outcomes.html", + "03/15/2018 23:59"), + + // WEEK 9 + new SimplifiedTask("LO[W9.1]" + STAR + STAR, + "Can use models to conceptualize an OO solution: checkurlhttps:" + + "//nus-cs2103-ay1718s2.github.io/website/schedule/week9/outcomes.html", + "03/22/2018 23:59"), + + // WEEK 10 + new SimplifiedTask("LO[W10.1]" + STAR + STAR + STAR, + "Can explain SE principles: checkurlhttps:" + + "//nus-cs2103-ay1718s2.github.io/website/schedule/week10/outcomes.html", + "03/29/2018 23:59"), + + // WEEK 11 + new SimplifiedTask("LO[W11.1]" + STAR + STAR + STAR, + "Can explain object oriented domain models: checkurlhttps:" + + "//nus-cs2103-ay1718s2.github.io/website/schedule/week11/outcomes.html", + "04/05/2018 23:59"), + + // WEEK 12 + new SimplifiedTask("LO[W12.1]" + STAR + STAR + STAR, + "Can explain some UML models: checkurlhttps:" + + "//nus-cs2103-ay1718s2.github.io/website/schedule/week12/outcomes.html", + "04/12/2018 23:59"), + + // WEEK 13 + new SimplifiedTask("LO[W13.1]" + STAR, + "Can demo a product: checkurlhttps:" + + "//nus-cs2103-ay1718s2.github.io/website/schedule/week13/outcomes.html", + "04/19/2018 23:59"), + + }; + } +} diff --git a/src/main/java/seedu/address/storage/JsonUserPrefsStorage.java b/src/main/java/seedu/progresschecker/storage/JsonUserPrefsStorage.java similarity index 83% rename from src/main/java/seedu/address/storage/JsonUserPrefsStorage.java rename to src/main/java/seedu/progresschecker/storage/JsonUserPrefsStorage.java index 4f41aff81251..a8b62dbd8567 100644 --- a/src/main/java/seedu/address/storage/JsonUserPrefsStorage.java +++ b/src/main/java/seedu/progresschecker/storage/JsonUserPrefsStorage.java @@ -1,11 +1,11 @@ -package seedu.address.storage; +package seedu.progresschecker.storage; import java.io.IOException; import java.util.Optional; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.commons.util.JsonUtil; -import seedu.address.model.UserPrefs; +import seedu.progresschecker.commons.exceptions.DataConversionException; +import seedu.progresschecker.commons.util.JsonUtil; +import seedu.progresschecker.model.UserPrefs; /** * A class to access UserPrefs stored in the hard disk as a json file diff --git a/src/main/java/seedu/progresschecker/storage/ProgressCheckerStorage.java b/src/main/java/seedu/progresschecker/storage/ProgressCheckerStorage.java new file mode 100644 index 000000000000..35416bc8d8fd --- /dev/null +++ b/src/main/java/seedu/progresschecker/storage/ProgressCheckerStorage.java @@ -0,0 +1,44 @@ +package seedu.progresschecker.storage; + +import java.io.IOException; +import java.util.Optional; + +import seedu.progresschecker.commons.exceptions.DataConversionException; +import seedu.progresschecker.model.ReadOnlyProgressChecker; + +/** + * Represents a storage for {@link seedu.progresschecker.model.ProgressChecker}. + */ +public interface ProgressCheckerStorage { + + /** + * Returns the file path of the data file. + */ + String getProgressCheckerFilePath(); + + /** + * Returns ProgressChecker data as a {@link ReadOnlyProgressChecker}. + * Returns {@code Optional.empty()} if storage file is not found. + * @throws DataConversionException if the data in storage is not in the expected format. + * @throws IOException if there was any problem when reading from the storage. + */ + Optional<ReadOnlyProgressChecker> readProgressChecker() throws DataConversionException, IOException; + + /** + * @see #getProgressCheckerFilePath() + */ + Optional<ReadOnlyProgressChecker> readProgressChecker(String filePath) throws DataConversionException, IOException; + + /** + * Saves the given {@link ReadOnlyProgressChecker} to the storage. + * @param progressChecker cannot be null. + * @throws IOException if there was any problem writing to the file. + */ + void saveProgressChecker(ReadOnlyProgressChecker progressChecker) throws IOException; + + /** + * @see #saveProgressChecker(ReadOnlyProgressChecker) + */ + void saveProgressChecker(ReadOnlyProgressChecker progressChecker, String filePath) throws IOException; + +} diff --git a/src/main/java/seedu/progresschecker/storage/Storage.java b/src/main/java/seedu/progresschecker/storage/Storage.java new file mode 100644 index 000000000000..8c9ff391fd63 --- /dev/null +++ b/src/main/java/seedu/progresschecker/storage/Storage.java @@ -0,0 +1,38 @@ +package seedu.progresschecker.storage; + +import java.io.IOException; +import java.util.Optional; + +import seedu.progresschecker.commons.events.model.ProgressCheckerChangedEvent; +import seedu.progresschecker.commons.events.storage.DataSavingExceptionEvent; +import seedu.progresschecker.commons.exceptions.DataConversionException; +import seedu.progresschecker.model.ReadOnlyProgressChecker; +import seedu.progresschecker.model.UserPrefs; + +/** + * API of the Storage component + */ +public interface Storage extends ProgressCheckerStorage, UserPrefsStorage { + + @Override + Optional<UserPrefs> readUserPrefs() throws DataConversionException, IOException; + + @Override + void saveUserPrefs(UserPrefs userPrefs) throws IOException; + + @Override + String getProgressCheckerFilePath(); + + @Override + Optional<ReadOnlyProgressChecker> readProgressChecker() throws DataConversionException, IOException; + + @Override + void saveProgressChecker(ReadOnlyProgressChecker progressChecker) throws IOException; + + /** + * Saves the current version of the Address Book to the hard disk. + * Creates the data file if it is missing. + * Raises {@link DataSavingExceptionEvent} if there was an error during saving. + */ + void handleProgressCheckerChangedEvent(ProgressCheckerChangedEvent abce); +} diff --git a/src/main/java/seedu/progresschecker/storage/StorageManager.java b/src/main/java/seedu/progresschecker/storage/StorageManager.java new file mode 100644 index 000000000000..693777deb961 --- /dev/null +++ b/src/main/java/seedu/progresschecker/storage/StorageManager.java @@ -0,0 +1,93 @@ +package seedu.progresschecker.storage; + +import java.io.IOException; +import java.util.Optional; +import java.util.logging.Logger; + +import com.google.common.eventbus.Subscribe; + +import seedu.progresschecker.commons.core.ComponentManager; +import seedu.progresschecker.commons.core.LogsCenter; +import seedu.progresschecker.commons.events.model.ProgressCheckerChangedEvent; +import seedu.progresschecker.commons.events.storage.DataSavingExceptionEvent; +import seedu.progresschecker.commons.exceptions.DataConversionException; +import seedu.progresschecker.model.ReadOnlyProgressChecker; +import seedu.progresschecker.model.UserPrefs; + +/** + * Manages storage of ProgressChecker data in local storage. + */ +public class StorageManager extends ComponentManager implements Storage { + + private static final Logger logger = LogsCenter.getLogger(StorageManager.class); + private ProgressCheckerStorage progressCheckerStorage; + private UserPrefsStorage userPrefsStorage; + + + public StorageManager(ProgressCheckerStorage progressCheckerStorage, UserPrefsStorage userPrefsStorage) { + super(); + this.progressCheckerStorage = progressCheckerStorage; + this.userPrefsStorage = userPrefsStorage; + } + + // ================ UserPrefs methods ============================== + + @Override + public String getUserPrefsFilePath() { + return userPrefsStorage.getUserPrefsFilePath(); + } + + @Override + public Optional<UserPrefs> readUserPrefs() throws DataConversionException, IOException { + return userPrefsStorage.readUserPrefs(); + } + + @Override + public void saveUserPrefs(UserPrefs userPrefs) throws IOException { + userPrefsStorage.saveUserPrefs(userPrefs); + } + + + // ================ ProgressChecker methods ============================== + + @Override + public String getProgressCheckerFilePath() { + return progressCheckerStorage.getProgressCheckerFilePath(); + } + + @Override + public Optional<ReadOnlyProgressChecker> readProgressChecker() throws DataConversionException, IOException { + return readProgressChecker(progressCheckerStorage.getProgressCheckerFilePath()); + } + + @Override + public Optional<ReadOnlyProgressChecker> readProgressChecker(String filePath) + throws DataConversionException, IOException { + logger.fine("Attempting to read data from file: " + filePath); + return progressCheckerStorage.readProgressChecker(filePath); + } + + @Override + public void saveProgressChecker(ReadOnlyProgressChecker progressChecker) throws IOException { + saveProgressChecker(progressChecker, progressCheckerStorage.getProgressCheckerFilePath()); + } + + @Override + public void saveProgressChecker(ReadOnlyProgressChecker progressChecker, String filePath) throws IOException { + logger.fine("Attempting to write to data file: " + filePath); + progressCheckerStorage.saveProgressChecker(progressChecker, filePath); + } + + + @Override + @Subscribe + public void handleProgressCheckerChangedEvent(ProgressCheckerChangedEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event, "Local data changed, saving to file")); + try { + saveProgressChecker(event.data); + } catch (IOException e) { + raise(new DataSavingExceptionEvent(e)); + } + } + +} diff --git a/src/main/java/seedu/progresschecker/storage/TestTasks.java b/src/main/java/seedu/progresschecker/storage/TestTasks.java new file mode 100644 index 000000000000..836e9220ba76 --- /dev/null +++ b/src/main/java/seedu/progresschecker/storage/TestTasks.java @@ -0,0 +1,31 @@ +package seedu.progresschecker.storage; + +import seedu.progresschecker.model.task.SimplifiedTask; + +//@@author EdwardKSG +/** + * Contains information of test tasks. + */ +public class TestTasks { + public static SimplifiedTask[] getTestTasks() { + return new SimplifiedTask[] { + new SimplifiedTask("LO[W3.10][Compulsory][Submission]★", + "Work with a 1KLoC code base: checkurlhttps://nus-cs2103-ay1718s2.github.io/website/schedule" + + "/week3/outcomes.html", + "02/01/2018 23:59"), + new SimplifiedTask("LO[W4.1]★★★", + "Can explain models: checkurlhttps://nus-cs2103-ay1718s2.github.io/website/schedule/week4" + + "/outcomes.html", + "02/08/2018 23:59"), + new SimplifiedTask("LO[W5.11][Compulsory][Submission]★", + "Work with a 2KLoC code base: checkurlhttps://nus-cs2103-ay1718s2.github.io/website/schedule" + + "/week5/outcomes.html", + "02/15/2018 23:59"), + new SimplifiedTask("LO[W6.5][Submission]★★★", + "Can use JavaFX to build a simple GUI: checkurlhttps://nus-cs2103-ay1718s2.github.io/website" + + "/schedule/week6/outcomes.html", + "02/22/2018 23:59") + }; + } + +} diff --git a/src/main/java/seedu/address/storage/UserPrefsStorage.java b/src/main/java/seedu/progresschecker/storage/UserPrefsStorage.java similarity index 71% rename from src/main/java/seedu/address/storage/UserPrefsStorage.java rename to src/main/java/seedu/progresschecker/storage/UserPrefsStorage.java index 146477fad976..5f6e7e7ac103 100644 --- a/src/main/java/seedu/address/storage/UserPrefsStorage.java +++ b/src/main/java/seedu/progresschecker/storage/UserPrefsStorage.java @@ -1,13 +1,13 @@ -package seedu.address.storage; +package seedu.progresschecker.storage; import java.io.IOException; import java.util.Optional; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.model.UserPrefs; +import seedu.progresschecker.commons.exceptions.DataConversionException; +import seedu.progresschecker.model.UserPrefs; /** - * Represents a storage for {@link seedu.address.model.UserPrefs}. + * Represents a storage for {@link seedu.progresschecker.model.UserPrefs}. */ public interface UserPrefsStorage { @@ -25,7 +25,7 @@ public interface UserPrefsStorage { Optional<UserPrefs> readUserPrefs() throws DataConversionException, IOException; /** - * Saves the given {@link seedu.address.model.UserPrefs} to the storage. + * Saves the given {@link seedu.progresschecker.model.UserPrefs} to the storage. * @param userPrefs cannot be null. * @throws IOException if there was any problem writing to the file. */ diff --git a/src/main/java/seedu/progresschecker/storage/XmlAdaptedAssignee.java b/src/main/java/seedu/progresschecker/storage/XmlAdaptedAssignee.java new file mode 100644 index 000000000000..586185ce3e17 --- /dev/null +++ b/src/main/java/seedu/progresschecker/storage/XmlAdaptedAssignee.java @@ -0,0 +1,59 @@ +package seedu.progresschecker.storage; + +import javax.xml.bind.annotation.XmlValue; + +import seedu.progresschecker.commons.exceptions.IllegalValueException; +import seedu.progresschecker.model.issues.Assignees; + +/** + * JAXB-friendly adapted version of the Assignee. + */ +public class XmlAdaptedAssignee { + + @XmlValue + private String assignee; + + /** + * Constructs an XmlAdaptedAssignee. + * This is the no-arg constructor that is required by JAXB. + */ + public XmlAdaptedAssignee() {} + + /** + * Constructs a {@code XmlAdaptedAssignee} with the given {@code assignee}. + */ + public XmlAdaptedAssignee(String assignee) { + this.assignee = assignee; + } + + /** + * Converts a given Assignee into this class for JAXB use. + * + * @param source future changes to this will not affect the created + */ + public XmlAdaptedAssignee(Assignees source) { + assignee = source.fullAssignees; + } + + /** + * Converts this jaxb-friendly adapted assignee object into the model's Assignee object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted person + */ + public Assignees toModelType() throws IllegalValueException { + return new Assignees(assignee); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlAdaptedAssignee)) { + return false; + } + + return assignee.equals(((XmlAdaptedAssignee) other).assignee); + } +} diff --git a/src/main/java/seedu/progresschecker/storage/XmlAdaptedExercise.java b/src/main/java/seedu/progresschecker/storage/XmlAdaptedExercise.java new file mode 100644 index 000000000000..754743a34d77 --- /dev/null +++ b/src/main/java/seedu/progresschecker/storage/XmlAdaptedExercise.java @@ -0,0 +1,131 @@ +package seedu.progresschecker.storage; + +import java.util.Objects; + +import javax.xml.bind.annotation.XmlElement; + +import seedu.progresschecker.commons.exceptions.IllegalValueException; +import seedu.progresschecker.model.exercise.Exercise; +import seedu.progresschecker.model.exercise.ModelAnswer; +import seedu.progresschecker.model.exercise.Question; +import seedu.progresschecker.model.exercise.QuestionIndex; +import seedu.progresschecker.model.exercise.QuestionType; +import seedu.progresschecker.model.exercise.StudentAnswer; + +//@@author iNekox3 +/** + * JAXB-friendly version of the Exercise. + */ +public class XmlAdaptedExercise { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Exercise's %s field is missing!"; + + @XmlElement(required = true) + private String questionIndex; + @XmlElement(required = true) + private String questionType; + @XmlElement(required = true) + private String question; + @XmlElement(required = true) + private String studentAnswer; + @XmlElement(required = true) + private String modelAnswer; + + /** + * Constructs an XmlAdaptedExercise. + * This is the no-arg constructor that is required by JAXB. + */ + public XmlAdaptedExercise() {} + + /** + * Constructs an {@code XmlAdaptedExercise} with the given exercise details. + */ + public XmlAdaptedExercise( + String questionIndex, String questionType, String question, + String studentAnswer, String modelAnswer) { + this.questionIndex = questionIndex; + this.questionType = questionType; + this.question = question; + this.studentAnswer = studentAnswer; + this.modelAnswer = modelAnswer; + } + + /** + * Converts a given Exercise into this class for JAXB use. + * + * @param source future changes to this will not affect the created XmlAdaptedExercise + */ + public XmlAdaptedExercise(Exercise source) { + questionIndex = source.getQuestionIndex().value; + questionType = source.getQuestionType().value; + question = source.getQuestion().value; + studentAnswer = source.getStudentAnswer().value; + modelAnswer = source.getModelAnswer().value; + } + + /** + * Converts this jaxb-friendly adapted exercise object into the model's Exercise object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted exercise + */ + public Exercise toModelType() throws IllegalValueException { + if (this.questionIndex == null) { + throw new IllegalValueException( + String.format(MISSING_FIELD_MESSAGE_FORMAT, + QuestionIndex.class.getSimpleName())); + } + if (!QuestionIndex.isValidIndex(this.questionIndex)) { + throw new IllegalValueException(QuestionIndex.MESSAGE_INDEX_CONSTRAINTS); + } + final QuestionIndex questionIndex = new QuestionIndex(this.questionIndex); + + if (this.questionType == null) { + throw new IllegalValueException( + String.format(MISSING_FIELD_MESSAGE_FORMAT, + QuestionType.class.getSimpleName())); + } + if (!QuestionType.isValidType(this.questionType)) { + throw new IllegalValueException(QuestionType.MESSAGE_TYPE_CONSTRAINTS); + } + final QuestionType questionType = new QuestionType(this.questionType); + + if (this.question == null) { + throw new IllegalValueException( + String.format(MISSING_FIELD_MESSAGE_FORMAT, + Question.class.getSimpleName())); + } + final Question question = new Question(this.question); + + if (this.studentAnswer == null) { + throw new IllegalValueException( + String.format(MISSING_FIELD_MESSAGE_FORMAT, + StudentAnswer.class.getSimpleName())); + } + final StudentAnswer studentAnswer = new StudentAnswer(this.studentAnswer); + + if (this.modelAnswer == null) { + throw new IllegalValueException( + String.format(MISSING_FIELD_MESSAGE_FORMAT, + ModelAnswer.class.getSimpleName())); + } + final ModelAnswer modelAnswer = new ModelAnswer(this.modelAnswer); + + return new Exercise(questionIndex, questionType, question, studentAnswer, modelAnswer); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlAdaptedExercise)) { + return false; + } + + XmlAdaptedExercise otherExercise = (XmlAdaptedExercise) other; + return Objects.equals(questionIndex, otherExercise.questionIndex) + && Objects.equals(questionType, otherExercise.questionType) + && Objects.equals(question, otherExercise.question); + } +} diff --git a/src/main/java/seedu/progresschecker/storage/XmlAdaptedIssue.java b/src/main/java/seedu/progresschecker/storage/XmlAdaptedIssue.java new file mode 100644 index 000000000000..e392fdea52db --- /dev/null +++ b/src/main/java/seedu/progresschecker/storage/XmlAdaptedIssue.java @@ -0,0 +1,129 @@ +package seedu.progresschecker.storage; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import javax.xml.bind.annotation.XmlElement; + +import seedu.progresschecker.commons.exceptions.IllegalValueException; +import seedu.progresschecker.model.issues.Assignees; +import seedu.progresschecker.model.issues.Body; +import seedu.progresschecker.model.issues.Issue; +import seedu.progresschecker.model.issues.Labels; +import seedu.progresschecker.model.issues.Milestone; +import seedu.progresschecker.model.issues.Title; + +//@@author adityaa1998 +/** + * JAXB-friendly version of the Issue. + */ +public class XmlAdaptedIssue { + + public static final String MISSING_FIELD_MESSAGE_FORMAT = "Issue's %s field is missing!"; + + @XmlElement(required = true) + private String title; + @XmlElement(required = false) + private String body; + @XmlElement(required = false) + private String milestone; + + @XmlElement + private List<XmlAdaptedAssignee> assignees = new ArrayList<>(); + + @XmlElement + private List<XmlAdaptedLabel> labelled = new ArrayList<>(); + + /** + * Constructs an XmlAdaptedIssue. + * This is the no-arg constructor that is required by JAXB. + */ + public XmlAdaptedIssue() {} + + /** + * Constructs an {@code XmlAdaptedPerson} with the given person details. + */ + public XmlAdaptedIssue( + String title, String body, String milestone, + List<XmlAdaptedAssignee> assignees, List<XmlAdaptedLabel> labelled) { + this.title = title; + this.body = body; + this.milestone = milestone; + + if (assignees != null) { + this.assignees = new ArrayList<>(assignees); + } + if (labelled != null) { + this.labelled = new ArrayList<>(labelled); + } + } + + /** + * Converts a given Issue into this class for JAXB use. + * + * @param source future changes to this will not affect the created XmlAdaptedIssue + */ + public XmlAdaptedIssue(Issue source) { + title = source.getTitle().fullMessage; + body = source.getBody().fullBody; + if (source.getMilestone() == null) { + milestone = ""; + } else { + milestone = source.getMilestone().fullMilestone; + } + assignees = new ArrayList<>(); + for (Assignees assignee : source.getAssignees()) { + assignees.add(new XmlAdaptedAssignee(assignee)); + } + for (Labels label : source.getLabelsList()) { + labelled.add(new XmlAdaptedLabel(label)); + } + } + + /** + * Converts this jaxb-friendly adapted issue object into the model's Issue object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted issue + */ + public Issue toModelType() throws IllegalValueException { + final List<Assignees> issueAssignees = new ArrayList<>(); + final List<Labels> issueLabels = new ArrayList<>(); + for (XmlAdaptedAssignee assigneeIssue : assignees) { + issueAssignees.add(assigneeIssue.toModelType()); + } + + for (XmlAdaptedLabel labelIssue : labelled) { + issueLabels.add(labelIssue.toModelType()); + } + + if (this.title == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Title.class.getSimpleName())); + } + final Title title = new Title(this.title); + + final Body body = new Body(this.body); + + final Milestone milestone = new Milestone(this.milestone); + + return new Issue(title, issueAssignees, milestone, body, issueLabels); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlAdaptedIssue)) { + return false; + } + + XmlAdaptedIssue otherIssue = (XmlAdaptedIssue) other; + return Objects.equals(title, otherIssue.title) + && Objects.equals(body, otherIssue.body) + && Objects.equals(milestone, otherIssue.milestone) + && Objects.equals(assignees, otherIssue.assignees) + && Objects.equals(labelled, otherIssue.labelled); + } +} diff --git a/src/main/java/seedu/progresschecker/storage/XmlAdaptedLabel.java b/src/main/java/seedu/progresschecker/storage/XmlAdaptedLabel.java new file mode 100644 index 000000000000..828d678333b7 --- /dev/null +++ b/src/main/java/seedu/progresschecker/storage/XmlAdaptedLabel.java @@ -0,0 +1,59 @@ +package seedu.progresschecker.storage; + +import javax.xml.bind.annotation.XmlValue; + +import seedu.progresschecker.commons.exceptions.IllegalValueException; +import seedu.progresschecker.model.issues.Labels; + +/** + * JAXB-friendly adapted version of the Labe;. + */ +public class XmlAdaptedLabel { + + @XmlValue + private String label; + + /** + * Constructs an XmlAdaptedLabel. + * This is the no-arg constructor that is required by JAXB. + */ + public XmlAdaptedLabel() {} + + /** + * Constructs a {@code XmlAdaptedLabel} with the given {@code assignee}. + */ + public XmlAdaptedLabel(String label) { + this.label = label; + } + + /** + * Converts a given Assignee into this class for JAXB use. + * + * @param source future changes to this will not affect the created + */ + public XmlAdaptedLabel(Labels source) { + label = source.fullLabels; + } + + /** + * Converts this jaxb-friendly adapted label object into the model's Label object. + * + * @throws IllegalValueException if there were any data constraints violated in the adapted label + */ + public Labels toModelType() throws IllegalValueException { + return new Labels(label); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlAdaptedLabel)) { + return false; + } + + return label.equals(((XmlAdaptedLabel) other).label); + } +} diff --git a/src/main/java/seedu/address/storage/XmlAdaptedPerson.java b/src/main/java/seedu/progresschecker/storage/XmlAdaptedPerson.java similarity index 61% rename from src/main/java/seedu/address/storage/XmlAdaptedPerson.java rename to src/main/java/seedu/progresschecker/storage/XmlAdaptedPerson.java index 2cd92dc4fd20..da7eaf06da54 100644 --- a/src/main/java/seedu/address/storage/XmlAdaptedPerson.java +++ b/src/main/java/seedu/progresschecker/storage/XmlAdaptedPerson.java @@ -1,4 +1,4 @@ -package seedu.address.storage; +package seedu.progresschecker.storage; import java.util.ArrayList; import java.util.HashSet; @@ -8,13 +8,15 @@ import javax.xml.bind.annotation.XmlElement; -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.person.Address; -import seedu.address.model.person.Email; -import seedu.address.model.person.Name; -import seedu.address.model.person.Person; -import seedu.address.model.person.Phone; -import seedu.address.model.tag.Tag; +import seedu.progresschecker.commons.exceptions.IllegalValueException; +import seedu.progresschecker.model.person.Email; +import seedu.progresschecker.model.person.GithubUsername; +import seedu.progresschecker.model.person.Major; +import seedu.progresschecker.model.person.Name; +import seedu.progresschecker.model.person.Person; +import seedu.progresschecker.model.person.Phone; +import seedu.progresschecker.model.person.Year; +import seedu.progresschecker.model.tag.Tag; /** * JAXB-friendly version of the Person. @@ -30,7 +32,11 @@ public class XmlAdaptedPerson { @XmlElement(required = true) private String email; @XmlElement(required = true) - private String address; + private String username; + @XmlElement(required = true) + private String major; + @XmlElement(required = true) + private String year; @XmlElement private List<XmlAdaptedTag> tagged = new ArrayList<>(); @@ -44,11 +50,15 @@ public XmlAdaptedPerson() {} /** * Constructs an {@code XmlAdaptedPerson} with the given person details. */ - public XmlAdaptedPerson(String name, String phone, String email, String address, List<XmlAdaptedTag> tagged) { + public XmlAdaptedPerson( + String name, String phone, String email, String username, String major, String year, + List<XmlAdaptedTag> tagged) { this.name = name; this.phone = phone; this.email = email; - this.address = address; + this.username = username; + this.major = major; + this.year = year; if (tagged != null) { this.tagged = new ArrayList<>(tagged); } @@ -63,7 +73,9 @@ public XmlAdaptedPerson(Person source) { name = source.getName().fullName; phone = source.getPhone().value; email = source.getEmail().value; - address = source.getAddress().value; + username = source.getUsername().username; + major = source.getMajor().value; + year = source.getYear().value; tagged = new ArrayList<>(); for (Tag tag : source.getTags()) { tagged.add(new XmlAdaptedTag(tag)); @@ -89,6 +101,15 @@ public Person toModelType() throws IllegalValueException { } final Name name = new Name(this.name); + if (this.username == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, + GithubUsername.class.getSimpleName())); + } + if (!Name.isValidName(this.username)) { + throw new IllegalValueException(GithubUsername.MESSAGE_USERNAME_CONSTRAINTS); + } + final GithubUsername username = new GithubUsername(this.username); + if (this.phone == null) { throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Phone.class.getSimpleName())); } @@ -105,16 +126,24 @@ public Person toModelType() throws IllegalValueException { } final Email email = new Email(this.email); - if (this.address == null) { - throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Address.class.getSimpleName())); + if (this.major == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Major.class.getSimpleName())); + } + if (!Major.isValidMajor(this.major)) { + throw new IllegalValueException(Major.MESSAGE_MAJOR_CONSTRAINTS); + } + final Major major = new Major(this.major); + + if (this.year == null) { + throw new IllegalValueException(String.format(MISSING_FIELD_MESSAGE_FORMAT, Year.class.getSimpleName())); } - if (!Address.isValidAddress(this.address)) { - throw new IllegalValueException(Address.MESSAGE_ADDRESS_CONSTRAINTS); + if (!Year.isValidYear(this.year)) { + throw new IllegalValueException(Year.MESSAGE_YEAR_CONSTRAINTS); } - final Address address = new Address(this.address); + final Year year = new Year(this.year); final Set<Tag> tags = new HashSet<>(personTags); - return new Person(name, phone, email, address, tags); + return new Person(name, phone, email, username, major, year, tags); } @Override @@ -131,7 +160,9 @@ public boolean equals(Object other) { return Objects.equals(name, otherPerson.name) && Objects.equals(phone, otherPerson.phone) && Objects.equals(email, otherPerson.email) - && Objects.equals(address, otherPerson.address) + && Objects.equals(username, otherPerson.username) + && Objects.equals(major, otherPerson.major) + && Objects.equals(year, otherPerson.year) && tagged.equals(otherPerson.tagged); } } diff --git a/src/main/java/seedu/address/storage/XmlAdaptedTag.java b/src/main/java/seedu/progresschecker/storage/XmlAdaptedTag.java similarity index 90% rename from src/main/java/seedu/address/storage/XmlAdaptedTag.java rename to src/main/java/seedu/progresschecker/storage/XmlAdaptedTag.java index d3e2d8be9c4f..b4e50569d1da 100644 --- a/src/main/java/seedu/address/storage/XmlAdaptedTag.java +++ b/src/main/java/seedu/progresschecker/storage/XmlAdaptedTag.java @@ -1,9 +1,9 @@ -package seedu.address.storage; +package seedu.progresschecker.storage; import javax.xml.bind.annotation.XmlValue; -import seedu.address.commons.exceptions.IllegalValueException; -import seedu.address.model.tag.Tag; +import seedu.progresschecker.commons.exceptions.IllegalValueException; +import seedu.progresschecker.model.tag.Tag; /** * JAXB-friendly adapted version of the Tag. diff --git a/src/main/java/seedu/address/storage/XmlFileStorage.java b/src/main/java/seedu/progresschecker/storage/XmlFileStorage.java similarity index 55% rename from src/main/java/seedu/address/storage/XmlFileStorage.java rename to src/main/java/seedu/progresschecker/storage/XmlFileStorage.java index 289fcb63038e..1baa4a298e68 100644 --- a/src/main/java/seedu/address/storage/XmlFileStorage.java +++ b/src/main/java/seedu/progresschecker/storage/XmlFileStorage.java @@ -1,36 +1,36 @@ -package seedu.address.storage; +package seedu.progresschecker.storage; import java.io.File; import java.io.FileNotFoundException; import javax.xml.bind.JAXBException; -import seedu.address.commons.exceptions.DataConversionException; -import seedu.address.commons.util.XmlUtil; +import seedu.progresschecker.commons.exceptions.DataConversionException; +import seedu.progresschecker.commons.util.XmlUtil; /** - * Stores addressbook data in an XML file + * Stores progresschecker data in an XML file */ public class XmlFileStorage { /** - * Saves the given addressbook data to the specified file. + * Saves the given progresschecker data to the specified file. */ - public static void saveDataToFile(File file, XmlSerializableAddressBook addressBook) + public static void saveDataToFile(File file, XmlSerializableProgressChecker progressChecker) throws FileNotFoundException { try { - XmlUtil.saveDataToFile(file, addressBook); + XmlUtil.saveDataToFile(file, progressChecker); } catch (JAXBException e) { throw new AssertionError("Unexpected exception " + e.getMessage()); } } /** - * Returns address book in the file or an empty address book + * Returns ProgressChecker in the file or an empty ProgressChecker */ - public static XmlSerializableAddressBook loadDataFromSaveFile(File file) throws DataConversionException, + public static XmlSerializableProgressChecker loadDataFromSaveFile(File file) throws DataConversionException, FileNotFoundException { try { - return XmlUtil.getDataFromFile(file, XmlSerializableAddressBook.class); + return XmlUtil.getDataFromFile(file, XmlSerializableProgressChecker.class); } catch (JAXBException e) { throw new DataConversionException(e); } diff --git a/src/main/java/seedu/progresschecker/storage/XmlProgressCheckerStorage.java b/src/main/java/seedu/progresschecker/storage/XmlProgressCheckerStorage.java new file mode 100644 index 000000000000..c8d7bd463817 --- /dev/null +++ b/src/main/java/seedu/progresschecker/storage/XmlProgressCheckerStorage.java @@ -0,0 +1,89 @@ +package seedu.progresschecker.storage; + +import static java.util.Objects.requireNonNull; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Optional; +import java.util.logging.Logger; + +import seedu.progresschecker.commons.core.LogsCenter; +import seedu.progresschecker.commons.exceptions.DataConversionException; +import seedu.progresschecker.commons.exceptions.IllegalValueException; +import seedu.progresschecker.commons.util.FileUtil; +import seedu.progresschecker.logic.commands.exceptions.CommandException; +import seedu.progresschecker.model.ReadOnlyProgressChecker; + +/** + * A class to access ProgressChecker data stored as an xml file on the hard disk. + */ +public class XmlProgressCheckerStorage implements ProgressCheckerStorage { + + private static final Logger logger = LogsCenter.getLogger(XmlProgressCheckerStorage.class); + + private String filePath; + + public XmlProgressCheckerStorage(String filePath) { + this.filePath = filePath; + } + + public String getProgressCheckerFilePath() { + return filePath; + } + + @Override + public Optional<ReadOnlyProgressChecker> readProgressChecker() throws DataConversionException, IOException { + return readProgressChecker(filePath); + } + + /** + * Similar to {@link #readProgressChecker()} + * @param filePath location of the data. Cannot be null + * @throws DataConversionException if the file is not in the correct format. + */ + public Optional<ReadOnlyProgressChecker> readProgressChecker(String filePath) throws DataConversionException, + FileNotFoundException { + requireNonNull(filePath); + + File progressCheckerFile = new File(filePath); + + if (!progressCheckerFile.exists()) { + logger.info("ProgressChecker file " + progressCheckerFile + " not found"); + return Optional.empty(); + } + + XmlSerializableProgressChecker xmlProgressChecker = XmlFileStorage.loadDataFromSaveFile(new File(filePath)); + try { + return Optional.of(xmlProgressChecker.toModelType()); + } catch (IllegalValueException ive) { + logger.info("Illegal values found in " + progressCheckerFile + ": " + ive.getMessage()); + throw new DataConversionException(ive); + } catch (IOException ie) { + logger.info("Illegal values found in " + progressCheckerFile + ": " + ie.getMessage()); + throw new DataConversionException(ie); + } catch (CommandException ce) { + logger.info("Illegal values found in " + progressCheckerFile + ": " + ce.getMessage()); + throw new DataConversionException(ce); + } + } + + @Override + public void saveProgressChecker(ReadOnlyProgressChecker progressChecker) throws IOException { + saveProgressChecker(progressChecker, filePath); + } + + /** + * Similar to {@link #saveProgressChecker(ReadOnlyProgressChecker)} + * @param filePath location of the data. Cannot be null + */ + public void saveProgressChecker(ReadOnlyProgressChecker progressChecker, String filePath) throws IOException { + requireNonNull(progressChecker); + requireNonNull(filePath); + + File file = new File(filePath); + FileUtil.createIfMissing(file); + XmlFileStorage.saveDataToFile(file, new XmlSerializableProgressChecker(progressChecker)); + } + +} diff --git a/src/main/java/seedu/progresschecker/storage/XmlSerializableProgressChecker.java b/src/main/java/seedu/progresschecker/storage/XmlSerializableProgressChecker.java new file mode 100644 index 000000000000..174c5d03df22 --- /dev/null +++ b/src/main/java/seedu/progresschecker/storage/XmlSerializableProgressChecker.java @@ -0,0 +1,88 @@ +package seedu.progresschecker.storage; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; + +import seedu.progresschecker.commons.exceptions.IllegalValueException; +import seedu.progresschecker.logic.commands.exceptions.CommandException; +import seedu.progresschecker.model.ProgressChecker; +import seedu.progresschecker.model.ReadOnlyProgressChecker; + +/** + * An Immutable ProgressChecker that is serializable to XML format + */ +@XmlRootElement(name = "progresschecker") +public class XmlSerializableProgressChecker { + + @XmlElement + private List<XmlAdaptedPerson> persons; + @XmlElement + private List<XmlAdaptedTag> tags; + @XmlElement + private List<XmlAdaptedExercise> exercises; + @XmlElement + private List<XmlAdaptedIssue> issues; + + /** + * Creates an empty XmlSerializableProgressChecker. + * This empty constructor is required for marshalling. + */ + public XmlSerializableProgressChecker() { + persons = new ArrayList<>(); + tags = new ArrayList<>(); + exercises = new ArrayList<>(); + issues = new ArrayList<>(); + } + + /** + * Conversion + */ + public XmlSerializableProgressChecker(ReadOnlyProgressChecker src) { + this(); + persons.addAll(src.getPersonList().stream().map(XmlAdaptedPerson::new).collect(Collectors.toList())); + tags.addAll(src.getTagList().stream().map(XmlAdaptedTag::new).collect(Collectors.toList())); + exercises.addAll(src.getExerciseList().stream().map(XmlAdaptedExercise::new).collect(Collectors.toList())); + issues.addAll(src.getIssueList().stream().map(XmlAdaptedIssue::new).collect(Collectors.toList())); + + } + + /** + * Converts this progresschecker into the model's {@code ProgressChecker} object. + * + * @throws IllegalValueException if there were any data constraints violated or duplicates in the + * {@code XmlAdaptedPerson} or {@code XmlAdaptedTag}. + */ + public ProgressChecker toModelType() throws IllegalValueException, CommandException, IOException { + ProgressChecker progressChecker = new ProgressChecker(); + for (XmlAdaptedTag t : tags) { + progressChecker.addTag(t.toModelType()); + } + for (XmlAdaptedPerson p : persons) { + progressChecker.addPerson(p.toModelType()); + } + for (XmlAdaptedExercise e : exercises) { + progressChecker.addExercise(e.toModelType()); + } + return progressChecker; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + if (!(other instanceof XmlSerializableProgressChecker)) { + return false; + } + + XmlSerializableProgressChecker otherAb = (XmlSerializableProgressChecker) other; + return persons.equals(otherAb.persons) && tags.equals(otherAb.tags) + && issues.equals(otherAb.issues) && exercises.equals(otherAb.issues); + } +} diff --git a/src/main/java/seedu/progresschecker/ui/Browser2Panel.java b/src/main/java/seedu/progresschecker/ui/Browser2Panel.java new file mode 100644 index 000000000000..e55624e415a9 --- /dev/null +++ b/src/main/java/seedu/progresschecker/ui/Browser2Panel.java @@ -0,0 +1,79 @@ +package seedu.progresschecker.ui; + +import java.net.URL; +import java.util.logging.Logger; + +import com.google.common.eventbus.Subscribe; + +import javafx.application.Platform; +import javafx.event.Event; +import javafx.fxml.FXML; +import javafx.scene.layout.Region; +import javafx.scene.web.WebView; +import seedu.progresschecker.MainApp; +import seedu.progresschecker.commons.core.LogsCenter; +import seedu.progresschecker.commons.events.ui.LoadBarEvent; + +/** + * The second Browser Panel of the App. + */ +public class Browser2Panel extends UiPart<Region> { + + public static final String DEFAULT_PAGE = "default.html"; + + private static final String FXML = "Browser2Panel.fxml"; + + private final Logger logger = LogsCenter.getLogger(this.getClass()); + + @FXML + private WebView browser2; + + public Browser2Panel() { + super(FXML); + + // To prevent triggering events for typing inside the loaded Web page. + getRoot().setOnKeyPressed(Event::consume); + + loadDefaultPage(); + registerAsAnEventHandler(this); + } + + public void loadPage(String url) { + Platform.runLater(() -> browser2.getEngine().load(url)); + } + + /** + * Loads a default HTML file with a background that matches the general theme. + */ + private void loadDefaultPage() { + URL defaultPage = MainApp.class.getResource(FXML_FILE_FOLDER + DEFAULT_PAGE); + loadPage(defaultPage.toExternalForm()); + } + + //@@author EdwardKSG + /** + * Loads the HTML file which contains task information. + */ + public void loadBarPage(String content) { + loadPageViaString(content); + } + + public void loadPageViaString(String content) { + Platform.runLater(() -> browser2.getEngine().loadContent(content)); + } + + /** + * Frees resources allocated to the browser. + */ + public void freeResources() { + browser2 = null; + } + + @Subscribe + private void handleLoadBarEvent(LoadBarEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + loadBarPage(event.getContent()); + } + //@@author +} + diff --git a/src/main/java/seedu/address/ui/BrowserPanel.java b/src/main/java/seedu/progresschecker/ui/BrowserPanel.java similarity index 59% rename from src/main/java/seedu/address/ui/BrowserPanel.java rename to src/main/java/seedu/progresschecker/ui/BrowserPanel.java index bb0d61380d5a..a63de319cf89 100644 --- a/src/main/java/seedu/address/ui/BrowserPanel.java +++ b/src/main/java/seedu/progresschecker/ui/BrowserPanel.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package seedu.progresschecker.ui; import java.net.URL; import java.util.logging.Logger; @@ -10,10 +10,11 @@ import javafx.fxml.FXML; import javafx.scene.layout.Region; import javafx.scene.web.WebView; -import seedu.address.MainApp; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.events.ui.PersonPanelSelectionChangedEvent; -import seedu.address.model.person.Person; +import seedu.progresschecker.MainApp; +import seedu.progresschecker.commons.core.LogsCenter; +import seedu.progresschecker.commons.events.ui.LoadTaskEvent; +import seedu.progresschecker.commons.events.ui.LoadUrlEvent; +import seedu.progresschecker.commons.events.ui.PersonPanelSelectionChangedEvent; /** * The Browser Panel of the App. @@ -21,8 +22,6 @@ public class BrowserPanel extends UiPart<Region> { public static final String DEFAULT_PAGE = "default.html"; - public static final String SEARCH_PAGE_URL = - "https://se-edu.github.io/addressbook-level4/DummySearchPage.html?name="; private static final String FXML = "BrowserPanel.fxml"; @@ -41,9 +40,18 @@ public BrowserPanel() { registerAsAnEventHandler(this); } - private void loadPersonPage(Person person) { - loadPage(SEARCH_PAGE_URL + person.getName().fullName); + //@@author EdwardKSG + /** + * Loads the HTML file which contains task information. + */ + public void loadTaskPage(String content) { + loadPageViaString(content); + } + + public void loadPageViaString(String content) { + Platform.runLater(() -> browser.getEngine().loadContent(content)); } + //@@author public void loadPage(String url) { Platform.runLater(() -> browser.getEngine().load(url)); @@ -67,6 +75,19 @@ public void freeResources() { @Subscribe private void handlePersonPanelSelectionChangedEvent(PersonPanelSelectionChangedEvent event) { logger.info(LogsCenter.getEventHandlingLogMessage(event)); - loadPersonPage(event.getNewSelection().person); } + + //@@author EdwardKSG + @Subscribe + private void handleLoadTaskEvent(LoadTaskEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + loadTaskPage(event.getContent()); + } + + @Subscribe + private void handleLoadUrlEvent(LoadUrlEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + loadPage(event.getUrl()); + } + //@@author } diff --git a/src/main/java/seedu/address/ui/CommandBox.java b/src/main/java/seedu/progresschecker/ui/CommandBox.java similarity index 59% rename from src/main/java/seedu/address/ui/CommandBox.java rename to src/main/java/seedu/progresschecker/ui/CommandBox.java index 9cef588df3c3..803efb047dd0 100644 --- a/src/main/java/seedu/address/ui/CommandBox.java +++ b/src/main/java/seedu/progresschecker/ui/CommandBox.java @@ -1,19 +1,24 @@ -package seedu.address.ui; +package seedu.progresschecker.ui; +import java.util.ArrayList; +import java.util.List; import java.util.logging.Logger; +import java.util.stream.Collectors; import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.scene.control.TextField; +import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; import javafx.scene.layout.Region; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.events.ui.NewResultAvailableEvent; -import seedu.address.logic.ListElementPointer; -import seedu.address.logic.Logic; -import seedu.address.logic.commands.CommandResult; -import seedu.address.logic.commands.exceptions.CommandException; -import seedu.address.logic.parser.exceptions.ParseException; +import seedu.progresschecker.commons.core.LogsCenter; +import seedu.progresschecker.commons.events.ui.NewResultAvailableEvent; +import seedu.progresschecker.logic.CommandFormatListUtil; +import seedu.progresschecker.logic.ListElementPointer; +import seedu.progresschecker.logic.Logic; +import seedu.progresschecker.logic.commands.CommandResult; +import seedu.progresschecker.logic.commands.exceptions.CommandException; +import seedu.progresschecker.logic.parser.exceptions.ParseException; /** * The UI component that is responsible for receiving user command inputs. @@ -22,10 +27,12 @@ public class CommandBox extends UiPart<Region> { public static final String ERROR_STYLE_CLASS = "error"; private static final String FXML = "CommandBox.fxml"; + private static final String CORRECT_COMMAND_WORD = "find"; private final Logger logger = LogsCenter.getLogger(CommandBox.class); private final Logic logic; private ListElementPointer historySnapshot; + private boolean isCorrectCommandWord = false; @FXML private TextField commandTextField; @@ -55,8 +62,38 @@ private void handleKeyPress(KeyEvent keyEvent) { keyEvent.consume(); navigateToNextInput(); break; + + //@@author adityaa1998 + //TAB case is used to auto-complete commands + case TAB: + keyEvent.consume(); + autocompleteCommad(commandTextField.getText()); + break; default: - // let JavaFx handle the keypress + //dynamic search implementation + try { + if ((commandTextField.getText().trim().equalsIgnoreCase(CORRECT_COMMAND_WORD) + || isCorrectCommandWord)) { + isCorrectCommandWord = !commandTextField.getText().trim().isEmpty(); + CommandResult commandResult; + if (keyEvent.getCode() != KeyCode.BACK_SPACE && keyEvent.getCode() != KeyCode.DELETE) { + commandResult = logic.execute(commandTextField.getText() + keyEvent.getText()); + } else { + commandResult = logic.execute(commandTextField.getText().substring(0, + commandTextField.getText().length() - 1)); + } + // process result of the command + logger.info("Result: " + commandResult.feedbackToUser); + raise(new NewResultAvailableEvent(commandResult.feedbackToUser)); + } + + } catch (CommandException | ParseException e) { + // handle command failure + setStyleToIndicateCommandFailure(); + logger.info("Invalid command: " + commandTextField.getText()); + raise(new NewResultAvailableEvent(e.getMessage())); + } + //@@author } } @@ -148,4 +185,22 @@ private void setStyleToIndicateCommandFailure() { styleClass.add(ERROR_STYLE_CLASS); } + /** + * Sets the commandbox to completed command format if the entered substring of the command is valid + * @param text is the command which is to be autocompleted + */ + private void autocompleteCommad(String text) { + ArrayList<String> commandFormatList = CommandFormatListUtil.getCommandFormatList(); + + //retrieve the list of words which begin with text + List<String> autocompleteCommandList = commandFormatList.stream() + .filter(s -> s.startsWith(text)) + .collect(Collectors.toList()); + + //replace input in text field with matched keyword + if (!autocompleteCommandList.isEmpty()) { + replaceText(autocompleteCommandList.get(0)); + } + + } } diff --git a/src/main/java/seedu/progresschecker/ui/ExerciseCard.java b/src/main/java/seedu/progresschecker/ui/ExerciseCard.java new file mode 100644 index 000000000000..9cd0e45fb073 --- /dev/null +++ b/src/main/java/seedu/progresschecker/ui/ExerciseCard.java @@ -0,0 +1,45 @@ +package seedu.progresschecker.ui; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.Region; +import seedu.progresschecker.model.exercise.Exercise; + +//@@author iNekox3 +/** + * An UI component that displays information of an {@code Exercise}. + */ +public class ExerciseCard extends UiPart<Region> { + + private static final String FXML = "ExerciseListCard.fxml"; + + public final Exercise exercise; + + @FXML + private Label questionIndex; + @FXML + private Label questionType; + @FXML + private Label question; + @FXML + private Label studentAnswer; + @FXML + private Label modelAnswerHeader; + @FXML + private Label modelAnswer; + + public ExerciseCard(Exercise exercise) { + super(FXML); + this.exercise = exercise; + questionIndex.setText(exercise.getQuestionIndex().value); + question.setText(exercise.getQuestion().value); + studentAnswer.setText(exercise.getStudentAnswer().value); + modelAnswer.setText(""); + + if (!exercise.getStudentAnswer().value.equals("")) { + questionIndex.getStyleClass().add("answered"); + modelAnswerHeader.setText("Suggested Answer: "); + modelAnswer.setText(exercise.getModelAnswer().value); + } + } +} diff --git a/src/main/java/seedu/progresschecker/ui/ExerciseListPanel.java b/src/main/java/seedu/progresschecker/ui/ExerciseListPanel.java new file mode 100644 index 000000000000..7c2828417f7b --- /dev/null +++ b/src/main/java/seedu/progresschecker/ui/ExerciseListPanel.java @@ -0,0 +1,57 @@ +package seedu.progresschecker.ui; + +import java.util.logging.Logger; + +import org.fxmisc.easybind.EasyBind; + +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; +import seedu.progresschecker.commons.core.LogsCenter; +import seedu.progresschecker.model.exercise.Exercise; + +//@@author iNekox3 +/** + * Panel containing the list of exercises. + */ +public class ExerciseListPanel extends UiPart<Region> { + private static final String FXML = "ExerciseListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(ExerciseListPanel.class); + + @FXML + private ListView<ExerciseCard> exerciseListView; + + public ExerciseListPanel(ObservableList<Exercise> exerciseList) { + super(FXML); + setConnections(exerciseList); + registerAsAnEventHandler(this); + } + + private void setConnections(ObservableList<Exercise> exerciseList) { + ObservableList<ExerciseCard> mappedList = EasyBind.map( + exerciseList, (exercise) -> new ExerciseCard(exercise)); + exerciseListView.setItems(mappedList); + exerciseListView.setCellFactory(listView -> new ExerciseListViewCell()); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code ExerciseCard}. + */ + class ExerciseListViewCell extends ListCell<ExerciseCard> { + + @Override + protected void updateItem(ExerciseCard exercise, boolean empty) { + super.updateItem(exercise, empty); + + if (empty || exercise == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(exercise.getRoot()); + } + } + } + +} diff --git a/src/main/java/seedu/address/ui/HelpWindow.java b/src/main/java/seedu/progresschecker/ui/HelpWindow.java similarity index 80% rename from src/main/java/seedu/address/ui/HelpWindow.java rename to src/main/java/seedu/progresschecker/ui/HelpWindow.java index 5254a1b3bbcb..89824e1e0b80 100644 --- a/src/main/java/seedu/address/ui/HelpWindow.java +++ b/src/main/java/seedu/progresschecker/ui/HelpWindow.java @@ -1,11 +1,12 @@ -package seedu.address.ui; +package seedu.progresschecker.ui; import java.util.logging.Logger; import javafx.fxml.FXML; +import javafx.scene.web.WebEngine; import javafx.scene.web.WebView; import javafx.stage.Stage; -import seedu.address.commons.core.LogsCenter; +import seedu.progresschecker.commons.core.LogsCenter; /** * Controller for a help page @@ -16,6 +17,7 @@ public class HelpWindow extends UiPart<Stage> { private static final Logger logger = LogsCenter.getLogger(HelpWindow.class); private static final String FXML = "HelpWindow.fxml"; + private static final String USER_AGENT = "Mozilla/5.0 (Windows NT 5.1; rv:7.0.1) Gecko/20100101 Firefox/7.0.1"; @FXML private WebView browser; @@ -29,7 +31,9 @@ public HelpWindow(Stage root) { super(FXML, root); String userGuideUrl = getClass().getResource(USERGUIDE_FILE_PATH).toString(); - browser.getEngine().load(userGuideUrl); + WebEngine engine = browser.getEngine(); + engine.setUserAgent(USER_AGENT); + engine.load(userGuideUrl); } /** diff --git a/src/main/java/seedu/progresschecker/ui/IssueCard.java b/src/main/java/seedu/progresschecker/ui/IssueCard.java new file mode 100644 index 000000000000..88e416789d3b --- /dev/null +++ b/src/main/java/seedu/progresschecker/ui/IssueCard.java @@ -0,0 +1,98 @@ +package seedu.progresschecker.ui; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import seedu.progresschecker.model.issues.Issue; + +//@@author adityaa1998 +/** + * An UI component that displays information of a {@code Issue}. + */ +public class IssueCard extends UiPart<Region> { + + private static final String FXML = "IssueListCard.fxml"; + private static final String[] LABEL_COLORS = { "red", "orange", "yellow", "green", "blue", "purple" }; + /** + * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. + * As a consequence, UI elements' variable names cannot be set to such keywords + * or an exception will be thrown by JavaFX during runtime. + * + * @see <a href="https://github.com/se-edu/addressbook-level4/issues/336">The issue on AddressBook level 4</a> + */ + + public final Issue issue; + + @javafx.fxml.FXML + private HBox cardPane; + @FXML + private Label title; + @FXML + private Label id; + @FXML + private Label body; + @FXML + private Label milestone; + @FXML + private FlowPane labelled; + @FXML + private FlowPane assignees; + + public IssueCard(Issue issue, int displayedIndex) { + super(FXML); + this.issue = issue; + id.setText("#" + displayedIndex + " "); + title.setText(issue.getTitle().toString()); + body.setText(issue.getBody().fullBody); + milestone.setText(issue.getMilestone().fullMilestone); + issue.getLabelsList().forEach(labels -> { + Label label = new Label(labels.fullLabels); + label.getStyleClass().add(getLabelColor(labels.fullLabels)); + labelled.getChildren().add(label); + }); + issue.getAssignees().forEach(assignee -> { + Label label = new Label(assignee.fullAssignees); + label.getStyleClass().add(getLabelColor(assignee.fullAssignees)); + assignees.getChildren().add(label); + }); + } + + /** + * Get a deterministic label color based off label's name value. + */ + private String getLabelColor(String labelName) { + int index = getValueOfString(labelName) % LABEL_COLORS.length; + return LABEL_COLORS[index]; + } + + /** + * Adds each letter of given string into an integer. + */ + private int getValueOfString(String labelName) { + int sum = 0; + for (char c : labelName.toCharArray()) { + sum += c; + } + return sum; + } + + @Override + public boolean equals(Object other) { + // short circuit if same object + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof IssueCard)) { + return false; + } + + // state check + IssueCard card = (IssueCard) other; + return id.getText().equals(card.id.getText()) + && issue.equals(card.issue); + } +} diff --git a/src/main/java/seedu/progresschecker/ui/IssueListPanel.java b/src/main/java/seedu/progresschecker/ui/IssueListPanel.java new file mode 100644 index 000000000000..7fa0be5e05d3 --- /dev/null +++ b/src/main/java/seedu/progresschecker/ui/IssueListPanel.java @@ -0,0 +1,76 @@ +package seedu.progresschecker.ui; + +import java.util.logging.Logger; + +import org.fxmisc.easybind.EasyBind; + +import com.google.common.eventbus.Subscribe; + +import javafx.application.Platform; +import javafx.collections.ObservableList; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.layout.Region; +import seedu.progresschecker.commons.core.LogsCenter; +import seedu.progresschecker.commons.events.ui.JumpToListRequestEvent; +import seedu.progresschecker.model.issues.Issue; + +//@@author adityaa1998 +/** + * Panel containing the issues on github. + */ +public class IssueListPanel extends UiPart<Region> { + private static final String FXML = "IssueListPanel.fxml"; + private final Logger logger = LogsCenter.getLogger(IssueListPanel.class); + + @javafx.fxml.FXML + private ListView<IssueCard> issueListView; + + public IssueListPanel(ObservableList<Issue> issueList) { + super(FXML); + setConnections(issueList); + registerAsAnEventHandler(this); + } + + private void setConnections(ObservableList<Issue> issueList) { + ObservableList<IssueCard> mappedList = EasyBind.map( + issueList, (issue) -> new IssueCard(issue, issue.getIssueIndex())); + issueListView.setItems(mappedList); + issueListView.setCellFactory(listView -> new IssueListViewCell()); + } + + /** + * Scrolls to the {@code IssueCard} at the {@code index} and selects it. + */ + private void scrollTo(int index) { + Platform.runLater(() -> { + issueListView.scrollTo(index); + issueListView.getSelectionModel().clearAndSelect(index); + }); + } + + @Subscribe + private void handleJumpToListRequestEvent(JumpToListRequestEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + scrollTo(event.targetIndex); + } + + /** + * Custom {@code ListCell} that displays the graphics of a {@code IssueCard}. + */ + class IssueListViewCell extends ListCell<IssueCard> { + + @Override + protected void updateItem(IssueCard issue, boolean empty) { + super.updateItem(issue, empty); + + if (empty || issue == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(issue.getRoot()); + } + } + } + +} diff --git a/src/main/java/seedu/address/ui/MainWindow.java b/src/main/java/seedu/progresschecker/ui/MainWindow.java similarity index 54% rename from src/main/java/seedu/address/ui/MainWindow.java rename to src/main/java/seedu/progresschecker/ui/MainWindow.java index 20ad5fee906a..4a9a671d00dc 100644 --- a/src/main/java/seedu/address/ui/MainWindow.java +++ b/src/main/java/seedu/progresschecker/ui/MainWindow.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package seedu.progresschecker.ui; import java.util.logging.Logger; @@ -6,27 +6,40 @@ import javafx.event.ActionEvent; import javafx.fxml.FXML; +import javafx.scene.Scene; import javafx.scene.control.MenuItem; +import javafx.scene.control.SingleSelectionModel; +import javafx.scene.control.Tab; +import javafx.scene.control.TabPane; import javafx.scene.control.TextInputControl; import javafx.scene.input.KeyCombination; import javafx.scene.input.KeyEvent; +import javafx.scene.layout.Region; import javafx.scene.layout.StackPane; import javafx.stage.Stage; -import seedu.address.commons.core.Config; -import seedu.address.commons.core.GuiSettings; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.events.ui.ExitAppRequestEvent; -import seedu.address.commons.events.ui.ShowHelpRequestEvent; -import seedu.address.logic.Logic; -import seedu.address.model.UserPrefs; +import seedu.progresschecker.commons.core.Config; +import seedu.progresschecker.commons.core.GuiSettings; +import seedu.progresschecker.commons.core.LogsCenter; +import seedu.progresschecker.commons.events.ui.ChangeThemeEvent; +import seedu.progresschecker.commons.events.ui.ExitAppRequestEvent; +import seedu.progresschecker.commons.events.ui.ShowHelpRequestEvent; +import seedu.progresschecker.commons.events.ui.TabLoadChangedEvent; +import seedu.progresschecker.commons.util.AppUtil; +import seedu.progresschecker.logic.Logic; +import seedu.progresschecker.model.UserPrefs; /** * The Main Window. Provides the basic application layout containing * a menu bar and space where other JavaFX elements can be placed. */ -public class MainWindow extends UiPart<Stage> { +public class MainWindow extends UiPart<Region> { private static final String FXML = "MainWindow.fxml"; + private static final String ICON = "/images/progress_checker_32.png"; + private static final String DARK_THEME = "view/DarkTheme.css"; + private static final String DAY_THEME = "view/DayTheme.css"; + private static final int MIN_HEIGHT = 600; + private static final int MIN_WIDTH = 450; private final Logger logger = LogsCenter.getLogger(this.getClass()); @@ -35,16 +48,32 @@ public class MainWindow extends UiPart<Stage> { // Independent Ui parts residing in this Ui container private BrowserPanel browserPanel; + private Browser2Panel browser2Panel; + private ExerciseListPanel exerciseListPanel; + private IssueListPanel issueListPanel; private PersonListPanel personListPanel; + private ProfilePanel profilePanel; private Config config; private UserPrefs prefs; @FXML private StackPane browserPlaceholder; + @FXML + private StackPane browser2Placeholder; + @FXML private StackPane commandBoxPlaceholder; + @FXML + private StackPane exerciseListPanelPlaceholder; + + @FXML + private StackPane profilePanelPlaceholder; + + @FXML + private StackPane issuePanelPlaceholder; + @FXML private MenuItem helpMenuItem; @@ -57,8 +86,24 @@ public class MainWindow extends UiPart<Stage> { @FXML private StackPane statusbarPlaceholder; + @FXML + private TabPane tabPlaceholder; + + @FXML + private Tab profilePlaceholder; + + @FXML + private Tab taskPlaceholder; + + @FXML + private Tab exercisePlaceholder; + + @FXML + private Tab issuePlaceholder; + + public MainWindow(Stage primaryStage, Config config, UserPrefs prefs, Logic logic) { - super(FXML, primaryStage); + super(FXML); // Set dependencies this.primaryStage = primaryStage; @@ -68,7 +113,12 @@ public MainWindow(Stage primaryStage, Config config, UserPrefs prefs, Logic logi // Configure the UI setTitle(config.getAppTitle()); + setIcon(ICON); + setWindowMinSize(); setWindowDefaultSize(prefs); + Scene scene = new Scene(getRoot()); + primaryStage.setScene(scene); + handleDayTheme(); setAccelerators(); registerAsAnEventHandler(this); @@ -119,13 +169,25 @@ void fillInnerParts() { browserPanel = new BrowserPanel(); browserPlaceholder.getChildren().add(browserPanel.getRoot()); + browser2Panel = new Browser2Panel(); + browser2Placeholder.getChildren().add(browser2Panel.getRoot()); + + profilePanel = new ProfilePanel(); + profilePanelPlaceholder.getChildren().add(profilePanel.getRoot()); + + exerciseListPanel = new ExerciseListPanel(logic.getFilteredExerciseList()); + exerciseListPanelPlaceholder.getChildren().add(exerciseListPanel.getRoot()); + + issueListPanel = new IssueListPanel(logic.getFilteredIssueList()); + issuePanelPlaceholder.getChildren().add(issueListPanel.getRoot()); + personListPanel = new PersonListPanel(logic.getFilteredPersonList()); personListPanelPlaceholder.getChildren().add(personListPanel.getRoot()); ResultDisplay resultDisplay = new ResultDisplay(); resultDisplayPlaceholder.getChildren().add(resultDisplay.getRoot()); - StatusBarFooter statusBarFooter = new StatusBarFooter(prefs.getAddressBookFilePath()); + StatusBarFooter statusBarFooter = new StatusBarFooter(prefs.getProgressCheckerFilePath()); statusbarPlaceholder.getChildren().add(statusBarFooter.getRoot()); CommandBox commandBox = new CommandBox(logic); @@ -169,6 +231,30 @@ public void handleHelp() { helpWindow.show(); } + //@@author: Livian1107 + /** + * Switches to the Night Theme. + */ + @FXML + public void handleNightTheme() { + Scene scene = primaryStage.getScene(); + scene.getStylesheets().setAll(DARK_THEME); + primaryStage.setScene(scene); + show(); + } + + /** + * Switches to the Day Theme. + */ + @FXML + public void handleDayTheme() { + Scene scene = primaryStage.getScene(); + scene.getStylesheets().setAll(DAY_THEME); + primaryStage.setScene(scene); + show(); + } + //@@author + void show() { primaryStage.show(); } @@ -187,6 +273,7 @@ public PersonListPanel getPersonListPanel() { void releaseResources() { browserPanel.freeResources(); + browser2Panel.freeResources(); } @Subscribe @@ -194,4 +281,61 @@ private void handleShowHelpEvent(ShowHelpRequestEvent event) { logger.info(LogsCenter.getEventHandlingLogMessage(event)); handleHelp(); } + + //@@author Livian1107 + /** + * Sets the icon of Main Window + * @param icon with given path + */ + private void setIcon(String icon) { + primaryStage.getIcons().setAll(AppUtil.getImage(icon)); + } + + /** + * Sets the minimum size of the main window + */ + private void setWindowMinSize() { + primaryStage.setMinHeight(MIN_HEIGHT); + primaryStage.setMinWidth(MIN_WIDTH); + } + + @Subscribe + private void handleChangeThemeEvent(ChangeThemeEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + switch (event.getTheme()) { + case "day": + handleDayTheme(); + break; + case "night": + handleNightTheme(); + break; + default: + handleDayTheme(); + } + } + //@@author + + //@@author iNekox3 + @Subscribe + private void handleTabLoadChangedEvent(TabLoadChangedEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + SingleSelectionModel<Tab> selectionModel = tabPlaceholder.getSelectionModel(); + switch (event.getTabName()) { + case "profile": + selectionModel.select(profilePlaceholder); + break; + case "task": + selectionModel.select(taskPlaceholder); + break; + case "exercise": + selectionModel.select(exercisePlaceholder); + break; + case "issues": + selectionModel.select(issuePlaceholder); + break; + default: + selectionModel.select(selectionModel.getSelectedItem()); + } + } + //@@author } diff --git a/src/main/java/seedu/address/ui/PersonCard.java b/src/main/java/seedu/progresschecker/ui/PersonCard.java similarity index 75% rename from src/main/java/seedu/address/ui/PersonCard.java rename to src/main/java/seedu/progresschecker/ui/PersonCard.java index f6727ea83abd..a8c8e1493e97 100644 --- a/src/main/java/seedu/address/ui/PersonCard.java +++ b/src/main/java/seedu/progresschecker/ui/PersonCard.java @@ -1,11 +1,11 @@ -package seedu.address.ui; +package seedu.progresschecker.ui; import javafx.fxml.FXML; import javafx.scene.control.Label; -import javafx.scene.layout.FlowPane; + import javafx.scene.layout.HBox; import javafx.scene.layout.Region; -import seedu.address.model.person.Person; +import seedu.progresschecker.model.person.Person; /** * An UI component that displays information of a {@code Person}. @@ -30,26 +30,26 @@ public class PersonCard extends UiPart<Region> { private Label name; @FXML private Label id; - @FXML - private Label phone; - @FXML - private Label address; - @FXML - private Label email; - @FXML - private FlowPane tags; public PersonCard(Person person, int displayedIndex) { super(FXML); this.person = person; id.setText(displayedIndex + ". "); name.setText(person.getName().fullName); - phone.setText(person.getPhone().value); - address.setText(person.getAddress().value); - email.setText(person.getEmail().value); - person.getTags().forEach(tag -> tags.getChildren().add(new Label(tag.tagName))); } + /** + * Adds each letter of given string into an integer. + */ + private int getValueOfString(String tagName) { + int sum = 0; + for (char c : tagName.toCharArray()) { + sum += c; + } + return sum; + } + + //@@author iNekox3 @Override public boolean equals(Object other) { // short circuit if same object diff --git a/src/main/java/seedu/address/ui/PersonListPanel.java b/src/main/java/seedu/progresschecker/ui/PersonListPanel.java similarity index 90% rename from src/main/java/seedu/address/ui/PersonListPanel.java rename to src/main/java/seedu/progresschecker/ui/PersonListPanel.java index 60a4f70f4e71..99097180756b 100644 --- a/src/main/java/seedu/address/ui/PersonListPanel.java +++ b/src/main/java/seedu/progresschecker/ui/PersonListPanel.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package seedu.progresschecker.ui; import java.util.logging.Logger; @@ -12,10 +12,10 @@ import javafx.scene.control.ListCell; import javafx.scene.control.ListView; import javafx.scene.layout.Region; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.events.ui.JumpToListRequestEvent; -import seedu.address.commons.events.ui.PersonPanelSelectionChangedEvent; -import seedu.address.model.person.Person; +import seedu.progresschecker.commons.core.LogsCenter; +import seedu.progresschecker.commons.events.ui.JumpToListRequestEvent; +import seedu.progresschecker.commons.events.ui.PersonPanelSelectionChangedEvent; +import seedu.progresschecker.model.person.Person; /** * Panel containing the list of persons. diff --git a/src/main/java/seedu/progresschecker/ui/ProfilePanel.java b/src/main/java/seedu/progresschecker/ui/ProfilePanel.java new file mode 100644 index 000000000000..f41abaca8fda --- /dev/null +++ b/src/main/java/seedu/progresschecker/ui/ProfilePanel.java @@ -0,0 +1,168 @@ +package seedu.progresschecker.ui; + +import java.io.File; +import java.util.logging.Logger; + +import com.google.common.eventbus.Subscribe; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.image.Image; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.Region; +import javafx.scene.paint.ImagePattern; +import javafx.scene.shape.Ellipse; +import seedu.progresschecker.MainApp; +import seedu.progresschecker.commons.core.LogsCenter; +import seedu.progresschecker.commons.events.ui.PersonPanelSelectionChangedEvent; +import seedu.progresschecker.model.person.Person; + +//@@author Livian1107 + +/** + * Panel contains the information of person + */ +public class ProfilePanel extends UiPart<Region> { + + private static final String FXML = "ProfilePanel.fxml"; + private static String DEFAULT_PHOTO = "/images/profile_photo.jpg"; + + private static final String[] TAG_COLORS = { "red", "orange", "yellow", "green", "blue", "purple" }; + /** + * Note: Certain keywords such as "location" and "resources" are reserved keywords in JavaFX. + * As a consequence, UI elements' variable names cannot be set to such keywords + * or an exception will be thrown by JavaFX during runtime. + * + * @see <a href="https://github.com/se-edu/addressbook-level4/issues/336">The issue on AddressBook level 4</a> + */ + + private Person person; + private Person currentlyViewedPerson; + + private final Logger logger = LogsCenter.getLogger(this.getClass()); + + @FXML + private Label name; + @FXML + private Label phone; + @FXML + private Label major; + @FXML + private Label year; + @FXML + private Label email; + @FXML + private Label username; + @FXML + private FlowPane tags; + @FXML + private Ellipse profile; + + public ProfilePanel() { + super(FXML); + this.person = null; + loadDefaultPerson(); + registerAsAnEventHandler(this); + + } + + //@@author iNekox3 + /** + * Get a deterministic tag color based off tag's name value. + */ + private String getTagColor(String tagName) { + int index = getValueOfString(tagName) % TAG_COLORS.length; + return TAG_COLORS[index]; + } + + /** + * Adds each letter of given string into an integer. + */ + private int getValueOfString(String tagName) { + int sum = 0; + for (char c : tagName.toCharArray()) { + sum += c; + } + return sum; + } + //@@author + + //@@author Livian1107 + /** + * Loads the default person + */ + private void loadDefaultPerson() { + name.setText("Person X"); + phone.setText(""); + username.setText(""); + email.setText(""); + year.setText(""); + major.setText(""); + tags.getChildren().clear(); + + setDefaultInfoPhoto(); + currentlyViewedPerson = null; + logger.info("Currently Viewing: Default Person"); + } + + /** + * Loads the info of the selected person + */ + public void loadPerson(Person person) { + this.person = person; + tags.getChildren().clear(); + name.setText(person.getName().fullName); + phone.setText(person.getPhone().value); + major.setText(person.getMajor().value); + year.setText(person.getYear().value); + email.setText(person.getEmail().value); + username.setText(person.getUsername().username); + //@@author iNekox3 + person.getTags().forEach(tag -> { + Label label = new Label(tag.tagName); + label.getStyleClass().add(getTagColor(tag.tagName)); + tags.getChildren().add(label); + }); + //@@author + + //@@author Livian1107 + loadPhoto(); + + currentlyViewedPerson = person; + logger.info("Currently Viewing: " + currentlyViewedPerson.getName()); + } + + /** + * Sets the default info photo. + */ + public void setDefaultInfoPhoto() { + Image defaultImage = new Image(MainApp.class.getResourceAsStream(DEFAULT_PHOTO)); + profile.setFill(new ImagePattern(defaultImage)); + } + + /** + * Loads profile photo + */ + private void loadPhoto() { + String photoPath = person.getPhotoPath(); + Image profilePhoto; + if (photoPath.contains("contact")) { + File photo = new File(photoPath); + if (photo.exists() && !photo.isDirectory()) { + String url = photo.toURI().toString(); + profilePhoto = new Image(url); + profile.setFill(new ImagePattern(profilePhoto)); + } + } else { + profilePhoto = new Image( + MainApp.class.getResourceAsStream(person.getDefaultPath())); + profile.setFill(new ImagePattern(profilePhoto)); + } + } + + @Subscribe + private void handlePersonPanelSelectionChangeEvent(PersonPanelSelectionChangedEvent event) { + logger.info(LogsCenter.getEventHandlingLogMessage(event)); + this.loadPerson(event.getNewSelection().person); + } +} diff --git a/src/main/java/seedu/address/ui/ResultDisplay.java b/src/main/java/seedu/progresschecker/ui/ResultDisplay.java similarity index 87% rename from src/main/java/seedu/address/ui/ResultDisplay.java rename to src/main/java/seedu/progresschecker/ui/ResultDisplay.java index d05536bbee96..59d954b1a67f 100644 --- a/src/main/java/seedu/address/ui/ResultDisplay.java +++ b/src/main/java/seedu/progresschecker/ui/ResultDisplay.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package seedu.progresschecker.ui; import java.util.logging.Logger; @@ -10,8 +10,8 @@ import javafx.fxml.FXML; import javafx.scene.control.TextArea; import javafx.scene.layout.Region; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.events.ui.NewResultAvailableEvent; +import seedu.progresschecker.commons.core.LogsCenter; +import seedu.progresschecker.commons.events.ui.NewResultAvailableEvent; /** * A ui for the status bar that is displayed at the header of the application. diff --git a/src/main/java/seedu/address/ui/StatusBarFooter.java b/src/main/java/seedu/progresschecker/ui/StatusBarFooter.java similarity index 89% rename from src/main/java/seedu/address/ui/StatusBarFooter.java rename to src/main/java/seedu/progresschecker/ui/StatusBarFooter.java index 06fb7e50c935..a6c4a4f8446d 100644 --- a/src/main/java/seedu/address/ui/StatusBarFooter.java +++ b/src/main/java/seedu/progresschecker/ui/StatusBarFooter.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package seedu.progresschecker.ui; import java.time.Clock; import java.util.Date; @@ -11,8 +11,8 @@ import javafx.application.Platform; import javafx.fxml.FXML; import javafx.scene.layout.Region; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.events.model.AddressBookChangedEvent; +import seedu.progresschecker.commons.core.LogsCenter; +import seedu.progresschecker.commons.events.model.ProgressCheckerChangedEvent; /** * A ui for the status bar that is displayed at the footer of the application. @@ -72,7 +72,7 @@ private void setSyncStatus(String status) { } @Subscribe - public void handleAddressBookChangedEvent(AddressBookChangedEvent abce) { + public void handleProgressCheckerChangedEvent(ProgressCheckerChangedEvent abce) { long now = clock.millis(); String lastUpdated = new Date(now).toString(); logger.info(LogsCenter.getEventHandlingLogMessage(abce, "Setting last updated status to " + lastUpdated)); diff --git a/src/main/java/seedu/address/ui/Ui.java b/src/main/java/seedu/progresschecker/ui/Ui.java similarity index 85% rename from src/main/java/seedu/address/ui/Ui.java rename to src/main/java/seedu/progresschecker/ui/Ui.java index e6a67fe8c027..e447ae352e58 100644 --- a/src/main/java/seedu/address/ui/Ui.java +++ b/src/main/java/seedu/progresschecker/ui/Ui.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package seedu.progresschecker.ui; import javafx.stage.Stage; diff --git a/src/main/java/seedu/address/ui/UiManager.java b/src/main/java/seedu/progresschecker/ui/UiManager.java similarity index 88% rename from src/main/java/seedu/address/ui/UiManager.java rename to src/main/java/seedu/progresschecker/ui/UiManager.java index 3fd3c17be156..ba5ca5d40cc2 100644 --- a/src/main/java/seedu/address/ui/UiManager.java +++ b/src/main/java/seedu/progresschecker/ui/UiManager.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package seedu.progresschecker.ui; import java.util.logging.Logger; @@ -9,14 +9,14 @@ import javafx.scene.control.Alert.AlertType; import javafx.scene.image.Image; import javafx.stage.Stage; -import seedu.address.MainApp; -import seedu.address.commons.core.ComponentManager; -import seedu.address.commons.core.Config; -import seedu.address.commons.core.LogsCenter; -import seedu.address.commons.events.storage.DataSavingExceptionEvent; -import seedu.address.commons.util.StringUtil; -import seedu.address.logic.Logic; -import seedu.address.model.UserPrefs; +import seedu.progresschecker.MainApp; +import seedu.progresschecker.commons.core.ComponentManager; +import seedu.progresschecker.commons.core.Config; +import seedu.progresschecker.commons.core.LogsCenter; +import seedu.progresschecker.commons.events.storage.DataSavingExceptionEvent; +import seedu.progresschecker.commons.util.StringUtil; +import seedu.progresschecker.logic.Logic; +import seedu.progresschecker.model.UserPrefs; /** * The manager of the UI component. @@ -30,7 +30,7 @@ public class UiManager extends ComponentManager implements Ui { public static final String FILE_OPS_ERROR_DIALOG_CONTENT_MESSAGE = "Could not save data to file"; private static final Logger logger = LogsCenter.getLogger(UiManager.class); - private static final String ICON_APPLICATION = "/images/address_book_32.png"; + private static final String ICON_APPLICATION = "/images/progress_checker_32.png"; private Logic logic; private Config config; diff --git a/src/main/java/seedu/address/ui/UiPart.java b/src/main/java/seedu/progresschecker/ui/UiPart.java similarity index 91% rename from src/main/java/seedu/address/ui/UiPart.java rename to src/main/java/seedu/progresschecker/ui/UiPart.java index 5c237e57154b..f6df9d54bee5 100644 --- a/src/main/java/seedu/address/ui/UiPart.java +++ b/src/main/java/seedu/progresschecker/ui/UiPart.java @@ -1,4 +1,4 @@ -package seedu.address.ui; +package seedu.progresschecker.ui; import static java.util.Objects.requireNonNull; @@ -6,9 +6,9 @@ import java.net.URL; import javafx.fxml.FXMLLoader; -import seedu.address.MainApp; -import seedu.address.commons.core.EventsCenter; -import seedu.address.commons.events.BaseEvent; +import seedu.progresschecker.MainApp; +import seedu.progresschecker.commons.core.EventsCenter; +import seedu.progresschecker.commons.events.BaseEvent; /** * Represents a distinct part of the UI. e.g. Windows, dialogs, panels, status bars, etc. @@ -34,7 +34,7 @@ public UiPart(URL fxmlFileUrl) { * @see #UiPart(URL) */ public UiPart(String fxmlFileName) { - this(getFxmlFileUrl(fxmlFileName)); + this(fxmlFileName != null ? MainApp.class.getResource(FXML_FILE_FOLDER + fxmlFileName) : null); } /** diff --git a/src/main/resources/client_id.json b/src/main/resources/client_id.json new file mode 100644 index 000000000000..35f713e68de8 --- /dev/null +++ b/src/main/resources/client_id.json @@ -0,0 +1 @@ +{"installed":{"client_id":"541257326219-tcakgeskr16g7sfjp8042sdbl7bqugpr.apps.googleusercontent.com","project_id":"progresschecker-t09b3","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://accounts.google.com/o/oauth2/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_secret":"stJPWp1Ho7PYUCNsTkMvTIpc","redirect_uris":["urn:ietf:wg:oauth:2.0:oob","http://localhost"]}} diff --git a/src/main/resources/defaultTasks.txt b/src/main/resources/defaultTasks.txt new file mode 100644 index 000000000000..450d6ca9cb91 --- /dev/null +++ b/src/main/resources/defaultTasks.txt @@ -0,0 +1,69 @@ +LO[W2.1]★★★★ +Can explain pros and cons of software engineering: checkurlhttps://nus-cs2103-ay1718s2.github.io/website/schedule/week2/outcomes.html +01/25/2018 23:59 +LO[W2.2]★ +Can use basic features of an IDE (2.2a, 2.2b, 2.2c): checkurlhttps://nus-cs2103-ay1718s2.github.io/website/schedule/week2/outcomes.html +01/25/2018 23:59 +LO[W2.2a]★ +Can explain IDEs: checkurlhttps://nus-cs2103-ay1718s2.github.io/website/schedule/week2/outcomes.html +01/25/2018 23:59 +LO[W2.2b][Submission]★ +Can setup a project in an IDE: checkurlhttps://nus-cs2103-ay1718s2.github.io/website/schedule/week2/outcomes.html +01/25/2018 23:59 +LO[W2.2c][Submission]★★ +Can navigate code effectively using IDE features: checkurlhttps://nus-cs2103-ay1718s2.github.io/website/schedule/week2/outcomes.html +01/25/2018 23:59 +LO[W2.3][Submission]★★ +Can use Java Collections: checkurlhttps://nus-cs2103-ay1718s2.github.io/website/schedule/week2/outcomes.html +01/25/2018 23:59 +LO[W2.4][Submission]★★★ +Can use Java varargs feature: checkurlhttps://nus-cs2103-ay1718s2.github.io/website/schedule/week2/outcomes.html +01/25/2018 23:59 +LO[W2.5]★★ +Can automate simple regression testing of text UIs (2.5a, 2.5b, 2.5c, 2.5d): checkurlhttps://nus-cs2103-ay1718s2.github.io/website/schedule/week2/outcomes.html +01/25/2018 23:59 +LO[W2.5a]★★ +Can explain testing: checkurlhttps://nus-cs2103-ay1718s2.github.io/website/schedule/week2/outcomes.html +01/25/2018 23:59 +LO[W2.5b]★★ +Can explain regression testing: checkurlhttps://nus-cs2103-ay1718s2.github.io/website/schedule/week2/outcomes.html +01/25/2018 23:59 +LO[W2.5c]★★ +Can explain test automation: checkurlhttps://nus-cs2103-ay1718s2.github.io/website/schedule/week2/outcomes.html +01/25/2018 23:59 +LO[W2.5d][Submission]★★ +Can semi-automate testing of CLIs: checkurlhttps://nus-cs2103-ay1718s2.github.io/website/schedule/week2/outcomes.html +01/25/2018 23:59 +LO[W2.6]★ +Can use Git to save history (2.6a, 2.6b, 2.6c, 2.6d, 2.6e, 2.6f): checkurlhttps://nus-cs2103-ay1718s2.github.io/website/schedule/week2/outcomes.html +01/25/2018 23:59 +LO[W2.6a]★ +Can explain revision control: checkurlhttps://nus-cs2103-ay1718s2.github.io/website/schedule/week2/outcomes.html +01/25/2018 23:59 +LO[W2.6b]★ +Can explain repositories: checkurlhttps://nus-cs2103-ay1718s2.github.io/website/schedule/week2/outcomes.html +01/25/2018 23:59 +LO[W2.6c]★ +Can create a local Git repo: checkurlhttps://nus-cs2103-ay1718s2.github.io/website/schedule/week2/outcomes.html +01/25/2018 23:59 +LO[W2.6d]★ +Can explain saving history: checkurlhttps://nus-cs2103-ay1718s2.github.io/website/schedule/week2/outcomes.html +01/25/2018 23:59 +LO[W2.6e][Submission]★ +Can commit using Git: checkurlhttps://nus-cs2103-ay1718s2.github.io/website/schedule/week2/outcomes.html +01/25/2018 23:59 +LO[W2.6f][Submission]★★ +Can set Git to ignore files: checkurlhttps://nus-cs2103-ay1718s2.github.io/website/schedule/week2/outcomes.html +01/25/2018 23:59 +LO[W3.10][Compulsory][Submission]★ +Work with a 1KLoC code base: checkurlhttps://nus-cs2103-ay1718s2.github.io/website/schedule/week3/outcomes.html +02/01/2018 23:59 +LO[W4.1]★★★ +Can explain models: checkurlhttps://nus-cs2103-ay1718s2.github.io/website/schedule/week4/outcomes.html +02/08/2018 23:59 +LO[W5.11][Compulsory][Submission]★ +Work with a 2KLoC code base: checkurlhttps://nus-cs2103-ay1718s2.github.io/website/schedule/week5/outcomes.html +02/15/2018 23:59 +LO[W6.5][Submission]★★★ +Can use JavaFX to build a simple GUI: checkurlhttps://nus-cs2103-ay1718s2.github.io/website/schedule/week6/outcomes.html +02/22/2018 23:59 diff --git a/src/main/resources/images/contact/user.JPG b/src/main/resources/images/contact/user.JPG new file mode 100644 index 000000000000..102380155183 Binary files /dev/null and b/src/main/resources/images/contact/user.JPG differ diff --git a/src/main/resources/images/email.png b/src/main/resources/images/email.png new file mode 100644 index 000000000000..07435d1d8b12 Binary files /dev/null and b/src/main/resources/images/email.png differ diff --git a/src/main/resources/images/github.png b/src/main/resources/images/github.png new file mode 100644 index 000000000000..acd5d2429895 Binary files /dev/null and b/src/main/resources/images/github.png differ diff --git a/src/main/resources/images/major.png b/src/main/resources/images/major.png new file mode 100644 index 000000000000..93f7ce880f6a Binary files /dev/null and b/src/main/resources/images/major.png differ diff --git a/src/main/resources/images/name.png b/src/main/resources/images/name.png new file mode 100644 index 000000000000..bfee676eac77 Binary files /dev/null and b/src/main/resources/images/name.png differ diff --git a/src/main/resources/images/phone.png b/src/main/resources/images/phone.png new file mode 100644 index 000000000000..ae2178e956ed Binary files /dev/null and b/src/main/resources/images/phone.png differ diff --git a/src/main/resources/images/profile_background.jpg b/src/main/resources/images/profile_background.jpg new file mode 100644 index 000000000000..916472e4dfac Binary files /dev/null and b/src/main/resources/images/profile_background.jpg differ diff --git a/src/main/resources/images/profile_background.png b/src/main/resources/images/profile_background.png new file mode 100644 index 000000000000..a872a94306d5 Binary files /dev/null and b/src/main/resources/images/profile_background.png differ diff --git a/src/main/resources/images/profile_background0008.png b/src/main/resources/images/profile_background0008.png new file mode 100644 index 000000000000..e27531f31931 Binary files /dev/null and b/src/main/resources/images/profile_background0008.png differ diff --git a/src/main/resources/images/profile_background_dark.png b/src/main/resources/images/profile_background_dark.png new file mode 100644 index 000000000000..48993a5e8dc8 Binary files /dev/null and b/src/main/resources/images/profile_background_dark.png differ diff --git a/src/main/resources/images/profile_photo.jpg b/src/main/resources/images/profile_photo.jpg new file mode 100644 index 000000000000..e690e130662f Binary files /dev/null and b/src/main/resources/images/profile_photo.jpg differ diff --git a/src/main/resources/images/address_book_32.png b/src/main/resources/images/progress_checker.png similarity index 100% rename from src/main/resources/images/address_book_32.png rename to src/main/resources/images/progress_checker.png diff --git a/src/main/resources/images/progress_checker_32.png b/src/main/resources/images/progress_checker_32.png new file mode 100644 index 000000000000..21ece12da4a8 Binary files /dev/null and b/src/main/resources/images/progress_checker_32.png differ diff --git a/src/main/resources/images/year.png b/src/main/resources/images/year.png new file mode 100644 index 000000000000..b2f02482a917 Binary files /dev/null and b/src/main/resources/images/year.png differ diff --git a/src/main/resources/testTasks.txt b/src/main/resources/testTasks.txt new file mode 100644 index 000000000000..4f313c93e9cc --- /dev/null +++ b/src/main/resources/testTasks.txt @@ -0,0 +1,12 @@ +LO[W3.10][Compulsory][Submission]★ +Work with a 1KLoC code base: checkurlhttps://nus-cs2103-ay1718s2.github.io/website/schedule/week3/outcomes.html +02/01/2018 23:59 +LO[W4.1]★★★ +Can explain models: checkurlhttps://nus-cs2103-ay1718s2.github.io/website/schedule/week4/outcomes.html +02/08/2018 23:59 +LO[W5.11][Compulsory][Submission]★ +Work with a 2KLoC code base: checkurlhttps://nus-cs2103-ay1718s2.github.io/website/schedule/week5/outcomes.html +02/15/2018 23:59 +LO[W6.5][Submission]★★★ +Can use JavaFX to build a simple GUI: checkurlhttps://nus-cs2103-ay1718s2.github.io/website/schedule/week6/outcomes.html +02/22/2018 23:59 diff --git a/src/main/resources/view/Browser2Panel.fxml b/src/main/resources/view/Browser2Panel.fxml new file mode 100644 index 000000000000..26af0d175648 --- /dev/null +++ b/src/main/resources/view/Browser2Panel.fxml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import javafx.scene.layout.StackPane?> +<?import javafx.scene.web.WebView?> + +<StackPane xmlns:fx="http://javafx.com/fxml/1"> + <WebView fx:id="browser2"/> +</StackPane> diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css index d06336391cca..a857bc20c8eb 100644 --- a/src/main/resources/view/DarkTheme.css +++ b/src/main/resources/view/DarkTheme.css @@ -1,42 +1,75 @@ .background { - -fx-background-color: derive(#1d1d1d, 20%); - background-color: #383838; /* Used in the default.html file */ + -fx-background-color: #202226; + background-color: #202226; } .label { - -fx-font-size: 11pt; + -fx-font-size: 12pt; -fx-font-family: "Segoe UI Semibold"; -fx-text-fill: #555555; -fx-opacity: 0.9; } .label-bright { - -fx-font-size: 11pt; + -fx-font-size: 12pt; -fx-font-family: "Segoe UI Semibold"; - -fx-text-fill: white; + -fx-text-fill: black; -fx-opacity: 1; } .label-header { -fx-font-size: 32pt; -fx-font-family: "Segoe UI Light"; - -fx-text-fill: white; + -fx-text-fill: black; -fx-opacity: 1; } .text-field { -fx-font-size: 12pt; -fx-font-family: "Segoe UI Semibold"; + -fx-text-fill: #555555; + -fx-opacity: 1; +} + +.tab-pane .tab-header-area .tab-header-background { + -fx-opacity: 0; } .tab-pane { - -fx-padding: 0 0 0 1; + -fx-tab-min-width:90px; +} + +.tab { + -fx-background-insets: 0 1 0 1,0,0; } .tab-pane .tab-header-area { -fx-padding: 0 0 0 0; -fx-min-height: 0; -fx-max-height: 0; + -fx-background-color: #202226; +} + +.tab-pane .tab:selected +{ + -fx-background-color: #203133; +} + +.tab-pane .tab +{ + -fx-background-color: #5e6d81; +} + +.tab .tab-label { + -fx-alignment: CENTER; + -fx-text-fill: silver; + -fx-font-size: 16px; +} + +.tab:selected .tab-label { + -fx-alignment: CENTER; + -fx-text-fill: #ffffff; + -fx-font-weight: bold; } .table-view { @@ -77,52 +110,56 @@ } .split-pane:horizontal .split-pane-divider { - -fx-background-color: derive(#1d1d1d, 20%); - -fx-border-color: transparent transparent transparent #4d4d4d; + -fx-background-color: #202226; + -fx-border-color: transparent transparent transparent #202226; } .split-pane { -fx-border-radius: 1; -fx-border-width: 1; - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: #202226; } .list-view { -fx-background-insets: 0; -fx-padding: 0; - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: #202226; } .list-cell { - -fx-label-padding: 0 0 0 0; + -fx-label-padding: 20; -fx-graphic-text-gap : 0; -fx-padding: 0 0 0 0; + -fx-background-color: #202226; + -fx-background-insets: 0 10 15 0; + -fx-background-radius: 5; } .list-cell:filled:even { - -fx-background-color: #3c3e3f; + -fx-background-color: #203133; + -fx-effect: dropshadow(gaussian, #121417, 3, 0, 2, 2); } .list-cell:filled:odd { - -fx-background-color: #515658; + -fx-background-color: #19282a; + -fx-effect: dropshadow(gaussian, #121417, 3, 0, 2, 2); } .list-cell:filled:selected { -fx-background-color: #424d5f; } -.list-cell:filled:selected #cardPane { - -fx-border-color: #3e7b91; - -fx-border-width: 1; +.list-cell .label { + -fx-text-fill: #ffffff; } -.list-cell .label { - -fx-text-fill: white; +.list-cell:selected .label { + -fx-text-fill: #ffffff; } .cell_big_label { - -fx-font-family: "Segoe UI Semibold"; - -fx-font-size: 16px; + -fx-font-family: "Segoe UI"; + -fx-font-size: 18px; -fx-text-fill: #010504; } @@ -133,13 +170,11 @@ } .anchor-pane { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: purple; } .pane-with-border { - -fx-background-color: derive(#1d1d1d, 20%); - -fx-border-color: derive(#1d1d1d, 10%); - -fx-border-top-width: 1px; + -fx-background-color: #202226; } .status-bar { @@ -151,11 +186,11 @@ -fx-background-color: transparent; -fx-font-family: "Segoe UI Light"; -fx-font-size: 13pt; - -fx-text-fill: white; + -fx-text-fill: #ffffff; } .result-display .label { - -fx-text-fill: black !important; + -fx-text-fill: white !important; } .status-bar .label { @@ -174,8 +209,8 @@ } .grid-pane { - -fx-background-color: derive(#1d1d1d, 30%); - -fx-border-color: derive(#1d1d1d, 30%); + -fx-background-color: #202226; + -fx-border-color: #202226; -fx-border-width: 1px; } @@ -188,11 +223,11 @@ } .context-menu .label { - -fx-text-fill: white; + -fx-text-fill: #555555; } .menu-bar { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: #1fbba6; } .menu-bar .label { @@ -281,12 +316,14 @@ } .scroll-bar { - -fx-background-color: derive(#1d1d1d, 20%); + -fx-background-color: #f9f9f9; + -fx-background-radius: 10; } .scroll-bar .thumb { - -fx-background-color: derive(#1d1d1d, 50%); - -fx-background-insets: 3; + -fx-background-color: #1fbba6; + -fx-background-insets: 5; + -fx-background-radius: 10; } .scroll-bar .increment-button, .scroll-bar .decrement-button { @@ -313,18 +350,17 @@ #commandTypeLabel { -fx-font-size: 11px; - -fx-text-fill: #F70D1A; + -fx-text-fill: #ffffff; } #commandTextField { - -fx-background-color: transparent #383838 transparent #383838; + -fx-background-color: #203133; -fx-background-insets: 0; - -fx-border-color: #383838 #383838 #ffffff #383838; - -fx-border-insets: 0; + -fx-border-color: #203133; -fx-border-width: 1; -fx-font-family: "Segoe UI Light"; -fx-font-size: 13pt; - -fx-text-fill: white; + -fx-text-fill: #ffffff; } #filterField, #personListPanel, #personWebpage { @@ -332,7 +368,7 @@ } #resultDisplay .content { - -fx-background-color: transparent, #383838, transparent, #383838; + -fx-background-color: #203133; -fx-background-radius: 0; } @@ -349,3 +385,90 @@ -fx-background-radius: 2; -fx-font-size: 11; } + +#tags .red { + -fx-background-color: #fc6d5f; +} + +#tags .orange { + -fx-background-color: #ff9e2b; +} + +#tags .yellow { + -fx-background-color: #ffc225; +} + +#tags .green { + -fx-background-color: #31b282; +} + +#tags .blue { + -fx-background-color: #00b7ee; +} + +#tags .purple { + -fx-background-color: #8064a7; +} + +#labelled { + -fx-hgap: 7; + -fx-vgap: 3; +} + +#labelled .label { + -fx-text-fill: white; + -fx-background-color: #3e7b91; + -fx-padding: 1 3 1 3; + -fx-border-radius: 2; + -fx-background-radius: 2; + -fx-font-size: 11; +} + +#labelled .red { + -fx-background-color: #fc6d5f; +} + +#labelled .orange { + -fx-background-color: #ff9e2b; +} + +#labelled .yellow { + -fx-background-color: #ffc225; +} + +#labelled .green { + -fx-background-color: #31b282; +} + +#labelled .blue { + -fx-background-color: #00b7ee; +} + +#labelled .purple { + -fx-background-color: #8064a7; +} + +.exercise .label { + -fx-text-fill: #ffffff; +} + +.exercise .question-number { + -fx-text-fill: white; + -fx-background-color: #fc6d5f; + -fx-padding: 0 6 0 6; + -fx-border-radius: 2; + -fx-background-radius: 2; + -fx-font-size: 16; +} + +.exercise .answered { + -fx-background-color: #31b282; +} + +.exercise .suggested-answer { + -fx-text-fill: #0099C7; +} + +.profile .label { + -fx-text-fill: white; +} diff --git a/src/main/resources/view/DayTheme.css b/src/main/resources/view/DayTheme.css new file mode 100644 index 000000000000..612e45921c31 --- /dev/null +++ b/src/main/resources/view/DayTheme.css @@ -0,0 +1,484 @@ +.background { + -fx-background-color: #eaedf1; + background-color: #eaedf1; +} + +.label { + -fx-font-size: 12pt; + -fx-font-family: "Segoe UI Semibold"; + -fx-text-fill: #555555; + -fx-opacity: 0.9; +} + +.label-bright { + -fx-font-size: 12pt; + -fx-font-family: "Segoe UI Semibold"; + -fx-text-fill: black; + -fx-opacity: 1; +} + +.label-header { + -fx-font-size: 32pt; + -fx-font-family: "Segoe UI Light"; + -fx-text-fill: black; + -fx-opacity: 1; +} + +.text-field { + -fx-font-size: 12pt; + -fx-font-family: "Segoe UI Semibold"; + -fx-text-fill: #555555; + -fx-opacity: 1; +} + +.tab-pane .tab-header-area .tab-header-background { + -fx-opacity: 0; +} + +.tab-pane { + -fx-tab-min-width:90px; +} + +.tab { + -fx-background-insets: 0 1 0 1,0,0; +} + +.tab-pane .tab-header-area { + -fx-padding: 0 0 0 0; + -fx-min-height: 0; + -fx-max-height: 0; + -fx-background-color: #eaedf1; +} + +.tab-pane .tab:selected +{ + -fx-background-color: #ffffff; +} + +.tab-pane .tab +{ + -fx-background-color: #f9fafc; +} + +.tab .tab-label { + -fx-alignment: CENTER; + -fx-text-fill: silver; + -fx-font-size: 16px; +} + +.tab:selected .tab-label { + -fx-alignment: CENTER; + -fx-text-fill: #5e6d81; + -fx-font-weight: bold; +} + +.table-view { + -fx-base: #1d1d1d; + -fx-control-inner-background: #1d1d1d; + -fx-background-color: #1d1d1d; + -fx-table-cell-border-color: transparent; + -fx-table-header-border-color: transparent; + -fx-padding: 5; +} + +.table-view .column-header-background { + -fx-background-color: transparent; +} + +.table-view .column-header, .table-view .filler { + -fx-size: 35; + -fx-border-width: 0 0 1 0; + -fx-background-color: transparent; + -fx-border-color: + transparent + transparent + derive(-fx-base, 80%) + transparent; + -fx-border-insets: 0 10 1 0; +} + +.table-view .column-header .label { + -fx-font-size: 20pt; + -fx-font-family: "Segoe UI Light"; + -fx-text-fill: white; + -fx-alignment: center-left; + -fx-opacity: 1; +} + +.table-view:focused .table-row-cell:filled:focused:selected { + -fx-background-color: -fx-focus-color; +} + +.split-pane:horizontal .split-pane-divider { + -fx-background-color: #eaedf1; + -fx-border-color: transparent transparent transparent #eaedf1; +} + +.split-pane { + -fx-border-radius: 1; + -fx-border-width: 1; + -fx-background-color: #eaedf1; +} + +.list-view { + -fx-background-insets: 0; + -fx-padding: 0; + -fx-background-color: #eaedf1; +} + +.list-cell { + -fx-label-padding: 20; + -fx-graphic-text-gap : 0; + -fx-padding: 0 0 0 0; + -fx-background-color: #eaedf1; + -fx-background-insets: 0 10 15 0; + -fx-background-radius: 5; +} + +.list-cell:filled:even { + -fx-background-color: #ffffff; + -fx-effect: dropshadow(gaussian, #9ea7b3, 3, 0, 2, 2); +} + +.list-cell:filled:odd { + -fx-background-color: #f9f9f9; + -fx-effect: dropshadow(gaussian, #9ea7b3, 3, 0, 2, 2); +} + +.list-cell:filled:selected { + -fx-background-color: #424d5f; +} + +.list-cell .label { + -fx-text-fill: #555555; +} + +.list-cell:selected .label { + -fx-text-fill: #ffffff; +} + +.cell_big_label { + -fx-font-family: "Segoe UI"; + -fx-font-size: 18px; + -fx-text-fill: #010504; +} + +.cell_small_label { + -fx-font-family: "Segoe UI"; + -fx-font-size: 13px; + -fx-text-fill: #010504; +} + +.anchor-pane { + -fx-background-color: purple; +} + +.pane-with-border { + -fx-background-color: #eaedf1; +} + +.status-bar { + -fx-background-color: derive(#1d1d1d, 20%); + -fx-text-fill: black; +} + +.result-display { + -fx-background-color: transparent; + -fx-font-family: "Segoe UI Light"; + -fx-font-size: 13pt; + -fx-text-fill: #555555; +} + +.result-display .label { + -fx-text-fill: black !important; +} + +.status-bar .label { + -fx-font-family: "Segoe UI Light"; + -fx-text-fill: white; +} + +.status-bar-with-border { + -fx-background-color: derive(#1d1d1d, 30%); + -fx-border-color: derive(#1d1d1d, 25%); + -fx-border-width: 1px; +} + +.status-bar-with-border .label { + -fx-text-fill: white; +} + +.grid-pane { + -fx-background-color: #ffffff; + -fx-border-color: #ffffff; + -fx-border-width: 1px; +} + +.grid-pane .anchor-pane { + -fx-background-color: derive(#1d1d1d, 30%); +} + +.context-menu { + -fx-background-color: derive(#1d1d1d, 50%); +} + +.context-menu .label { + -fx-text-fill: #555555; +} + +.menu-bar { + -fx-background-color: #1fbba6; +} + +.menu-bar .label { + -fx-font-size: 14pt; + -fx-font-family: "Segoe UI Light"; + -fx-text-fill: white; + -fx-opacity: 0.9; +} + +.menu .left-container { + -fx-background-color: black; +} + +/* + * Metro style Push Button + * Author: Pedro Duque Vieira + * http://pixelduke.wordpress.com/2012/10/23/jmetro-windows-8-controls-on-java/ + */ +.button { + -fx-padding: 5 22 5 22; + -fx-border-color: #e2e2e2; + -fx-border-width: 2; + -fx-background-radius: 0; + -fx-background-color: #1d1d1d; + -fx-font-family: "Segoe UI", Helvetica, Arial, sans-serif; + -fx-font-size: 11pt; + -fx-text-fill: #d8d8d8; + -fx-background-insets: 0 0 0 0, 0, 1, 2; +} + +.button:hover { + -fx-background-color: #3a3a3a; +} + +.button:pressed, .button:default:hover:pressed { + -fx-background-color: white; + -fx-text-fill: #1d1d1d; +} + +.button:focused { + -fx-border-color: white, white; + -fx-border-width: 1, 1; + -fx-border-style: solid, segments(1, 1); + -fx-border-radius: 0, 0; + -fx-border-insets: 1 1 1 1, 0; +} + +.button:disabled, .button:default:disabled { + -fx-opacity: 0.4; + -fx-background-color: #1d1d1d; + -fx-text-fill: white; +} + +.button:default { + -fx-background-color: -fx-focus-color; + -fx-text-fill: #ffffff; +} + +.button:default:hover { + -fx-background-color: derive(-fx-focus-color, 30%); +} + +.dialog-pane { + -fx-background-color: #1d1d1d; +} + +.dialog-pane > *.button-bar > *.container { + -fx-background-color: #1d1d1d; +} + +.dialog-pane > *.label.content { + -fx-font-size: 14px; + -fx-font-weight: bold; + -fx-text-fill: white; +} + +.dialog-pane:header *.header-panel { + -fx-background-color: derive(#1d1d1d, 25%); +} + +.dialog-pane:header *.header-panel *.label { + -fx-font-size: 18px; + -fx-font-style: italic; + -fx-fill: white; + -fx-text-fill: white; +} + +.scroll-bar { + -fx-background-color: #f9f9f9; + -fx-background-radius: 10; +} + +.scroll-bar .thumb { + -fx-background-color: #1fbba6; + -fx-background-insets: 5; + -fx-background-radius: 10; +} + +.scroll-bar .increment-button, .scroll-bar .decrement-button { + -fx-background-color: transparent; + -fx-padding: 0 0 0 0; +} + +.scroll-bar .increment-arrow, .scroll-bar .decrement-arrow { + -fx-shape: " "; +} + +.scroll-bar:vertical .increment-arrow, .scroll-bar:vertical .decrement-arrow { + -fx-padding: 1 8 1 8; +} + +.scroll-bar:horizontal .increment-arrow, .scroll-bar:horizontal .decrement-arrow { + -fx-padding: 8 1 8 1; +} + +#cardPane { + -fx-background-color: transparent; + -fx-border-width: 0; +} + +#commandTypeLabel { + -fx-font-size: 11px; + -fx-text-fill: #555555; +} + +#commandTextField { + -fx-background-color: #ffffff; + -fx-background-insets: 0; + -fx-border-color: #ffffff; + -fx-border-width: 1; + -fx-font-family: "Segoe UI Light"; + -fx-font-size: 13pt; + -fx-text-fill: #555555; +} + +#filterField, #personListPanel, #personWebpage { + -fx-effect: innershadow(gaussian, black, 10, 0, 0, 0); +} + +#resultDisplay .content { + -fx-background-color: #ffffff; + -fx-background-radius: 0; +} + +#tags { + -fx-hgap: 7; + -fx-vgap: 3; +} + +#tags .label { + -fx-text-fill: white; + -fx-background-color: #3e7b91; + -fx-padding: 1 3 1 3; + -fx-border-radius: 2; + -fx-background-radius: 2; + -fx-font-size: 11; +} + +#tags .red { + -fx-background-color: #fc6d5f; +} + +#tags .orange { + -fx-background-color: #ff9e2b; +} + +#tags .yellow { + -fx-background-color: #ffc225; +} + +#tags .green { + -fx-background-color: #31b282; +} + +#tags .blue { + -fx-background-color: #00b7ee; +} + +#tags .purple { + -fx-background-color: #8064a7; +} + +#labelled { + -fx-hgap: 7; + -fx-vgap: 3; +} + +#labelled .label { + -fx-text-fill: white; + -fx-background-color: #3e7b91; + -fx-padding: 1 3 1 3; + -fx-border-radius: 2; + -fx-background-radius: 2; + -fx-font-size: 11; +} + +#labelled .red { + -fx-background-color: #fc6d5f; +} + +#labelled .orange { + -fx-background-color: #ff9e2b; +} + +#labelled .yellow { + -fx-background-color: #ffc225; +} + +#labelled .green { + -fx-background-color: #31b282; +} + +#labelled .blue { + -fx-background-color: #00b7ee; +} + +#labelled .purple { + -fx-background-color: #8064a7; +} + +.exercise .label { + -fx-text-fill: #555555; +} + +.exercise .question-number { + -fx-text-fill: white; + -fx-background-color: #fc6d5f; + -fx-padding: 0 6 0 6; + -fx-border-radius: 2; + -fx-background-radius: 2; + -fx-font-size: 16; +} + +.exercise .answered { + -fx-background-color: #31b282; +} + +.exercise .suggested-answer { + -fx-text-fill: #0099C7; +} + +.main-box { + -fx-border-color: #28a43f; + -fx-border-radius: 0; + -fx-border-width: 0 0 0 5; + /*-fx-effect: dropshadow(gaussian, black, 0, 16, 2, 8); */ + -fx-padding: 0 0 0 0; + /*-fx-effect: innershadow(gaussian, black, 10, 0, 0, 0);*/ +} + +.main-box .title-issue { + /*-fx-font-weight: 10;*/ + -fx-font-size: 10; +} diff --git a/src/main/resources/view/ExerciseListCard.fxml b/src/main/resources/view/ExerciseListCard.fxml new file mode 100644 index 000000000000..e9d3e88e08a8 --- /dev/null +++ b/src/main/resources/view/ExerciseListCard.fxml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import javafx.geometry.Insets?> +<?import javafx.scene.control.Label?> +<?import javafx.scene.control.Separator?> +<?import javafx.scene.layout.VBox?> +<?import javafx.scene.text.Text?> + +<!-- @@author iNekox3 --> +<VBox id="cardPane" fx:id="cardPane" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"> + <padding> + <Insets bottom="5" left="15" right="5" top="5" /> + </padding> + <VBox prefWidth="325.0" styleClass="exercise"> + <children> + <Label fx:id="questionIndex" text="\$questionIndex" wrapText="true" styleClass="question-number"/> + <Label fx:id="question" text="\$question" wrapText="true"/> + <Text/> + <Separator/> + <Text/> + + <Label text="Your Answer: "/> + <Label fx:id="studentAnswer" text="\$studentAnswer" wrapText="true"/> + <Text/> + + <Label fx:id="modelAnswerHeader" styleClass="exercise-header, suggested-answer"/> + <Label fx:id="modelAnswer" text="\$modelAnswer" wrapText="true" styleClass="suggested-answer"/> + </children> + </VBox> +</VBox> diff --git a/src/main/resources/view/ExerciseListPanel.fxml b/src/main/resources/view/ExerciseListPanel.fxml new file mode 100644 index 000000000000..0d97c82b1f24 --- /dev/null +++ b/src/main/resources/view/ExerciseListPanel.fxml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import javafx.scene.control.ListView?> +<?import javafx.scene.layout.VBox?> + +<!-- @@author iNekox3 --> +<VBox xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"> + <ListView fx:id="exerciseListView" VBox.vgrow="ALWAYS" /> +</VBox> diff --git a/src/main/resources/view/IssueListCard.fxml b/src/main/resources/view/IssueListCard.fxml new file mode 100644 index 000000000000..87aafc9672ae --- /dev/null +++ b/src/main/resources/view/IssueListCard.fxml @@ -0,0 +1,81 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import javafx.geometry.Insets?> +<?import javafx.geometry.Point3D?> +<?import javafx.scene.control.Label?> +<?import javafx.scene.layout.ColumnConstraints?> +<?import javafx.scene.layout.FlowPane?> +<?import javafx.scene.layout.GridPane?> +<?import javafx.scene.layout.HBox?> +<?import javafx.scene.layout.Region?> +<?import javafx.scene.layout.RowConstraints?> +<?import javafx.scene.layout.VBox?> +<?import javafx.scene.text.Text?> + +<!-- @@author adityaa1998 --> + +<HBox id="cardPane" fx:id="cardPane" prefHeight="140.0" prefWidth="380.0" xmlns="http://javafx.com/javafx/8.0.121" xmlns:fx="http://javafx.com/fxml/1" styleClass="main-box"> + <GridPane prefHeight="140.0" prefWidth="380.0" HBox.hgrow="ALWAYS"> + <columnConstraints> + <ColumnConstraints hgrow="SOMETIMES" minWidth="10" prefWidth="150" /> + </columnConstraints> + <HBox prefHeight="140.0" prefWidth="380.0"> + <children> + <VBox alignment="CENTER_LEFT" minHeight="-Infinity" prefHeight="140.0" prefWidth="510.0"> + <padding> + <Insets bottom="5" left="15" right="5" top="5" /> + </padding> + <HBox alignment="CENTER_LEFT" spacing="5" prefWidth="380.0"> + <Label fx:id="id" styleClass="cell_big_label"> + <minWidth> + <!-- Ensures that the label text is never truncated --> + <Region fx:constant="USE_PREF_SIZE" /> + </minWidth> + </Label> + <Label fx:id="title" prefWidth="380.0" styleClass="title-issue" text="\$first" wrapText="true"> + <minWidth> + <!-- Ensures that the label text is never truncated --> + <Region fx:constant="USE_PREF_SIZE" /> + </minWidth> + </Label> + </HBox> + <FlowPane fx:id="labelled" /> + <GridPane prefHeight="80.0" prefWidth="301.0"> + <columnConstraints> + <ColumnConstraints hgrow="SOMETIMES" maxWidth="92.0" minWidth="0.0" prefWidth="78.0" /> + <ColumnConstraints hgrow="ALWAYS" maxWidth="360.0" minWidth="10.0" prefWidth="282.0" /> + </columnConstraints> + <rowConstraints> + <RowConstraints maxHeight="25.0" minHeight="10.0" prefHeight="20.0" vgrow="ALWAYS" /> + <RowConstraints maxHeight="32.0" minHeight="0.0" prefHeight="32.0" vgrow="ALWAYS" /> + <RowConstraints maxHeight="23.0" minHeight="7.0" prefHeight="17.0" vgrow="ALWAYS" /> + </rowConstraints> + <children> + <Text strokeType="OUTSIDE" strokeWidth="0.0" text="Body: " /> + <Label fx:id="body" styleClass="cell_small_label" text="\$body" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" GridPane.valignment="CENTER" GridPane.vgrow="ALWAYS" wrapText="true"/> + <Text strokeType="OUTSIDE" strokeWidth="0.0" text="Assignees: " GridPane.rowIndex="2" /> + <FlowPane fx:id="assignees" prefHeight="25.0" prefWidth="282.0" GridPane.columnIndex="1" GridPane.rowIndex="2"> + <GridPane.margin> + <Insets bottom="-1.0" top="1.0" /> + </GridPane.margin> + <padding> + <Insets top="3.0" /> + </padding></FlowPane> + <Text strokeType="OUTSIDE" strokeWidth="0.0" text="Milestone: " GridPane.rowIndex="1" /> + <Label fx:id="milestone" styleClass="cell_small_label" text="\$milestone" GridPane.columnIndex="1" GridPane.rowIndex="1"> + <rotationAxis> + <Point3D /> + </rotationAxis> + <GridPane.margin> + <Insets top="1.0" /> + </GridPane.margin></Label> + </children> + </GridPane> + </VBox> + </children> + </HBox> + <rowConstraints> + <RowConstraints /> + </rowConstraints> + </GridPane> +</HBox> diff --git a/src/main/resources/view/IssueListPanel.fxml b/src/main/resources/view/IssueListPanel.fxml new file mode 100644 index 000000000000..63d8a6b53b20 --- /dev/null +++ b/src/main/resources/view/IssueListPanel.fxml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import javafx.scene.control.ListView?> +<?import javafx.scene.layout.VBox?> + +<!-- @@author adityaa1998 --> +<VBox xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"> + <ListView fx:id="issueListView" VBox.vgrow="ALWAYS" /> +</VBox> diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml index 1dadb95b6ffe..3c0fb07f291d 100644 --- a/src/main/resources/view/MainWindow.fxml +++ b/src/main/resources/view/MainWindow.fxml @@ -1,29 +1,16 @@ <?xml version="1.0" encoding="UTF-8"?> -<?import java.net.URL?> <?import javafx.geometry.Insets?> -<?import javafx.scene.Scene?> <?import javafx.scene.control.Menu?> <?import javafx.scene.control.MenuBar?> <?import javafx.scene.control.MenuItem?> <?import javafx.scene.control.SplitPane?> -<?import javafx.scene.image.Image?> +<?import javafx.scene.control.Tab?> +<?import javafx.scene.control.TabPane?> <?import javafx.scene.layout.StackPane?> <?import javafx.scene.layout.VBox?> -<fx:root type="javafx.stage.Stage" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" - minWidth="450" minHeight="600"> - <icons> - <Image url="@/images/address_book_32.png" /> - </icons> - <scene> - <Scene> - <stylesheets> - <URL value="@DarkTheme.css" /> - <URL value="@Extensions.css" /> - </stylesheets> - - <VBox> +<VBox xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"> <MenuBar fx:id="menuBar" VBox.vgrow="NEVER"> <Menu mnemonicParsing="false" text="File"> <MenuItem mnemonicParsing="false" onAction="#handleExit" text="Exit" /> @@ -31,38 +18,60 @@ <Menu mnemonicParsing="false" text="Help"> <MenuItem fx:id="helpMenuItem" mnemonicParsing="false" onAction="#handleHelp" text="Help" /> </Menu> + <!-- @@author Livian1107 --> + <Menu mnemonicParsing="false" text="Theme"> + <MenuItem fx:id="nightTheme" mnemonicParsing="false" onAction="#handleNightTheme" text="NightTheme" /> + <MenuItem fx:id="dayTheme" mnemonicParsing="false" onAction="#handleDayTheme" text="DayTheme" /> + </Menu> + <!-- @@author --> </MenuBar> - <StackPane VBox.vgrow="NEVER" fx:id="commandBoxPlaceholder" styleClass="pane-with-border"> + <StackPane fx:id="commandBoxPlaceholder" styleClass="pane-with-border" VBox.vgrow="NEVER"> <padding> - <Insets top="5" right="10" bottom="5" left="10" /> + <Insets bottom="5" left="10" right="10" top="5" /> </padding> </StackPane> - <StackPane VBox.vgrow="NEVER" fx:id="resultDisplayPlaceholder" styleClass="pane-with-border" - minHeight="100" prefHeight="100" maxHeight="100"> + <StackPane fx:id="resultDisplayPlaceholder" maxHeight="100" minHeight="100" prefHeight="100" styleClass="pane-with-border" VBox.vgrow="NEVER"> <padding> - <Insets top="5" right="10" bottom="5" left="10" /> + <Insets bottom="5" left="10" right="10" top="5" /> </padding> </StackPane> <SplitPane id="splitPane" fx:id="splitPane" dividerPositions="0.4" VBox.vgrow="ALWAYS"> <VBox fx:id="personList" minWidth="340" prefWidth="340" SplitPane.resizableWithParent="false"> <padding> - <Insets top="10" right="10" bottom="10" left="10" /> + <Insets bottom="10" left="10" right="10" top="10" /> </padding> - <StackPane fx:id="personListPanelPlaceholder" VBox.vgrow="ALWAYS"/> + <StackPane fx:id="personListPanelPlaceholder" VBox.vgrow="ALWAYS" /> </VBox> - - <StackPane fx:id="browserPlaceholder" prefWidth="340" > - <padding> - <Insets top="10" right="10" bottom="10" left="10" /> - </padding> - </StackPane> + <!-- @@author Livian1107 --> + <TabPane fx:id="tabPlaceholder" prefHeight="200.0" prefWidth="200.0" tabClosingPolicy="UNAVAILABLE"> + <tabs> + <Tab fx:id="profilePlaceholder" text="Profile"> + <StackPane fx:id="profilePanelPlaceholder" VBox.vgrow="ALWAYS" /> + </Tab> + <Tab fx:id="taskPlaceholder" text="Task"> + <!-- @@author --> + <!-- @@author EdwardKSG --> + <SplitPane id="taskPane" fx:id="taskPane" dividerPositions="0.25" orientation="VERTICAL" VBox.vgrow="ALWAYS"> + <items> + <StackPane fx:id="browser2Placeholder" maxHeight="-Infinity" minHeight="-Infinity" prefHeight="120.0" prefWidth="200.0" /> + <StackPane fx:id="browserPlaceholder" prefHeight="120.0" prefWidth="200.0" /> + </items> + </SplitPane> + <!-- @@author --> + <!-- @@author Livian1107 --> + </Tab> + <Tab fx:id="exercisePlaceholder" text="Exercise"> + <StackPane fx:id="exerciseListPanelPlaceholder" VBox.vgrow="ALWAYS" /> + </Tab> + <Tab fx:id="issuePlaceholder" text="Issues"> + <StackPane fx:id="issuePanelPlaceholder" /> + </Tab> + </tabs> + </TabPane> + <!-- @@author --> </SplitPane> - - <StackPane fx:id="statusbarPlaceholder" VBox.vgrow="NEVER" /> - </VBox> - </Scene> - </scene> -</fx:root> + <StackPane fx:id="statusbarPlaceholder" VBox.vgrow="NEVER" /> +</VBox> diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/PersonListCard.fxml index f08ea32ad558..729c76084a26 100644 --- a/src/main/resources/view/PersonListCard.fxml +++ b/src/main/resources/view/PersonListCard.fxml @@ -3,34 +3,37 @@ <?import javafx.geometry.Insets?> <?import javafx.scene.control.Label?> <?import javafx.scene.layout.ColumnConstraints?> -<?import javafx.scene.layout.FlowPane?> <?import javafx.scene.layout.GridPane?> <?import javafx.scene.layout.HBox?> <?import javafx.scene.layout.Region?> +<?import javafx.scene.layout.RowConstraints?> <?import javafx.scene.layout.VBox?> -<HBox id="cardPane" fx:id="cardPane" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"> - <GridPane HBox.hgrow="ALWAYS"> +<HBox id="cardPane" fx:id="cardPane" prefHeight="72.0" prefWidth="200.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"> + <GridPane prefHeight="72.0" prefWidth="200.0" HBox.hgrow="ALWAYS"> <columnConstraints> <ColumnConstraints hgrow="SOMETIMES" minWidth="10" prefWidth="150" /> </columnConstraints> - <VBox alignment="CENTER_LEFT" minHeight="105" GridPane.columnIndex="0"> - <padding> - <Insets top="5" right="5" bottom="5" left="15" /> - </padding> - <HBox spacing="5" alignment="CENTER_LEFT"> - <Label fx:id="id" styleClass="cell_big_label"> - <minWidth> - <!-- Ensures that the label text is never truncated --> - <Region fx:constant="USE_PREF_SIZE" /> - </minWidth> - </Label> - <Label fx:id="name" text="\$first" styleClass="cell_big_label" /> + <HBox prefHeight="72.0" prefWidth="200.0"> + <children> + <VBox alignment="CENTER_LEFT" minHeight="-Infinity" prefHeight="56.0" prefWidth="216.0"> + <padding> + <Insets bottom="5" left="15" right="5" top="5" /> + </padding> + <HBox alignment="CENTER_LEFT" spacing="5"> + <Label fx:id="id" styleClass="cell_big_label"> + <minWidth> + <!-- Ensures that the label text is never truncated --> + <Region fx:constant="USE_PREF_SIZE" /> + </minWidth> + </Label> + <Label fx:id="name" styleClass="cell_big_label" text="\$first" /> + </HBox> + </VBox> + </children> </HBox> - <FlowPane fx:id="tags" /> - <Label fx:id="phone" styleClass="cell_small_label" text="\$phone" /> - <Label fx:id="address" styleClass="cell_small_label" text="\$address" /> - <Label fx:id="email" styleClass="cell_small_label" text="\$email" /> - </VBox> + <rowConstraints> + <RowConstraints /> + </rowConstraints> </GridPane> </HBox> diff --git a/src/main/resources/view/ProfilePanel.fxml b/src/main/resources/view/ProfilePanel.fxml new file mode 100644 index 000000000000..009f082ff528 --- /dev/null +++ b/src/main/resources/view/ProfilePanel.fxml @@ -0,0 +1,96 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import javafx.scene.control.Label?> +<?import javafx.scene.image.Image?> +<?import javafx.scene.image.ImageView?> +<?import javafx.scene.layout.ColumnConstraints?> +<?import javafx.scene.layout.FlowPane?> +<?import javafx.scene.layout.GridPane?> +<?import javafx.scene.layout.Pane?> +<?import javafx.scene.layout.RowConstraints?> +<?import javafx.scene.shape.Ellipse?> + +<!-- @@author Livian1107 --> + +<Pane prefHeight="500.0" prefWidth="731.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"> + <children> + <ImageView fx:id="profileBackground" fitHeight="500.0" fitWidth="1000.0" layoutX="-127.0" pickOnBounds="true" preserveRatio="true"> + <image> + <Image url="@../images/profile_background0008.png" /> + </image> + </ImageView> + <Ellipse fx:id="profile" centerX="300.0" centerY="100.0" fill="WHITE" layoutX="87.0" layoutY="14.0" radiusX="100.0" radiusY="80.0" stroke="BLACK" strokeType="INSIDE" /> + <GridPane layoutX="28.0" layoutY="59.0" prefHeight="28.0" prefWidth="254.0" styleClass="profile"> + <columnConstraints> + <ColumnConstraints hgrow="SOMETIMES" maxWidth="94.0" minWidth="10.0" prefWidth="34.0" /> + <ColumnConstraints hgrow="SOMETIMES" maxWidth="94.0" minWidth="10.0" prefWidth="55.0" /> + <ColumnConstraints hgrow="SOMETIMES" maxWidth="174.0" minWidth="10.0" prefWidth="168.0" /> + </columnConstraints> + <rowConstraints> + <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> + </rowConstraints> + <children> + <Label fx:id="name" prefHeight="17.0" prefWidth="164.0" text="\$name" GridPane.columnIndex="2" /> + <Label text="Name:" GridPane.columnIndex="1" /> + <ImageView fitHeight="19.0" fitWidth="19.0" pickOnBounds="true" preserveRatio="true"> + <image> + <Image url="@../images/name.png" /> + </image> + </ImageView> + </children> + </GridPane> + <FlowPane fx:id="tags" layoutX="28.0" layoutY="20.0" prefHeight="28.0" prefWidth="135.0" /> + <GridPane layoutX="260.0" layoutY="214.0" prefHeight="144.0" prefWidth="307.0" styleClass="profile"> + <columnConstraints> + <ColumnConstraints hgrow="SOMETIMES" maxWidth="95.0" minWidth="10.0" prefWidth="20.0" /> + <ColumnConstraints hgrow="SOMETIMES" maxWidth="95.0" minWidth="10.0" prefWidth="79.0" /> + <ColumnConstraints hgrow="SOMETIMES" maxWidth="197.0" minWidth="10.0" prefWidth="197.0" /> + </columnConstraints> + <rowConstraints> + <RowConstraints /> + <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> + <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> + <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> + <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> + <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> + </rowConstraints> + <children> + <Label fx:id="phone" prefHeight="17.0" prefWidth="226.0" text="\$phone" GridPane.columnIndex="2" GridPane.rowIndex="1" /> + <Label fx:id="email" prefHeight="17.0" prefWidth="215.0" text="\$email" GridPane.columnIndex="2" GridPane.rowIndex="2" /> + <Label fx:id="username" prefHeight="17.0" prefWidth="262.0" text="\$username" GridPane.columnIndex="2" GridPane.rowIndex="3" /> + <Label fx:id="major" prefHeight="17.0" prefWidth="197.0" text="\$major" GridPane.columnIndex="2" GridPane.rowIndex="4" /> + <Label fx:id="year" prefHeight="17.0" prefWidth="130.0" text="\$year" GridPane.columnIndex="2" GridPane.rowIndex="5" /> + <Label text="Phone:" GridPane.columnIndex="1" GridPane.rowIndex="1" /> + <Label prefHeight="17.0" prefWidth="56.0" text="Email:" GridPane.columnIndex="1" GridPane.rowIndex="2" /> + <Label prefHeight="17.0" prefWidth="61.0" text="Github:" GridPane.columnIndex="1" GridPane.rowIndex="3" /> + <Label text="Major:" GridPane.columnIndex="1" GridPane.rowIndex="4" /> + <Label text="Year:" GridPane.columnIndex="1" GridPane.rowIndex="5" /> + <ImageView fitHeight="19.0" fitWidth="19.0" layoutX="10.0" layoutY="15.0" pickOnBounds="true" preserveRatio="true" GridPane.rowIndex="1"> + <image> + <Image url="@../images/phone.png" /> + </image> + </ImageView> + <ImageView fitHeight="19.0" fitWidth="19.0" layoutX="10.0" layoutY="10.0" pickOnBounds="true" preserveRatio="true" GridPane.rowIndex="2"> + <image> + <Image url="@../images/email.png" /> + </image> + </ImageView> + <ImageView fitHeight="19.0" fitWidth="19.0" layoutX="10.0" layoutY="10.0" pickOnBounds="true" preserveRatio="true" GridPane.rowIndex="3"> + <image> + <Image url="@../images/github.png" /> + </image> + </ImageView> + <ImageView fitHeight="19.0" fitWidth="19.0" layoutX="10.0" layoutY="10.0" pickOnBounds="true" preserveRatio="true" GridPane.rowIndex="4"> + <image> + <Image url="@../images/major.png" /> + </image> + </ImageView> + <ImageView fitHeight="19.0" fitWidth="19.0" pickOnBounds="true" preserveRatio="true" GridPane.rowIndex="5"> + <image> + <Image url="@../images/year.png" /> + </image> + </ImageView> + </children> + </GridPane> + </children> +</Pane> diff --git a/src/main/resources/view/TaskDetailsPanel.fxml b/src/main/resources/view/TaskDetailsPanel.fxml new file mode 100644 index 000000000000..7b57df2088e7 --- /dev/null +++ b/src/main/resources/view/TaskDetailsPanel.fxml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import javafx.scene.layout.StackPane?> +<?import javafx.scene.web.WebView?> + +<StackPane xmlns:fx="http://javafx.com/fxml/1"> + <WebView fx:id="details"/> +</StackPane> + diff --git a/src/main/resources/view/TaskListCard.fxml b/src/main/resources/view/TaskListCard.fxml new file mode 100644 index 000000000000..613047d9d8ce --- /dev/null +++ b/src/main/resources/view/TaskListCard.fxml @@ -0,0 +1,65 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import javafx.geometry.Insets?> +<?import javafx.scene.control.Label?> +<?import javafx.scene.layout.ColumnConstraints?> +<?import javafx.scene.layout.FlowPane?> +<?import javafx.scene.layout.GridPane?> +<?import javafx.scene.layout.HBox?> +<?import javafx.scene.layout.Region?> +<?import javafx.scene.layout.RowConstraints?> +<?import javafx.scene.layout.VBox?> +<?import javafx.scene.text.Text?> + +<!-- @@author EdwardKSG --> +<HBox id="taskCardPane" fx:id="taskCardPane" prefHeight="140.0" prefWidth="250.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"> + <GridPane prefHeight="140.0" prefWidth="362.0" HBox.hgrow="ALWAYS"> + <columnConstraints> + <ColumnConstraints hgrow="SOMETIMES" minWidth="10" prefWidth="150" /> + </columnConstraints> + <HBox prefHeight="140.0" prefWidth="250.0"> + <children> + <VBox alignment="CENTER_LEFT" minHeight="-Infinity" prefHeight="140.0" prefWidth="216.0"> + <padding> + <Insets bottom="5" left="15" right="5" top="5" /> + </padding> + <HBox alignment="CENTER_LEFT" spacing="5"> + <Label fx:id="id" styleClass="cell_big_label"> + <minWidth> + <!-- Ensures that the label text is never truncated --> + <Region fx:constant="USE_PREF_SIZE" /> + </minWidth> + </Label> + <Label fx:id="title" styleClass="cell_big_label" text="\$title" /> + </HBox> + <FlowPane fx:id="tags" /> + <GridPane prefHeight="80.0" prefWidth="199.0"> + <columnConstraints> + <ColumnConstraints hgrow="SOMETIMES" maxWidth="89.0" minWidth="10.0" prefWidth="54.0" /> + <ColumnConstraints hgrow="SOMETIMES" maxWidth="171.0" minWidth="10.0" prefWidth="142.0" /> + </columnConstraints> + <rowConstraints> + <RowConstraints maxHeight="21.0" minHeight="0.0" prefHeight="20.0" vgrow="SOMETIMES" /> + <RowConstraints maxHeight="26.0" minHeight="10.0" prefHeight="20.0" vgrow="SOMETIMES" /> + <RowConstraints maxHeight="23.0" minHeight="7.0" prefHeight="20.0" vgrow="SOMETIMES" /> + <RowConstraints maxHeight="23.0" minHeight="10.0" prefHeight="20.0" vgrow="SOMETIMES" /> + </rowConstraints> + <children> + <Text fill="WHITE" strokeType="OUTSIDE" strokeWidth="0.0" text="PS:" GridPane.rowIndex="3" /> + <Label fx:id="ps" styleClass="cell_small_label" text="\$ps" GridPane.columnIndex="1" GridPane.rowIndex="3" /> + <Text fill="WHITE" strokeType="OUTSIDE" strokeWidth="0.0" text="Status:" GridPane.rowIndex="2" /> + <Label fx:id="status" styleClass="cell_small_label" text="\$status" GridPane.columnIndex="1" GridPane.rowIndex="2" /> + <Text fill="WHITE" strokeType="OUTSIDE" strokeWidth="0.0" text="Start:" /> + <Label fx:id="start" styleClass="cell_small_label" text="\$start" GridPane.columnIndex="1" /> + <Text fill="WHITE" strokeType="OUTSIDE" strokeWidth="0.0" text="Deadline:" GridPane.rowIndex="1" /> + <Label fx:id="deadline" styleClass="cell_small_label" text="\$deadline" GridPane.columnIndex="1" GridPane.rowIndex="1" /> + </children> + </GridPane> + </VBox> + </children> + </HBox> + <rowConstraints> + <RowConstraints /> + </rowConstraints> + </GridPane> +</HBox> diff --git a/src/main/resources/view/TaskListPanel.fxml b/src/main/resources/view/TaskListPanel.fxml new file mode 100644 index 000000000000..2dcdf7684bbf --- /dev/null +++ b/src/main/resources/view/TaskListPanel.fxml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import javafx.scene.control.ListView?> +<?import javafx.scene.layout.VBox?> + +<VBox xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"> + <ListView fx:id="taskListView" VBox.vgrow="ALWAYS" /> +</VBox> + diff --git a/src/main/resources/view/default.html b/src/main/resources/view/default.html index c49aa0f61682..50f09606f603 100644 --- a/src/main/resources/view/default.html +++ b/src/main/resources/view/default.html @@ -1,7 +1,6 @@ <!DOCTYPE html> <html> <head> - <link rel="stylesheet" href="DarkTheme.css"> </head> <body class="background"> diff --git a/src/main/resources/view/sampleTasklist.html b/src/main/resources/view/sampleTasklist.html new file mode 100644 index 000000000000..837b7ec249c5 --- /dev/null +++ b/src/main/resources/view/sampleTasklist.html @@ -0,0 +1,59 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <title>Task List + + + + + + + +

CS2103 LOs

+
+ + +
+
1. LO[W6.5][Submission]★★★
+
+
⚠  2018-02-22
+
⚑  Completed! ☑
+
✎  Can use JavaFX to build a simple GUI:
+

https://nus-cs2103-ay1718s2.github.io/website/schedule/week6/outcomes.html

+
+
+ + +
+
2. LO[W5.11][Compulsory][Submission]★
+
+
⚠  2018-02-15
+
⚑  Please work on it :) ☐
+
✎  Work with a 2KLoC code base:
+

https://nus-cs2103-ay1718s2.github.io/website/schedule/week5/outcomes.html

+
+
+
+
3. LO[W4.1]★★★
+
+
⚠  2018-02-08
+
⚑  Please work on it :) ☐
+
✎  Can explain models:
+

https://nus-cs2103-ay1718s2.github.io/website/schedule/week4/outcomes.html

+
+
+
+
4. LO[W3.10][Compulsory][Submission]★
+
+
⚠  2018-02-01
+
⚑  Please work on it :) ☐
+
✎  Work with a 1KLoC code base:
+

https://nus-cs2103-ay1718s2.github.io/website/schedule/week3/outcomes.html

+
+
+
+
+ + +

You have completed 1/4 !

+ diff --git a/src/main/resources/view/tasklist.html b/src/main/resources/view/tasklist.html new file mode 100644 index 000000000000..6b68f67e058c --- /dev/null +++ b/src/main/resources/view/tasklist.html @@ -0,0 +1,15 @@ + + +

My List

+
+
Dummy
+
Deadline: 31/03/2018
+
Status: needAction
+
PS: just a dummy list
+
Dummy2
+
Deadline: 01/04/2018
+
Status: needAction
+
PS: nuffin
+
+ + diff --git a/src/test/data/JsonUserPrefsStorageTest/ExtraValuesUserPref.json b/src/test/data/JsonUserPrefsStorageTest/ExtraValuesUserPref.json index 87e25c850ade..de63270cd407 100644 --- a/src/test/data/JsonUserPrefsStorageTest/ExtraValuesUserPref.json +++ b/src/test/data/JsonUserPrefsStorageTest/ExtraValuesUserPref.json @@ -9,6 +9,6 @@ "z" : 99 } }, - "addressBookFilePath" : "addressbook.xml", - "addressBookName" : "TypicalAddressBookName" + "progressCheckerFilePath" : "progresschecker.xml", + "progressCheckerName" : "TypicalProgressCheckerName" } diff --git a/src/test/data/JsonUserPrefsStorageTest/TypicalUserPref.json b/src/test/data/JsonUserPrefsStorageTest/TypicalUserPref.json index ea6836466914..418445c6486b 100644 --- a/src/test/data/JsonUserPrefsStorageTest/TypicalUserPref.json +++ b/src/test/data/JsonUserPrefsStorageTest/TypicalUserPref.json @@ -7,6 +7,6 @@ "y" : 100 } }, - "addressBookFilePath" : "addressbook.xml", - "addressBookName" : "TypicalAddressBookName" + "progressCheckerFilePath" : "progresschecker.xml", + "progressCheckerName" : "TypicalProgressCheckerName" } diff --git a/src/test/data/XmlAddressBookStorageTest/invalidAndValidPersonAddressBook.xml b/src/test/data/XmlAddressBookStorageTest/invalidAndValidPersonAddressBook.xml deleted file mode 100644 index 41e411568a5f..000000000000 --- a/src/test/data/XmlAddressBookStorageTest/invalidAndValidPersonAddressBook.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - Hans Muster - 9482424 - hans@example.com -
4th street
-
- - - Hans Muster - 948asdf2424 - hans@example.com -
4th street
-
-
diff --git a/src/test/data/XmlAddressBookStorageTest/invalidPersonAddressBook.xml b/src/test/data/XmlAddressBookStorageTest/invalidPersonAddressBook.xml deleted file mode 100644 index cfa128e72828..000000000000 --- a/src/test/data/XmlAddressBookStorageTest/invalidPersonAddressBook.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - Ha!ns Mu@ster - 9482424 - hans@example.com -
4th street
-
-
diff --git a/src/test/data/XmlAddressBookStorageTest/NotXmlFormatAddressBook.xml b/src/test/data/XmlProgressCheckerStorageTest/NotXmlFormatProgressChecker.xml similarity index 100% rename from src/test/data/XmlAddressBookStorageTest/NotXmlFormatAddressBook.xml rename to src/test/data/XmlProgressCheckerStorageTest/NotXmlFormatProgressChecker.xml diff --git a/src/test/data/XmlProgressCheckerStorageTest/invalidAndValidPersonProgressChecker.xml b/src/test/data/XmlProgressCheckerStorageTest/invalidAndValidPersonProgressChecker.xml new file mode 100644 index 000000000000..8782d09ccd97 --- /dev/null +++ b/src/test/data/XmlProgressCheckerStorageTest/invalidAndValidPersonProgressChecker.xml @@ -0,0 +1,21 @@ + + + + + Hans Muster + 9482424 + hans@gmail.com + HansGithub + Computer Science + 2 + + + + Hans Muster + 948asdf2424 + hans@gmail.com + HansGithub + Computer Science + 2 + + diff --git a/src/test/data/XmlProgressCheckerStorageTest/invalidPersonProgressChecker.xml b/src/test/data/XmlProgressCheckerStorageTest/invalidPersonProgressChecker.xml new file mode 100644 index 000000000000..887171a30498 --- /dev/null +++ b/src/test/data/XmlProgressCheckerStorageTest/invalidPersonProgressChecker.xml @@ -0,0 +1,12 @@ + + + + + Ha!ns Mu@ster + 9482424 + hans@gmail.com + HansGithub + Computer Science + 2 + + diff --git a/src/test/data/XmlSerializableAddressBookTest/invalidPersonAddressBook.xml b/src/test/data/XmlSerializableProgressCheckerTest/invalidPersonProgressChecker.xml similarity index 62% rename from src/test/data/XmlSerializableAddressBookTest/invalidPersonAddressBook.xml rename to src/test/data/XmlSerializableProgressCheckerTest/invalidPersonProgressChecker.xml index 13d5b1cb1c8a..4c30ef066c3c 100644 --- a/src/test/data/XmlSerializableAddressBookTest/invalidPersonAddressBook.xml +++ b/src/test/data/XmlSerializableProgressCheckerTest/invalidPersonProgressChecker.xml @@ -1,10 +1,12 @@ - + Hans Muster 9482424 hans@exam!32ple -
4th street
+ HansGithub + Computer Science + 2
-
+ diff --git a/src/test/data/XmlSerializableAddressBookTest/invalidTagAddressBook.xml b/src/test/data/XmlSerializableProgressCheckerTest/invalidTagProgressChecker.xml similarity index 74% rename from src/test/data/XmlSerializableAddressBookTest/invalidTagAddressBook.xml rename to src/test/data/XmlSerializableProgressCheckerTest/invalidTagProgressChecker.xml index 5fa697c22c4c..c1c890eb829d 100644 --- a/src/test/data/XmlSerializableAddressBookTest/invalidTagAddressBook.xml +++ b/src/test/data/XmlSerializableProgressCheckerTest/invalidTagProgressChecker.xml @@ -1,5 +1,5 @@ - + frie!nds - + diff --git a/src/test/data/XmlSerializableAddressBookTest/typicalPersonsAddressBook.xml b/src/test/data/XmlSerializableProgressCheckerTest/typicalPersonsProgressChecker.xml similarity index 55% rename from src/test/data/XmlSerializableAddressBookTest/typicalPersonsAddressBook.xml rename to src/test/data/XmlSerializableProgressCheckerTest/typicalPersonsProgressChecker.xml index c778cccc4c89..e28481e6ebf3 100644 --- a/src/test/data/XmlSerializableAddressBookTest/typicalPersonsAddressBook.xml +++ b/src/test/data/XmlSerializableProgressCheckerTest/typicalPersonsProgressChecker.xml @@ -1,19 +1,23 @@ - - + + Alice Pauline 85355255 alice@example.com -
123, Jurong West Ave 6, #08-111
+ AliceGithub + Computer Science + 2 friends
Benson Meier 98765432 johnd@example.com -
311, Clementi Ave 2, #02-25
+ BensonGithub + Computer Science + 2 owesMoney friends
@@ -21,32 +25,42 @@ Carl Kurz 95352563 heinz@example.com -
wall street
+ CarlGithub + Information Security + 2 Daniel Meier 87652533 cornelia@example.com -
10th street
+ DanielGithub + Computer Engineering + 2
Elle Meyer 9482224 werner@example.com -
michegan ave
+ ElleGithub + Computer Science + 2
Fiona Kunz 9482427 lydia@example.com -
little tokyo
+ FionaGithub + Computer Engineering + 2
George Best 9482442 anna@example.com -
4th street
+ GeorgeGithub + Computer Science + 2
friends owesMoney -
+ diff --git a/src/test/data/XmlUtilTest/invalidPersonField.xml b/src/test/data/XmlUtilTest/invalidPersonField.xml index ba49c971e884..cb0f171883ab 100644 --- a/src/test/data/XmlUtilTest/invalidPersonField.xml +++ b/src/test/data/XmlUtilTest/invalidPersonField.xml @@ -4,6 +4,8 @@ Hans Muster 9482asf424 hans@example -
4th street
+ HansGithub + Computer Science + 2 friends
diff --git a/src/test/data/XmlUtilTest/missingPersonField.xml b/src/test/data/XmlUtilTest/missingPersonField.xml index c0da5c86d080..27bb4ef4f4ee 100644 --- a/src/test/data/XmlUtilTest/missingPersonField.xml +++ b/src/test/data/XmlUtilTest/missingPersonField.xml @@ -3,6 +3,8 @@ 9482424 hans@example -
4th street
+ HansGithub + Computer Science + 2 friends
diff --git a/src/test/data/XmlUtilTest/tempAddressBook.xml b/src/test/data/XmlUtilTest/tempProgressChecker.xml similarity index 59% rename from src/test/data/XmlUtilTest/tempAddressBook.xml rename to src/test/data/XmlUtilTest/tempProgressChecker.xml index 41eeb8eb391a..c4a52d572f8a 100644 --- a/src/test/data/XmlUtilTest/tempAddressBook.xml +++ b/src/test/data/XmlUtilTest/tempProgressChecker.xml @@ -1,15 +1,14 @@ - + 1 John Doe - - - - + + + Friends - + diff --git a/src/test/data/XmlUtilTest/validPerson.xml b/src/test/data/XmlUtilTest/validPerson.xml index c029008d54f4..7c423d0efec8 100644 --- a/src/test/data/XmlUtilTest/validPerson.xml +++ b/src/test/data/XmlUtilTest/validPerson.xml @@ -3,6 +3,8 @@ Hans Muster 9482424 hans@example -
4th street
+ HansGithub + Computer Science + 2 friends diff --git a/src/test/data/XmlUtilTest/validAddressBook.xml b/src/test/data/XmlUtilTest/validProgressChecker.xml similarity index 51% rename from src/test/data/XmlUtilTest/validAddressBook.xml rename to src/test/data/XmlUtilTest/validProgressChecker.xml index 6265778674d3..88256fada1d8 100644 --- a/src/test/data/XmlUtilTest/validAddressBook.xml +++ b/src/test/data/XmlUtilTest/validProgressChecker.xml @@ -1,57 +1,75 @@ - + Hans Muster 9482424 hans@example.com -
4th street
+ HansGithub + Computer Science + 2
Ruth Mueller 87249245 ruth@example.com -
81th street
+ RuthGithub + Computer Science + 2
Heinz Kurz 95352563 heinz@example.com -
wall street
+ HeinzGithub + Computer Science + 2
Cornelia Meier 87652533 cornelia@example.com -
10th street
+ CorneliaGithub + Computer Science + 2
Werner Meyer 9482224 werner@example.com -
michegan ave
+ WernerGithub + Computer Engineering + 2
Lydia Kunz 9482427 lydia@example.com -
little tokyo
+ LydiaGithub + Information Security + 2
Anna Best 9482442 anna@example.com -
4th street
+ AnnaGithub + Computer Engineering + 2
Stefan Meier 8482424 stefan@example.com -
little india
+ StefanGithub + Computer Science + 2
Martin Mueller 8482131 hans@example.com -
chicago ave
+ MartinGithub + Computer Science + 2
-
+ diff --git a/src/test/java/guitests/guihandles/AlertDialogHandle.java b/src/test/java/guitests/guihandles/AlertDialogHandle.java index c69c77320c52..80a926b1de6d 100644 --- a/src/test/java/guitests/guihandles/AlertDialogHandle.java +++ b/src/test/java/guitests/guihandles/AlertDialogHandle.java @@ -2,7 +2,7 @@ import javafx.scene.control.DialogPane; import javafx.stage.Stage; -import seedu.address.ui.UiManager; +import seedu.progresschecker.ui.UiManager; /** * A handle for the {@code AlertDialog} of the UI. diff --git a/src/test/java/guitests/guihandles/Browser2PanelHandle.java b/src/test/java/guitests/guihandles/Browser2PanelHandle.java new file mode 100644 index 000000000000..1ffcc18cf22f --- /dev/null +++ b/src/test/java/guitests/guihandles/Browser2PanelHandle.java @@ -0,0 +1,72 @@ +package guitests.guihandles; + +import java.net.URL; + +import guitests.GuiRobot; +import javafx.concurrent.Worker; +import javafx.scene.Node; +import javafx.scene.web.WebEngine; +import javafx.scene.web.WebView; + +/** + * A handler for the {@code Browser2Panel} of the UI. + */ +public class Browser2PanelHandle extends NodeHandle { + + public static final String BROWSER_ID = "#browser2"; + + private boolean isWebViewLoaded = true; + + private URL lastRememberedUrl; + + public Browser2PanelHandle(Node browserPanelNode) { + super(browserPanelNode); + + WebView webView = getChildNode(BROWSER_ID); + WebEngine engine = webView.getEngine(); + new GuiRobot().interact(() -> engine.getLoadWorker().stateProperty().addListener((obs, oldState, newState) -> { + if (newState == Worker.State.RUNNING) { + isWebViewLoaded = false; + } else if (newState == Worker.State.SUCCEEDED) { + isWebViewLoaded = true; + } + })); + } + + /** + * Returns the {@code String title} of the currently loaded page. + */ + public String getLoadedTitle() { + return WebViewUtil.getLoadedTitle(getChildNode(BROWSER_ID)); + } + + /** + * Returns the {@code URL} of the currently loaded page. + */ + public URL getLoadedUrl() { + return WebViewUtil.getLoadedUrl(getChildNode(BROWSER_ID)); + } + + /** + * Remembers the {@code URL} of the currently loaded page. + */ + public void rememberUrl() { + lastRememberedUrl = getLoadedUrl(); + } + + /** + * Returns true if the current {@code URL} is different from the value remembered by the most recent + * {@code rememberUrl()} call. + */ + public boolean isUrlChanged() { + return !lastRememberedUrl.equals(getLoadedUrl()); + } + + /** + * Returns true if the browser is done loading a page, or if this browser has yet to load any page. + */ + public boolean isLoaded() { + return isWebViewLoaded; + } +} + diff --git a/src/test/java/guitests/guihandles/BrowserPanelHandle.java b/src/test/java/guitests/guihandles/BrowserPanelHandle.java index bd3633af78f3..53a969cf63da 100644 --- a/src/test/java/guitests/guihandles/BrowserPanelHandle.java +++ b/src/test/java/guitests/guihandles/BrowserPanelHandle.java @@ -33,6 +33,15 @@ public BrowserPanelHandle(Node browserPanelNode) { })); } + //@@author EdwardKSG + /** + * Returns the {@code String title} of the currently loaded page. + */ + public String getLoadedTitle() { + return WebViewUtil.getLoadedTitle(getChildNode(BROWSER_ID)); + } + //@@author + /** * Returns the {@code URL} of the currently loaded page. */ diff --git a/src/test/java/guitests/guihandles/CommandBoxHandle.java b/src/test/java/guitests/guihandles/CommandBoxHandle.java index 066c2b0b1d90..a293e5df1273 100644 --- a/src/test/java/guitests/guihandles/CommandBoxHandle.java +++ b/src/test/java/guitests/guihandles/CommandBoxHandle.java @@ -3,7 +3,7 @@ import javafx.collections.ObservableList; import javafx.scene.control.TextField; import javafx.scene.input.KeyCode; -import seedu.address.ui.CommandBox; +import seedu.progresschecker.ui.CommandBox; /** * A handle to the {@code CommandBox} in the GUI. @@ -37,6 +37,13 @@ public boolean run(String command) { return !getStyleClass().contains(CommandBox.ERROR_STYLE_CLASS); } + /** + * Sets text in the command box. + */ + public void setInput(String input) { + getRootNode().setText(input); + } + /** * Returns the list of style classes present in the command box. */ diff --git a/src/test/java/guitests/guihandles/MainWindowHandle.java b/src/test/java/guitests/guihandles/MainWindowHandle.java index 34e36054f4fd..0e1f122680a7 100644 --- a/src/test/java/guitests/guihandles/MainWindowHandle.java +++ b/src/test/java/guitests/guihandles/MainWindowHandle.java @@ -13,6 +13,7 @@ public class MainWindowHandle extends StageHandle { private final StatusBarFooterHandle statusBarFooter; private final MainMenuHandle mainMenu; private final BrowserPanelHandle browserPanel; + private final Browser2PanelHandle browser2Panel; public MainWindowHandle(Stage stage) { super(stage); @@ -23,6 +24,7 @@ public MainWindowHandle(Stage stage) { statusBarFooter = new StatusBarFooterHandle(getChildNode(StatusBarFooterHandle.STATUS_BAR_PLACEHOLDER)); mainMenu = new MainMenuHandle(getChildNode(MainMenuHandle.MENU_BAR_ID)); browserPanel = new BrowserPanelHandle(getChildNode(BrowserPanelHandle.BROWSER_ID)); + browser2Panel = new Browser2PanelHandle(getChildNode(Browser2PanelHandle.BROWSER_ID)); } public PersonListPanelHandle getPersonListPanel() { @@ -48,4 +50,8 @@ public MainMenuHandle getMainMenu() { public BrowserPanelHandle getBrowserPanel() { return browserPanel; } + + public Browser2PanelHandle getBrowser2Panel() { + return browser2Panel; + } } diff --git a/src/test/java/guitests/guihandles/PersonCardHandle.java b/src/test/java/guitests/guihandles/PersonCardHandle.java index d337d3a4cee9..ff2b073af61d 100644 --- a/src/test/java/guitests/guihandles/PersonCardHandle.java +++ b/src/test/java/guitests/guihandles/PersonCardHandle.java @@ -1,11 +1,7 @@ package guitests.guihandles; -import java.util.List; -import java.util.stream.Collectors; - import javafx.scene.Node; import javafx.scene.control.Label; -import javafx.scene.layout.Region; /** * Provides a handle to a person card in the person list panel. @@ -13,33 +9,15 @@ public class PersonCardHandle extends NodeHandle { private static final String ID_FIELD_ID = "#id"; private static final String NAME_FIELD_ID = "#name"; - private static final String ADDRESS_FIELD_ID = "#address"; - private static final String PHONE_FIELD_ID = "#phone"; - private static final String EMAIL_FIELD_ID = "#email"; - private static final String TAGS_FIELD_ID = "#tags"; private final Label idLabel; private final Label nameLabel; - private final Label addressLabel; - private final Label phoneLabel; - private final Label emailLabel; - private final List