diff --git a/.gitignore b/.gitignore index f69985ef1f..8bd43bf326 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,8 @@ bin/ /text-ui-test/ACTUAL.txt text-ui-test/EXPECTED-UNIX.TXT +Workout.txt +Schedule.txt +Food.txt +FoodBank.txt +Weight.txt diff --git a/build.gradle b/build.gradle index b0c5528fb5..4c456b4da1 100644 --- a/build.gradle +++ b/build.gradle @@ -42,5 +42,6 @@ checkstyle { } run{ + enableAssertions = true standardInput = System.in } diff --git a/diagrams/Fluid.puml b/diagrams/Fluid.puml new file mode 100644 index 0000000000..6fc915cdcd --- /dev/null +++ b/diagrams/Fluid.puml @@ -0,0 +1,34 @@ +@startuml +'https://plantuml.com/class-diagram +skinparam classAttributeIconSize 0 +hide circle + +abstract class Tracker { +} + +class Fluid { + #fluidArray: ArrayList + #fluidNumber: int + #description: String + #calories: int + #volume: int + #date: String + #time: String + #totalCalories: int + #totalVolume: int + + +generateFluidParameters(inputArguments: String): void + +addFluid(inputArguments: String): void + +deleteFluid(inputArguments: String): void + +listFluid(date: String): void + +getCalories(date: String): int + +getVolume(date: String): int +} + +class FluidExceptions { +} + +FluidExceptions --|> Fluid +Fluid --|> "<>" Tracker + +@enduml \ No newline at end of file diff --git a/diagrams/Fluid_AddFluid_Sequence.puml b/diagrams/Fluid_AddFluid_Sequence.puml new file mode 100644 index 0000000000..2326a7f0cc --- /dev/null +++ b/diagrams/Fluid_AddFluid_Sequence.puml @@ -0,0 +1,24 @@ +@startuml +'https://plantuml.com/sequence-diagram +||| +participant "fluid:Fluid" + +->"fluid:Fluid" : addFluid(inputArguments: String) +activate "fluid:Fluid" #FFBBBB + +"fluid:Fluid"->"fluid:Fluid" : generateFluidParameters(inputArguments: String) +activate "fluid:Fluid" #DarkSalmon +return +||| +alt invalid fluid description / missing parameters \n/ description has separators +else else +"fluid:Fluid"->"fluid:Fluid" : fluidArray.add(inputArguments: String) +activate "fluid:Fluid" #IndianRed +return +||| + end + note left : if block only throws an exception + ||| + deactivate "fluid:Fluid" + ||| +@enduml \ No newline at end of file diff --git a/diagrams/Fluid_DeleteFluid_Sequence.puml b/diagrams/Fluid_DeleteFluid_Sequence.puml new file mode 100644 index 0000000000..7b74f6df95 --- /dev/null +++ b/diagrams/Fluid_DeleteFluid_Sequence.puml @@ -0,0 +1,28 @@ +@startuml +'https://plantuml.com/sequence-diagram +||| +participant "fluid:Fluid" + +->"fluid:Fluid" : deleteFluid(inputArguments: String) +activate "fluid:Fluid" #FFBBBB + +"fluid:Fluid"->"fluid:Fluid" : generateFluidParameters(fluidArray.get(taskNumber: int): String) +activate "fluid:Fluid" #DarkSalmon +return +||| +"fluid:Fluid"->"parser:Parser" : parseStringToInteger(inputArguments: String) - 1; +activate "parser:Parser" #DarkSalmon +return taskNumber +||| +alt invalid fluid index / empty fluid list +else else +"fluid:Fluid"->"fluid:Fluid" : fluidArray.remove(taskNumber: int) +activate "fluid:Fluid" #IndianRed +return +||| + end + note left : if block only throws an exception + ||| + deactivate "fluid:Fluid" + ||| +@enduml \ No newline at end of file diff --git a/diagrams/Fluid_GetCalories.puml b/diagrams/Fluid_GetCalories.puml new file mode 100644 index 0000000000..b6b036e21b --- /dev/null +++ b/diagrams/Fluid_GetCalories.puml @@ -0,0 +1,22 @@ +@startuml +'https://plantuml.com/sequence-diagram + +participant "fluid:Fluid" +->"fluid:Fluid" : getCalories(date: String) +activate "fluid:Fluid" #FFBBBB +||| +opt for all elements in fluidArray +||| +alt fluidArray contains a fluid with date provided +"fluid:Fluid"->"fluid:Fluid" : generateFluidParameters(fluid: ArrayList) + activate "fluid:Fluid" #FFBBBB + ||| + return + ||| + end + end + ||| + return calorieTotal + deactivate "fluid:Fluid" + ||| +@enduml \ No newline at end of file diff --git a/diagrams/FoodBank_AddCustomMeal_sequence.puml b/diagrams/FoodBank_AddCustomMeal_sequence.puml new file mode 100644 index 0000000000..b6222b2de4 --- /dev/null +++ b/diagrams/FoodBank_AddCustomMeal_sequence.puml @@ -0,0 +1,31 @@ +@startuml +'https://plantuml.com/sequence-diagram +||| +participant ":FoodBank" + ->":FoodBank" : "addCustomMeal(inputArguments: String)" + activate ":FoodBank" #FFBBBB + opt inputArguments == null + ||| + end + note left : if block only throws an exception: EmptyLibraryDescription + ||| + ":FoodBank"->":FoodBank" : generateParameters(inputArguments: String) + ||| + activate ":FoodBank" #FFBBBB + return + ||| + opt Until no more meals + ||| + opt m.contains(description) + ||| + end + note left : if block only throws an exception: DuplicateFood + ||| + end + ||| + ":FoodBank"->":ClickfitMessages" : printAddedLibraryMeal(description: String,calories: int,totalMeals: int) + activate ":ClickfitMessages" #FFBBBB + return + deactivate ":FoodBank" + ||| +@enduml \ No newline at end of file diff --git a/diagrams/Meal.puml b/diagrams/Meal.puml new file mode 100644 index 0000000000..946ac87660 --- /dev/null +++ b/diagrams/Meal.puml @@ -0,0 +1,30 @@ +@startuml +hide circle +skinparam classAttributeIconSize 0 +'https://plantuml.com/class-diagram + +class Meal + +abstract class Tracker { +} + +class Meal { +protected ArrayList meals; + #mealNumber: int + #calories: int + #description: String + #date: String + #time: String + #totalCalories: int + - {static} logger: Logger + +generateWeightParameters(inputArguments: String): void + +addMeal(inputArguments: String): void + +deleteMeal(inputArguments: String): void + +listMeals(inputArguments: String): void + +getCalories(date: String): int +} + +Meal --|> "<>" Tracker + + +@enduml \ No newline at end of file diff --git a/diagrams/Meal_ListMeals_sequence.puml b/diagrams/Meal_ListMeals_sequence.puml new file mode 100644 index 0000000000..4809c34e87 --- /dev/null +++ b/diagrams/Meal_ListMeals_sequence.puml @@ -0,0 +1,26 @@ +@startuml +'https://plantuml.com/sequence-diagram +participant "meal:Meal" + ->"meal:Meal" : "listMeals(userDate: String)" + activate "meal:Meal" #FFBBBB + alt userDate.equals(Keywords.ALL) + ||| + "meal:Meal"->"meal:Meal" : listAllMeals() + activate "meal:Meal" #FFBBBB + return + ||| + else + ||| + ||| + "meal:Meal"->"meal:Meal" : listMealsByDate(userDate: String) + activate "meal:Meal" #FFBBBB + return + ||| + end + ||| + "meal:Meal"->":ClickfitMessages" : printMealListTotals(mealNumber: int, totalCalories: int) + activate ":ClickfitMessages" #FFBBBB + return + deactivate "meal:Meal" + ||| +@enduml \ No newline at end of file diff --git a/diagrams/ScheduleTracker_add_sequence.puml b/diagrams/ScheduleTracker_add_sequence.puml new file mode 100644 index 0000000000..91a745c880 --- /dev/null +++ b/diagrams/ScheduleTracker_add_sequence.puml @@ -0,0 +1,29 @@ +@startuml +'https://plantuml.com/sequence-diagram +participant ":ScheduleTracker" + +->":ScheduleTracker" : addScheduledWorkout(inputArguments: String) +activate ":ScheduleTracker" #FFBBBB + +":ScheduleTracker"->":ScheduleTracker" : generateParameters(inputArguments: String) +activate ":ScheduleTracker" #DarkSalmon +return + +create ":ScheduledWorkout" +":ScheduleTracker"->":ScheduledWorkout" : add ScheduledWorkout +activate ":ScheduledWorkout" +":ScheduledWorkout"-->":ScheduleTracker" +deactivate ":ScheduledWorkout" + +":ScheduleTracker"->":ScheduleTracker" : cleanUpScheduleList() + +activate ":ScheduleTracker" #DarkSalmon + +ref over ":ScheduleTracker", ":ScheduledWorkout" : updateOrDeleteScheduledWorkout + + + +return +return + +@enduml \ No newline at end of file diff --git a/diagrams/ScheduleTracker_add_sequence_ref.puml b/diagrams/ScheduleTracker_add_sequence_ref.puml new file mode 100644 index 0000000000..067025ac89 --- /dev/null +++ b/diagrams/ScheduleTracker_add_sequence_ref.puml @@ -0,0 +1,22 @@ +@startuml + +mainframe **sd** updateOrDeleteScheduledWorkout + +activate ":ScheduleTracker" #DarkSalmon +loop no more overdue workouts + note left : Take note that a workout is "overdue" \nif its scheduled date is before the current date + alt isRecurringWorkout + ":ScheduleTracker"->":ScheduledWorkout" : incrementWorkoutDate(days: long) + ||| + activate ":ScheduledWorkout" + return + ||| + else else + ":ScheduleTracker"->":ScheduledWorkout" : delete ScheduledWorkout + activate ":ScheduledWorkout" + destroy ":ScheduledWorkout" + ||| + end +end + +@enduml \ No newline at end of file diff --git a/diagrams/ScheduleTracker_class.puml b/diagrams/ScheduleTracker_class.puml new file mode 100644 index 0000000000..f158f0d883 --- /dev/null +++ b/diagrams/ScheduleTracker_class.puml @@ -0,0 +1,47 @@ +@startuml +<<<<<<< HEAD + +======= +hide circle +skinparam classAttributeIconSize 0 +>>>>>>> master +'https://plantuml.com/class-diagram +skinparam classAttributeIconSize 0 +hide circle + +class ScheduleTracker { ++ScheduleTracker() ++addScheduledWorkout(inputArguments: String):void ++deleteScheduledworkout(inputArguments: String): void ++listScheduledWorkouts(): void +} + +note bottom of ScheduledWorkout : Basic getter methods for attributes omitted +class ScheduledWorkout { +-workoutDescription: String +-workoutDate: String +-workoutTime: String +-workoutDateTime: LocalDateTime +-isRecurring: boolean + ++ScheduledWorkout(workoutDescription: String, \n\tworkoutDate: String, workoutTime: String, \n\tactivityMap: Map> \n\tisRecurring: boolean) ++incrementWorkoutDate(): void +} + +note top of WorkoutActivity : Basic getter methods for attribute omitted +class WorkoutActivity { +private String activityDescription; + -boolean isDistanceActivity = false; + -activityDistance: int + -activitySets: int + -activityReps: int + + +WorkoutActivity(activityDescription: String, + \tactivityQuantifier: ArrayList,\n\tisDistanceActivity: boolean) +} + +ScheduleTracker --> "[0..*] scheduledWorkouts" ScheduledWorkout +ScheduledWorkout --> "\t\t\n\n\n\n[0..*] activities" WorkoutActivity + + +@enduml \ No newline at end of file diff --git a/diagrams/Ui.puml b/diagrams/Ui.puml new file mode 100644 index 0000000000..b414c43897 --- /dev/null +++ b/diagrams/Ui.puml @@ -0,0 +1,30 @@ +@startuml +'https://plantuml.com/class-diagram +hide circle +skinparam classAttributeIconSize 0 + +class Ui { ++welcomeMessage():void ++getInfo():void ++memoryStartup():void ++sex:String ++weight:int ++height:int ++age:int ++activityLevel:int +} + +note bottom of Calculator : Prints out BMI of the user and his/her recommended caloric intake +class Calculator { ++getBmi():void ++getIdealCalories():void ++sex:String ++weight:int ++height:int ++age:int ++activityLevel:int +} + +Ui --> "1" Calculator + +@enduml \ No newline at end of file diff --git a/diagrams/WeightTracker_add_sequence.puml b/diagrams/WeightTracker_add_sequence.puml new file mode 100644 index 0000000000..fcaf1ad675 --- /dev/null +++ b/diagrams/WeightTracker_add_sequence.puml @@ -0,0 +1,32 @@ +@startuml +'https://plantuml.com/sequence-diagram + + +participant ":WeightTracker" +participant ":ClickfitMessages" + +->":WeightTracker" : weightTracker.addWeights(input: String) +activate ":WeightTracker" #Salmon +":WeightTracker"->":WeightTracker" : generateWeightParameters(inputArguments: String) +activate ":WeightTracker" #LightSalmon +return +alt typical input +":WeightTracker"->":ClickfitMessages" : printAddWeightResponse(weight: int, date: String) +activate ":WeightTracker" #LightSalmon +activate ":ClickfitMessages" #Tomato +return +deactivate ":ClickfitMessages" +return +else missing date +":WeightTracker"->":ClickfitMessages" : printAddWeightResponse(weight: int, date: String) +activate ":WeightTracker" #LightSalmon +activate ":ClickfitMessages"#Tomato +return +deactivate ":ClickfitMessages" +<--":WeightTracker" +deactivate ":WeightTracker" +else exception +<--":WeightTracker" : AddWeightException() +end + +@enduml \ No newline at end of file diff --git a/diagrams/WeightTracker_class.puml b/diagrams/WeightTracker_class.puml new file mode 100644 index 0000000000..4acc156b0b --- /dev/null +++ b/diagrams/WeightTracker_class.puml @@ -0,0 +1,55 @@ +@startuml +hide circle +skinparam classAttributeIconSize 0 +'https://plantuml.com/class-diagram + +abstract class Tracker { +} + +class WeightTracker { +#weightsArray: ArrayList +#numberOfWeights: int +#weight: int +#date: String +- {static} logger: Logger ++WeightTracker() ++generateWeightParameters(inputArguments: String): void ++addWeight(input: String): void ++deleteWeight(input: String): void ++printWeight(input: String): void ++listWeights(input: String): void ++listSpecificWeights(input: String): void ++listAllWeights(): void +} + +class AddWeightException { ++getMessage():String +} +class DeleteWeightException { ++getMessage():String +} +class DeleteWeightIndexException { ++getMessage():String +} +class NoWeightsException { ++getMessage():String +} +class WeightException { ++getMessage():String +} +class ClickfitMessages { ++printAddWeightResponse(weight: double, date: String):void ++printDeleteWeightResponse(weight: double, date: String):void +} +WeightTracker --> ClickfitMessages +WeightTracker ..> "<>" AddWeightException +WeightTracker ..> "<>" DeleteWeightException +WeightTracker ..> "<>" DeleteWeightIndexException +WeightTracker ..> "<>" NoWeightsException +WeightTracker --|> Tracker +AddWeightException --|> WeightException +DeleteWeightException --|> WeightException +DeleteWeightIndexException --|> WeightException +NoWeightsException --|> WeightException + +@enduml \ No newline at end of file diff --git a/diagrams/memorystartup.puml b/diagrams/memorystartup.puml new file mode 100644 index 0000000000..b56da25736 --- /dev/null +++ b/diagrams/memorystartup.puml @@ -0,0 +1,35 @@ +@startuml +'https://plantuml.com/sequence-diagram +hide circle +skinparam classAttributeIconSize 0 + +participant "Ui" + +->"Ui" :memoryStartup() +activate "Ui" #FFBBBB + +"Ui" -> "Scanner" : uiInput = uiScanner.nextLine(); +activate "Scanner" #DarkSalmon +return +||| +opt !flag +"Ui" -> "Scanner" : uiInput = uiScanner.nextLine(); +activate "Scanner" #DarkSalmon +return +||| +alt uiInput.isEmpty() +||| +else uiInput.trim().equals("y") +||| +else +||| +end +||| +note left: if block loads the data for the previous session else if block deletes the data for the previous session, else just prints out incorrect input +end +||| +return result +deactivate "Ui" +||| + +@enduml \ No newline at end of file diff --git a/diagrams/welcomeMessage.puml b/diagrams/welcomeMessage.puml new file mode 100644 index 0000000000..3af1376724 --- /dev/null +++ b/diagrams/welcomeMessage.puml @@ -0,0 +1,6 @@ +@startuml +'https://plantuml.com/sequence-diagram +participant "welcomeMesage" + + +@enduml \ No newline at end of file diff --git a/docs/AboutUs.md b/docs/AboutUs.md index 0f072953ea..b3d29fdadc 100644 --- a/docs/AboutUs.md +++ b/docs/AboutUs.md @@ -2,8 +2,8 @@ Display | Name | Github Profile | Portfolio --------|:----:|:--------------:|:---------: -![](https://via.placeholder.com/100.png?text=Photo) | John Doe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Don Joe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Ron John | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | John Roe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) -![](https://via.placeholder.com/100.png?text=Photo) | Don Roe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md) +![Imgur](https://i.imgur.com/3ubqQJI.jpg) | Edward Wang | [Github](https://github.com/EdwardZYWang) | [Portfolio](team/edwardzywang.md) +![](https://user-images.githubusercontent.com/69446495/135881811-957f79eb-0afe-48dd-94f0-d237edce256a.jpg) | Samal Sthitipragyan | [Github](https://github.com/pragyan01) | [Portfolio](https://www.linkedin.com/in/pragyan01/) +![](https://avatars0.githubusercontent.com/arvejw?s=100) | Teh Jiewen | [Github](https://github.com/arvejw) | [Portfolio](team/arvejw.md) +![](https://avatars0.githubusercontent.com/teoziyiivy?s=100) | Teo Ziyi Ivy | [Github](https://github.com/teoziyiivy) | [Portfolio](team/teoziyiivy.md) +![](https://user-images.githubusercontent.com/69350459/140319995-3b66c651-6398-4b62-941d-edb54840f741.jpg) | Vishal Jeyaram | [Github](https://github.com/VishalJeyaram) | [Portfolio](team/vishaljeyaram.md) diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 64e1f0ed2b..27dc7a339a 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -1,38 +1,938 @@ # Developer Guide +## Introduction +CLI.ckFit is a desktop-based fitness app which can be accessed easily via Command Line Interface (CLI). CLI.ckFit allows you to input and track +your calories, weight, foods, and workouts throughout the day. It also allows you to save your data and view it whenever +you wish to. It comes with a BMI and recommended caloric intake calculator which can give you an idea of your current fitness +level. + ## Acknowledgements +The UML Diagrams were generated with the help of: [PlantUML](https://plantuml.com/) + +Written with reference to: +* [https://se-education.org/addressbook-level2/DeveloperGuide.html](https://se-education.org/addressbook-level2/DeveloperGuide.html) +* [https://se-education.org/addressbook-level3/DeveloperGuide.html](https://se-education.org/addressbook-level3/DeveloperGuide.html) -{list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well} +## Table of Contents +- [**Design & Implementation**](#design--implementation) + - [**User Interface**](#user-interface-ui) + - [**Meal Tracker**](#mealtracker) + - [**Fluid Tracker**](#fluidtracker) + - [**Weight Tracker**](#weighttracker) + - [**Schedule Tracker**](#scheduletracker) +- [**Appendix A: Product Scope**](#appendix-a-product-scope) +- [**Appendix B: User Stories**](#appendix-b-user-stories) +- [**Appendix C: Non-Functional Requirements**](#appendix-c-non-functional-requirements) +- [**Appendix D: Glossary**](#appendix-d-glossary) +- [**Appendix E: Instructions for manual testing**](#appendix-e-instructions-for-manual-testing) ## Design & implementation -{Describe the design and implementation of the product. Use UML diagrams and short code snippets where applicable.} +### User Interface (Ui) +The proposed Ui class has the class level attributes of sex, weight, height, age and activityLevel. It consists of 3 +methods that in turn reference from different classes. -## Product scope -### Target user profile +It implements the following operations: + +* welcomeMessage() +* getInfo() +* memoryStartup() + +These operations will be illustrated through UML diagrams. + +#### Printing the welcome message + +The user launches the CLI for the first time. The welcomeMessage() is called first and prints out the messages imported from Clickfitmessages class. + +#### Getting BMI and recommended daily caloric intake +![Imgur](https://i.imgur.com/Pr4fVXf.png) + +The user is then greeted with a prompt that asks whether he or she wished to enter the calculator function of CLI.ckFit. +The calculator takes in the following inputs as shown in the UML diagram through the instantiating of a new calculator +object that takes in the class-level attributes of Ui to calculate the user's BMI in getBmi and the user's recommended +daily caloric intake through getIdealCalories(). + +#### Memory startup method +![Imgur](https://i.imgur.com/cRw6Bp3.png) + +The user is greeted with a prompt that asks whether he wishes to load his saved date. Pressing the "enter" keystroke will +load up the previous session's data. Typing in "y" will cause the previous session's data to be wiped, meaning the +contents of the storage text files will all be emptied, so the user can start afresh. + +#### Get summary of all info stored in text files + +The user is next greeted with a second prompt that asks whether he or she wishes to get a summary of all meals, fluids +and workouts he has eaten or completed. +memoryStartup() is used to return a Boolean value. If it returns ture, it enters an if-loop in Duke class which calls +the method printLoadedLists() in Storage class. In storage class, the text files(storage) of Meal, Fluid and Workout +classes are first converted +to arrayLists, which are converted to arrayLists, which are then referenced by PrintLoadedLists() to be formatted and +printed as a summary of all stored information iu the text files. + +### MealTracker + +#### Meal: Listing Meals +![](https://user-images.githubusercontent.com/69350459/138880611-c82f4574-037f-4b64-9631-90d914f71701.png) + +The UML sequence diagram above shows what happens when the user attempts to list their meals. If the user inputs +"list meals all", the if block's condition will be satisfied, and the method "listAllMeals()" will be called. If +the user inputs "list meals DATE" where date is an arbitrary date, the method "listMealsByDate(userDate)" will be +called, where userDate is the date specified by the user. Then, the static method +"printMealListTotals(mealNumber, totalCalories)", from the ClickfitMessages class will be called, +printing out a message to tell the user the total meal calories and number of meals. + +#### Class Diagram +![image](https://user-images.githubusercontent.com/69350459/140748412-948edbb6-313f-4658-a373-25051414d9ae.png) + +Above are the UML class level diagrams of `Meal`, and `Tracker`. As seen in +the diagram, the `Meal` class inherits from the `Tracker` class. This class diagram has been simplified for better readability. + +### FoodBank +#### FoodBank: Adding custom meal +![image](https://user-images.githubusercontent.com/69350459/140748085-b258f953-5d76-4ce4-9a4f-974d410ff22b.png) + +The UML sequence diagram above shows what happens when the user attempts to add a meal to their library. +If the user inputs "library addmeal", the first if block's condition will be satisfied, and the exception +"EmptyLibraryDescription()" will be thrown. Then the method "generateParameters(inputArguments)" is called, which sets +the class level attributes. Then, a for loop will be entered until the entire meal arraylist has been iterated through. +If the user has input a meal that already exists within the meal arraylist, the exception "DuplicateFood()" will be +thrown. Lastly, after exiting the for loop, the static method "printAddedLibraryMeal(description,calories,totalMeals)", +from the ClickfitMessages class will be called, printing out a message to tell the user that their meal has been added +to the library. + +### FluidTracker +#### Class diagram +![](https://user-images.githubusercontent.com/69461398/140756868-222576b6-87b1-4c10-9b7a-deac1e5c5643.png) + +Above are the UML class level diagrams of `Fluid`, `FluidExceptions` and `Tracker`. As seen in the diagram, the `Fluid` class is dependent on the `FluidExceptions` and the `Fluid`class inherits from the `Tracker` class. This class diagram has been simplified for better readability. + +#### Adding fluid sequence diagram +![](https://user-images.githubusercontent.com/69446495/140606905-45c62251-ae7e-43f5-a5aa-b37d7a12ec1a.png) + +The UML sequence diagram above shows what happens when the input command is recognised as `add fluid`. +`generateFluidParameters` method in the `Fluid class` is called upon which updates variables relevent to a fluid, such its `description`, `calories`, `volume`, `date` and `time`. An `if` block checks for possible errors in user input, which are caught by their respective exceptions. Otherwise, variables are then concatenated together as a string called `inputArguments` and added to the `fluidArray` list to be saved. + +#### Deleting fluid sequence diagram +![](https://user-images.githubusercontent.com/69446495/140607494-11daab2e-4c64-482c-80e9-4bf435ef554d.png) + +The UML sequence diagram above shows what happens when the input command is recognised as `delete fluid`. +`generateFluidParameters` method in the `Fluid class` is called upon which updates variables relevent to a fluid, such its `description`, `calories`, `volume`, `date` and `time`. `taskNumber`, which refers to the respective fluid's entry index is parsed from user input. An `if` block checks for possible errors in user input, which are caught by their respective exceptions. Otherwise, `fluidArray.remove(taskNumber)` is called, which deletes the relevant entry from the `fluidArray` list. + +#### Get total calories for specific date fluid sequence diagram +![](https://user-images.githubusercontent.com/69446495/140621687-7f221499-f29b-4003-a0f5-90d26ecf7f16.png) + +The UML sequence diagram above shows what happens when the input command is recognised as `list calories`. For all fluid entries stored in `fluidArray`, if the entries contain the date as provided, `generateFluidParameters` method in the `Fluid class` is called upon which updates variables relevent to a fluid, such its `description`, `calories`, `volume`, `date` and `time`. The `calorie` parameter for each fluid entry in the `fluidArray` is added up, which returns `calorieTotal` at the end of the method's lifeline. + + +### WeightTracker + +#### Class diagram +![image](https://user-images.githubusercontent.com/69350459/140748525-1ca74ba6-f530-4fd1-a6fe-c2952e184e78.png) + +Above are the UML class level diagrams of `WeightTracker`, `ClickfitMessages`, `Tracker` and relevant exception classes. +As seen in the diagram, the `WeightTracker` class is dependent on the `ClickfitMessages` class and inherits from the +`Tracker` class. The `WeightTracker` class also throws 4 exceptions which inherit from the `WeightException` class to +print the corresponding error message for each exception. +This class diagram has been simplified for better readability. + +#### Adding weight +![WeightTracker_add_sequence](https://user-images.githubusercontent.com/69446729/140641320-d243b7a8-a75c-4960-a731-6aeed02fd7ea.png) + +The UML sequence diagram above shows what happens when the input command is recognised as `add weight` which +calls `AddWeights(...)` method in the `WeightTracker` class. The `WeightTracker` class calls the +`generateWeightParameters` function which updates the `weight` and `date` variables. Then the `weight` and +`date` variables are added to weight array list and `printAddWeightResponse` is called from the `ClickfitMessages` +class for both the typical input and missing date cases. However, when an exception is encountered, the +`WeightTracker` class will throw `AddWeightException()` instead. + +### ScheduleTracker + +#### Class diagram + +![image](https://user-images.githubusercontent.com/69350459/140748620-e191dccb-d8ef-43fa-8b4f-48def5b0d84a.png) + +Above are the UML class level diagrams of `ScheduleTracker` and `ScheduledWorkout`. As seen in the diagram, one +`ScheduleTracker` object keeps track/is linked to **any** number of `ScheduledWorkout` objects, thus have a +multiplicity of `0..*`. This association forms through a private attribute `scheduledWorkouts` which is of type +`ArrayList`. + +Each `ScheduledWorkout` object also has a private attribute called `activities` which is +of type `ArrayList` where `activities` keeps track of **any** number of `WorkoutActivity` objects. +Do note that **not all** class attributes and methods are present in the diagram for +the sake of better comprehensibility. + +#### Adding scheduled workout +![ScheduleTracker_add_diag](https://user-images.githubusercontent.com/69461398/140618156-1622efcb-7c48-48a6-b611-788138ec955f.png) + +The UML sequence diagram above shows what happens when the method `addScheduledWorkout(...)` is called. +Parameters are generated and a `ScheduledWorkout` object is added into the `scheduledWorkouts` ArrayList. +There are a few method calls which are omitted from this diagram. The main focus is on `cleanUpScheduleList()` where +the rescheduling or deleting of overdue workouts take place. This process as represented by the ref frame +`updateOrDeleteScheduledWorkout`, will be elaborated on in the next section. -{Describe the target user profile} +#### Updating and deleting of overdue workouts +![ScheduleTracker_update_diag](https://user-images.githubusercontent.com/69461398/140618246-1fea0a8f-46b2-499a-a05f-cf85f38133d5.png) + + +If there are any overdue workouts, a `loop` continues until there are no more overdue workouts in the schedule list. +Essentially, depending on whether the overdue workout detected is recurring, the workout is deleted or rescheduled +appropriately. + +This process is done to ensure a correctly sorted and cleaned up list is always output to the user. +This also ensures the `scheduledWorkouts` ArrayList remains free of overdue workouts. + +## Appendix A: Product scope +### Target user profile +Specifically made for people getting into serious fitness routines such as athletes but helpful for general populace. ### Value proposition -{Describe the value proposition: what problem does it solve?} +CLI.ckFit is a desktop-based fitness app which can be accessed easily via CLI. CLI.ckFit allows you to input and track +your calories, weight, foods, and workouts throughout the day. It also allows you to save your data and view it whenever +you wish to. It also comes with a BMI and recommended caloric intake calculator which can give you an idea of +your current fitness level. All these features come together and complement each other but can also be used +independently of one another. For instance, CLI.ckFit can simply be used as a stand alone Weight tracker if the user +sees fit. -## User Stories +## Appendix B: User Stories |Version| As a ... | I want to ... | So that I can ...| |--------|----------|---------------|------------------| -|v1.0|new user|see usage instructions|refer to them when I forget how to use the application| -|v2.0|user|find a to-do item by name|locate a to-do without having to go through the entire list| +|v1.0|fitness enthusiast|record my fitness activities|plan my extensive workout schedule| +|v1.0|user|update how many calories I have burned through my workouts|keep track of my daily calories| +|v1.0|athlete|record my weight|keep track of and maintain a competitive weight| +|v1.0|forgetful user|input a "help" command|know the standard ways of inputting my commands| +|v2.0|user|have the app remember my user data|access my data anytime| +|v2.0|user|have scheduled workouts in the list to be sorted by the nearest dates|easily keep track of upcoming workouts| +|v2.0|frequent gym goer| be able to schedule recurring weekly workouts| have a routine schedule without having to reschedule the same workout every week| +|v2.0|serious athlete|breakdown my workout into smaller activities|track things like sets, reps and distance| +|v2.0|beginner|check my fluid summary| budget my remaining calories| +|v2.0|beginner|check my meal summary| budget my remaining calories| +|v2.0|beginner|check my workout summary| know whether i have burned enough calories| +|v2.0|beginner|check my weight summary| check my weight loss or gain progress| +|v2.0|health conscious individual|know my recommended caloric intake|know how much food to consume in one day| +|v2.0|health conscious individual| know my BMI indicator|know whether i need to lose weight| +|v2.0| lazy user| have date/time automatically input corresponding to system date/time if i leave it empty| save time and effort for other stuff after my meal or drinks| + +## Appendix C: Non-Functional Requirements + +1. Should work on any mainstream OS as long as it has Java 11 or above installed (Java has backward compatibility). +2. A user with above average typing speed in English text (i.e. not coding/system admin commands) should + be able to accomplish most of the tasks faster using commands than using the mouse. + +## Appendix D: Glossary + +* *recurring workouts* - workouts that are scheduled ***weekly*** +* *overdue workouts* - workouts whose dates are before the current date + +## Appendix E: Instructions for manual testing + +### Launching CLI.ckFit +1. Ensure that you have Java 11 or above installed. +2. Download the latest version of `CLI.ckFit` from [here](https://github.com/AY2122S1-CS2113T-F14-3/tp/releases/tag/v2.1). +3. Go to the folder you saved the `CLIckFit.jar` file and note the absolute file path. +4. If you are using Windows, open up a Command prompt terminal cmd.exe or powershell.exe and for + Mac and Linux users, do the same with the terminal of your respective systems. +5. Navigate to the folder where the `CLIckFit.jar` file is stored. +6. Execute `java -jar CLIckFit.jar` in the terminal, and the application will start running. + +**Expected**: CLI.ckFit will launch and a welcome message will be displayed. + +### Saving Data +Data is saved in "Food.txt", "FoodBank.txt", "Weight.txt", "Workout.txt" and "Schedule.txt" in the same folder +as your `CLIckFit.jar`. + * **Test Case**: + 1. Run `CLIckFit.jar`. + * **Note**: Ensure this is done in a new isolated folder or all data is already wiped prior. + 3. Add one valid meal, fluid, library, weight, workout and schedule entry each. + 4. Verify that there is a new entry in all the respective data files. + * **Note**: Closing and reopening the data files might be required to see the changes. + 5. Exit the application. + 6. Verify that data is still saved in their respective data files. + 7. Run `CLIckFit.jar` again and load the saved data. + 8. Verify that data is loaded correctly by listing the respective entries. + 9. Delete all entries added in *step 3*. + 9. Exit the application. + * **Expected**: "Food.txt", "FoodBank.txt", "Weight.txt", "Workout.txt" and "Schedule.txt" will be empty. + +### Command testing +Please check out CLI.ckFit's [User Guide](https://ay2122s1-cs2113t-f14-3.github.io/tp/UserGuide.html) for more detailed +walk through of all commands and their formats. + +Take note that the following sections will focus more on command behavior not specified in the User Guide, such as +expected output for incorrect or invalid inputs. The test cases stated in the following sections are not exhaustive and +testers are expected to do more exploratory work for more comprehensive testing. + +### BMI calculator and Recommended caloric intake calculator + +Description: It is a calculator that allows the user to check his current BMI and daily recommended caloric intake. +The user will be asked to enter his weight, height, age, activity level and gender. + +#### Gender: +Possible inputs: M , F + +Wrong inputs: negative integers and doubles, symbols, combination of integers and doubles with symbols +and strings, m, f + +Testcase 1 : m (small m or f are rejected) + +Testcase 2 : M (only M or F are accepted) + +Expected: + +`what is your SEX : M / F ?` + +`m` + +`what is your SEX : M / F ?` + +`M` + +*input accepted, proceeding to next question* + +#### Weight: +Possible inputs: 60, 60.5 (integers or doubles are accepted) + +Wrong inputs: negative integers and doubles, symbols, strings, combination of integers and doubles with symbols +and strings + +Testcase 1 : -34 (negative integers or doubles are not accepted) + +Testcase 2 : 60.5 (integers or doubles are accepted) + +Expected: + +`what is your weight in kg? Enter a valid number!` + +`-34` + +`what is your weight in kg? Enter a valid number!` + +`60.5` + +*input accepted, proceeding to next question* + +#### Height: +Possible inputs: 181, 181.5 (integers or doubles are accepted) + +Wrong inputs: negative integers and doubles, symbols, strings, combination of integers and doubles with symbols +and strings + +Testcase 1 : -34.7 (negative integers or doubles are not accepted) + +Testcase 2 : 132! (combination of integers or doubles and symbols are not accepted) + +Testcase 3 : 181.5 (integers or doubles are accepted) + +Expected: + +`what is your height in cm? Enter a valid number!` + +`-34.7` + +`what is your height in cm? Enter a valid number!` + +`132!` + +`what is your height in cm? Enter a valid number!` + +`181.5` + +*input accepted, proceeding to next question* + +#### Age: +Possible inputs: 23 (only integers are accepted) + +Wrong inputs: negative integers and doubles, positive doubles, symbols, strings, combination of integers and doubles with symbols +and strings + +Testcase 1 : 23.5 (doubles are not accepted) + +Testcase 2 : twenty three (strings are not accepted) + +Testcase 3 : 23 (only integers are accepted) + +Expected: + +`what is your age in years? Enter an integer!` + +`23.5` + +`Please enter a valid input!` + +`what is your age in years? Enter an integer!` + +`twenty three` + +`what is your age in years? Enter an integer!` + +`23` + +*input accepted, proceeding to next question* + +#### Activity level: +Possible inputs: 1, 2, 3, 4, 5 (integers 1 to 5) + +Wrong inputs: negative integers and doubles, positive doubles, symbols, strings, combination of integers and +doubles with symbols and strings + +Testcase 1 : 1.0 (doubles are not accepted even though it is within the range of 1 to 5) + +Testcase 2 : 6 (integers outside the range of 1 to 5 are not accepted) + +Testcase 3 : 4 (only integers within the range of 1 to 5 are accepted) + +Expected: + +`what is your activity level from a scale of 1 - 5? Enter an integer from 1 to 5!` + +`1.0` + +`Please enter a valid input!` + +`what is your activity level from a scale of 1 - 5? Enter an integer from 1 to 5!` + +`6` + +`what is your activity level from a scale of 1 - 5? Enter an integer from 1 to 5!` + +`4` + +*input accepted, proceeding to result* +#### Result +If all the same inputs are used and accepted, the user will be greeted with this printout: + +`Your BMI outcome is` + +`You are underweight` + +`Your ideal number of calories to maintain your weight is` + +`2847 kcal` + +`Would you like to clear all records of your fitness journey? +Key in "y" to clear your records, or press enter keystroke to load in data from your previous session(s) +Note: Keying in "y" will result in the previous session's data being deleted!` + +### Workout Commands + +#### Adding a workout +1. Adding a workout omitting date and time + * **Test Case**: `add workout test /c 123` + * **Expected**: workout with description "test" and "123" calories burned will be recorded with the current date and time. + +2. Adding a workout with specified date and time + * **Test Case**: `add workout test /c 123 /d 08/11/2021 /t 23:59` + * **Expected**: workout with description "test" and "123" calories burned will be recorded on "08/11/2021" at "23:59". + +3. Adding a workout with missing description + * **Test Case**: `add workout /c 123` + * **Expected**: + ``` + I am sorry... it appears the description is missing. + Please enter a workout description! + ``` + +4. Adding a workout with missing calorie separator "/c" + * **Test Case**: `add workout test 123` + * **Expected**: + ``` + CLI.ckFit is having difficulties finding the calorie separator /c + Please minimally have the format: add workout [workout_description] /c [calories] + Do remember to put spaces between your separators! + ``` + +5. Adding a workout with invalid date or time format + * **Test Case**: `add workout test /c 123 /d 08-11-21 /t 5:00pm` + * **Expected**: + ``` + Please enter your date and time in the right format. + It should be "DD/MM/YYYY" and "HH:MM" respectively. + ``` + +#### Deleting a workout +1. Deleting a workout with valid index + * **Test Case**: `delete workout 1` + * **Expected**: deletes the first workout in the workout list. + +2. Deleting a workout with invalid index + * **Test Case**: `delete workout -1` + * **Expected**: + ``` + Failed to delete that workout! Please enter an Integer within range. + ``` + +3. Deleting a workout without specifying index + * **Test Case**: `delete workout` + * **Expected**: + ``` + Please enter the workout index in the format: delete workout [index] + ``` + +#### Listing workouts +1. Listing workouts on date without any recorded workouts + * **Test Case**: `list workouts 23/12/2021` + * **Expected**: + ``` + No workouts recorded on the date: 23/12/2021 + ``` + +### Schedule Commands + +#### Adding a scheduled workout +1. Adding a scheduled workout with missing date or time separators, "/d" and "/t" + * **Test Case**: `add schedule 12/12/2021 23:59` + * **Expected**: + ``` + CLI.ckFit is having difficulties finding the separators... + Please enter in the format: add schedule [workout_description] /d [dd/mm/yyyy] /t [hh:mm] + Do remember to put spaces between your separators. + ``` + +2. Adding a scheduled workout with missing description + * **Test Case**: `add schedule /d 12/12/2021 /t 23:59` + * **Expected**: + ``` + I am sorry... it appears the description is missing. + Please enter a description for your scheduled workout! + ``` + +4. Adding a scheduled workout with invalid date or time format + * **Test Case**: `add schedule test /d 12-12-21 /t 11:59pm` + * **Expected**: + ``` + Please enter your date and time in the right format. + It should be "DD/MM/YYYY" and "HH:MM" respectively. + ``` + +5. Adding a scheduled workout with a date in the past + * **Test Case**: `add schedule test /d 07/11/2021 /t 13:59` + * **Expected**: + ``` + Noted! CLI.ckFit has scheduled your workout of description "test" on 07/11/2021 at 13:59. + CLI.ckFit has detected some overdue scheduled workouts and has deleted/rescheduled them! + ``` + +5. Adding a scheduled workout with activity breakdown, missing activity splitter ":" + * **Test Case**: `add schedule test /d 12/12/2021 /t 13:59 /a chest8x10, squats:3x10` + * **Expected**: + ``` + Missing activity splitter ":" detected. + Please enter [activity name]:[sets]x[reps] or [activity name]:[distance in metres] + for your workout activities + ``` + +6. Adding a scheduled workout with activity breakdown, unnecessary quantifier "x" + * **Test Case**: `add schedule test /d 12/12/2021 /t 13:59 /a swimming:1000x2` + * **Expected**: + ``` + Unnecessary activity quantifier splitter "x" detected. + Please enter [activity name]:[distance in metres] for distance based workout activities + if your activity name is either running/swimming/cycling. + E.g. running:8000 + ``` + +7. Adding a scheduled workout with activity breakdown, missing quantifier "x" + * **Test Case**: `add schedule test /d 07/11/2021 /t 13:59 /a chest:8x10, squats:3 10` + * **Expected**: + ``` + Missing activity quantifier "x" detected. + Please enter your [sets]x[reps] for your non-distance based workout activities. + ``` + +8. Adding a scheduled workout with activity breakdown with non-positive integers for distance or sets and reps + * **Test Case**: `add schedule test /d 07/11/2021 /t 13:59 /a chest:8x10, squats:3x-10` + * **Expected**: + ``` + There was an issue getting your activity breakdown. + Please enter a positive integer [distance in metres] for distance based + activities, namely swimming/running/cycling. + E.g. running:8000 + Enter two positive integers in the format [set]x[reps] for everything else. + E.g. bench press:3x12 + For multiple activities please separate them by "," + ``` + +#### Deleting a scheduled workout +1. Deleting a scheduled workout with valid index + * **Test Case**: `delete schedule 1` + * **Expected**: deletes the first workout in the workout list. + +2. Deleting a scheduled workout with invalid index + * **Test Case**: `delete schedule -1` + * **Expected**: + ``` + Failed to delete that scheduled workout! Please enter an Integer within range. + ``` + +3. Deleting a scheduled workout without specifying index + * **Test Case**: `delete schedule` + * **Expected**: + ``` + Please enter the schedule index in the format: delete schedule [index] + ``` + +#### Listing scheduled workouts +1. Listing workout schedule on date without any scheduled workouts + * **Test Case**: `list schedule 23/12/2021` + * **Expected**: + ``` + Workout schedule is empty on the date: 23/12/2021 + ``` + +### Meal commands + +#### Adding a meal + +1. Adding a meal omitting date and time + * **Test Case**: `add meal pasta /c 190` + * **Expected**: + ``` + Noted! CLI.ckFit has recorded your meal of pasta on 08/11/2021 and at 20:46. + 190 calories has been added to the calorie count! + ``` + +2. Adding a meal with specified date and time + * **Test Case**: `add meal pasta /c 190 /d 08/11/2021 /t 23:59` + * **Expected**: + ``` + Noted! CLI.ckFit has recorded your meal of pasta on 08/11/2021 and at 23:59. + 190 calories has been added to the calorie count! + ``` + +3. Adding a meal with missing description + * **Test Case**: `add meal /c 190` + * **Expected**: + ``` + Please enter a meal/fluid description, volume for fluids, and optionally, + calories if not previously added to the library! + e.g "add meal {DESCRIPTION} /c {CALORIES}" OR "add meal {DESCRIPTION}" + e.g "add fluid {DESCRIPTION} /c {CALORIES} /v {volume}" OR "add fluid {DESCRIPTION}" + ``` + +4. Adding a meal with missing calories + * **Test Case**: `add meal pasta /c` + * **Expected**: + ``` + Please enter a meal/fluid description, volume for fluids, and optionally, + calories if not previously added to the library! + e.g "add meal {DESCRIPTION} /c {CALORIES}" OR "add meal {DESCRIPTION}" + e.g "add fluid {DESCRIPTION} /c {CALORIES} /v {volume}" OR "add fluid {DESCRIPTION}" + ``` + +5. Adding a meal with invalid date or time format + * **Test Case**: `add meal pasta /c 123 /d 08-11-21 /t 5:00pm` + * **Expected**: + ``` + Please enter your date and time in the right format. + It should be "DD/MM/YYYY" and "HH:MM" respectively. + ``` + +6. Adding a meal with negative calories + * **Test Case**: `add meal pasta /c -123` + * **Expected**: + ``` + Please input a positive integer value for your calories! + ``` + +#### Deleting a meal +1. Deleting a meal with valid index + * **Test Case**: `delete meal 1` + * **Expected**: + ``` + pasta will be removed from your list of meals consumed! + ``` + +2. Deleting a meal with invalid index + * **Test Case**: `delete meal -1` + * **Expected**: + ``` + Please enter a proper meal index. Use "list meals all" to view each meal's index + ``` + +3. Deleting a meal without specifying index + * **Test Case**: `delete meal` + * **Expected**: + ``` + Please enter the index of the meal you wish to delete! + ``` + +### Fluid commands + +#### Adding a fluid + +1. Adding a fluid omitting date and time + * **Test Case**: `add fluid cola /c 100 /v 100` + * **Expected**: + ``` + Noted! CLI.ckFit has recorded your drink of cola of 100 calories and 100 ml + on 08/11/2021 19:47. + ``` + +2. Adding a fluid with specified date and time + * **Test Case**: `add fluid cola /c 190 /v 100 /d 01/02/2021 /t 23:59` + * **Expected**: + ``` + Noted! CLI.ckFit has recorded your drink of cola of 190 calories and 100 ml + on 01/02/2021 23:59. + ``` + +3. Adding a fluid with missing description + * **Test Case**: `add fluid /c 190` + * **Expected**: + ``` + Please enter a meal/fluid description, volume for fluids, and optionally, + calories if not previously added to the library! + e.g "add meal {DESCRIPTION} /c {CALORIES}" OR "add meal {DESCRIPTION}" + e.g "add fluid {DESCRIPTION} /c {CALORIES} /v {volume}" OR "add fluid {DESCRIPTION}" + ``` + +4. Adding a fluid with missing calories + * **Test Case**: `add fluid water /c` + * **Expected**: + ``` + Please enter a meal/fluid description, volume for fluids, and optionally, + calories if not previously added to the library! + e.g "add meal {DESCRIPTION} /c {CALORIES}" OR "add meal {DESCRIPTION}" + e.g "add fluid {DESCRIPTION} /c {CALORIES} /v {volume}" OR "add fluid {DESCRIPTION}" + ``` + +5. Adding a fluid with invalid date or time format + * **Test Case**: `add fluid cola /c 123 /d 08-11-21 /t 5:00pm` + * **Expected**: + ``` + Please enter your date and time in the right format. + It should be "DD/MM/YYYY" and "HH:MM" respectively. + ``` + +6. Adding a fluid with negative calories + * **Test Case**: `add fluid pasta /c -123` + * **Expected**: + ``` + Please input a positive integer value for your calories! + ``` + +#### Deleting a fluid +1. Deleting a fluid with valid index + * **Test Case**: `delete fluid 1` + * **Expected**: + ``` + Noted! CLI.ckFit has deleted your drink of cola of 60 calories and 80 ml + on 08/11/2021 20:24. + ``` + +2. Deleting a fluid with invalid index + * **Test Case**: `delete fluid -1` + * **Expected**: + ``` + This fluid entry does not exist. + You may try again or wish to add a fluid entry first. + ``` + +3. Deleting a fluid without specifying index + * **Test Case**: `delete fluid` + * **Expected**: + ``` + Please enter a valid fluid index. + You may wish to list to check the index numbers. + ``` + +### FoodBank commands + +#### Adding a custom meal + +1. Adding a custom meal + * **Test Case**: `library addmeal pasta /c 190` + * **Expected**: + ``` + pasta, which has 190 calories, will be added to your library of meals. + You now have 1 meals! + ``` + +2. Adding a custom meal without its calories + * **Test Case**: `library addmeal pasta` + * **Expected**: + ``` + Please enter a food description, and its associated calories + e.g "library addmeal {DESCRIPTION} /c {CALORIES}" + e.g "library addfluid {DESCRIPTION} /c {CALORIES}" + ``` + +3. Adding a custom meal with missing description + * **Test Case**: `library addmeal /c 190` + * **Expected**: + ``` + Please enter a food description, and its associated calories + e.g "library addmeal {DESCRIPTION} /c {CALORIES}" + e.g "library addfluid {DESCRIPTION} /c {CALORIES}" + ``` + +6. Adding a meal with negative calories + * **Test Case**: `library addmeal pasta /c -123` + * **Expected**: + ``` + Please input a positive integer value for your calories! + ``` + +#### Deleting a custom meal + +1. Deleting a meal with valid index + * **Test Case**: `library deletemeal 1` + * **Expected**: + ``` + pasta will be removed from your meal library. + You now have 0 meals left! + ``` + +2. Deleting a meal with invalid index + * **Test Case**: `library deletemeal -1` + * **Expected**: + ``` + Please enter a valid meal index! + Use "library listmeals" to see the meal indexes + ``` + +3. Deleting a meal without specifying index + * **Test Case**: `library deletemeal` + * **Expected**: + ``` + Please enter a food index! + ``` + +#### Adding a custom fluid + +1. Adding a custom fluid + * **Test Case**: `library addfluid water /c 190` + * **Expected**: + ``` + water, which has 190 calories, will be added to your library of fluids. + You now have 1 fluids! + ``` + +2. Adding a custom meal without its calories + * **Test Case**: `library addfluid cola` + * **Expected**: + ``` + Please enter a food description, and its associated calories + e.g "library addmeal {DESCRIPTION} /c {CALORIES}" + e.g "library addfluid {DESCRIPTION} /c {CALORIES}" + ``` + +3. Adding a custom fluid with missing description + * **Test Case**: `library addfluid /c 190` + * **Expected**: + ``` + Please enter a food description, and its associated calories + e.g "library addmeal {DESCRIPTION} /c {CALORIES}" + e.g "library addfluid {DESCRIPTION} /c {CALORIES}" + ``` + +6. Adding a fluid with negative calories + * **Test Case**: `library addfluid juice /c -123` + * **Expected**: + ``` + Please input a positive integer value for your calories! + ``` + +#### Deleting a custom fluid + +1. Deleting a fluid with valid index + * **Test Case**: `library deletefluid 1` + * **Expected**: + ``` + water will be removed from your list of fluids consumed. + You now have 0 fluids left! + ``` + +2. Deleting a fluid with invalid index + * **Test Case**: `library deletemeal -1` + * **Expected**: + ``` + Please enter a valid fluid index! + Use "library listfluids" to see the fluid indexes + ``` + +3. Deleting a fluid without specifying index + * **Test Case**: `library deletefluid` + * **Expected**: + ``` + Please enter a food index! + ``` + +#### Listing calories -## Non-Functional Requirements +1. Listing meals on a specific date + * **Test Case**: `list calories` + * **Expected**: + ``` + Your total calorie consumption for 08/11/2021 is: 60 calories. + Your total calories burned for 08/11/2021 is: 0 calories. + Your NET calories for 08/11/2021 is: 60 calories. + ``` -{Give non-functional requirements} +#### Listing volumes -## Glossary +1. Listing meals on a specific date + * **Test Case**: `list volumes` + * **Expected**: + ``` + Your total volume consumption for 08/11/2021 is: 100 ml. + ``` + +### Weight Commands -* *glossary item* - Definition +#### Adding a weight +1. Adding a weight for current date + * **Test Case**: `add weight 50` + * **Expected**: `Noted! CLI.ckFit has recorded your weight as 50.0 kg on 08/11/2021. Check back for your progress!` +2. Adding a weight for specific date + * **Test Case**: `add weight 50 08/11/2021` + * **Expected**: `Noted! CLI.ckFit has recorded your weight as 50.0 kg on 08/11/2021. Check back for your progress!` +3. Adding a weight for unaccepted weight +(only numbers to 1 decimal point from 0 <= weight <= 1000 allowed) + * **Test Case**: `add weight 99999` + * **Expected**: + ``` + Exceeded maximum weight + An unknown error has occurred in Weight Tracker + ``` +4. Adding a weight with omission of weight + * **Test Case**: `add weight` + * **Expected**: `CLI.ckFit has detected a wrong input, kindly check your inputs or type "help commands" for input examples.` -## Instructions for manual testing +#### Deleting a weight +1. Deleting a weight with valid index + * **Test Case**: `delete weight 1` + * **Expected**: `Noted! CLI.ckFit has successfully deleted your weight of 50.0 kg on 08/11/2021.` +2. Deleting a weight with invalid index + * **Test Case**: `delete weight -1` + * **Expected**: + ``` + CLI.ckFit encountered a problem deleting your weight. + Please ensure the index is within the list. + ``` +3. Deleting a weight with omission of index + * **Test Case**: `delete weight` + * **Expected**: + ``` + CLI.ckFit encountered a problem deleting your weight. + Please follow the format: delete weight INDEX + ``` + +#### Listing weights +1. Listing weights for current date + * **Test Case**: `list weights` + * **Expected**: + ``` + 1. Weight: 50.0 kg + Total number of weights: 1 + ``` +2. Listing weights for specific date + * **Test Case**: `list weights 08/11/2021` + * **Expected**: + ``` + 1. Weight: 50.0 kg + Total number of weights: 1 + ``` -{Give instructions on how to do a manual product testing e.g., how to load sample data to be used for testing} diff --git a/docs/README.md b/docs/README.md index bbcc99c1e7..3cd0d5158e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,10 @@ -# Duke +# CLI.ckFit -{Give product intro here} +CLI.ckFit is a desktop-based fitness app which can be accessed easily via Command Line Interface (CLI). CLI.ckFit +allows you to input and track your calories, weight, foods, and workouts throughout the day. +It also allows you to save your data and view it whenever you wish to. +It comes with a BMI and recommended caloric intake calculator which can give you an idea of your current fitness +level. Useful links: * [User Guide](UserGuide.md) diff --git a/docs/UserGuide.md b/docs/UserGuide.md index abd9fbe891..cf9eefb453 100644 --- a/docs/UserGuide.md +++ b/docs/UserGuide.md @@ -1,42 +1,1238 @@ -# User Guide +# CLI.ckFit User Guide ## Introduction -{Give a product intro} +CLI.ckFit is a desktop-based fitness app which can be accessed easily via Command Line Interface (CLI). CLI.ckFit +allows you to input and track your calories, weight, foods, and workouts throughout the day. +It also allows you to save your data and view it whenever you wish to. +It comes with a BMI and recommended caloric intake calculator which can give you an idea of your current fitness +level. You can also schedule a variety of workouts such as running, cycling, gym etc. It is suitable for students +staying on campus, especially existing people already involved in some level of exercise, such as casual gym goers or +even student athletes. Prior knowledge in fitness and gym-related terminologies is beneficial, but not necessary. + +[**>>Skip to Table of Contents<<**](#table-of-contents) + +## Motivations + +University students staying on-campus have always found it difficult to juggle their hall activities, academic workload, +and social activities. This makes it difficult for them to track their health & fitness. Furthermore, most students +don’t want to budget for a fitness app which may be inconvenient to access. Lastly, one's foray into serious fitness may +be a daunting and a confusing process. The abundance of apps in the market that each provide different services can +exacerbate this issue. ## Quick Start -{Give steps to get started quickly} +1. The BMI calculator and recommended caloric intake calculator gives you an idea of where your current fitness +level stands. You can also choose to skip using the calculators by entering the appropriate keystrokes. + * *NOTE* : Please follow _**exactly**_ the explicitly _**required**_ input formats for answering the calculator + questions. Any deviating inputs will result in the question being repeated so that you are able to know exactly what inputs and formats are needed by the + calculator to calculate BMI and recommended daily caloric intake. + * Please only enter one input for each question. + E. g. When prompted for your weight, you should only type in "50" and not "50 6000". + +2. You can then access the calorie manager to track your caloric intake, while also using the workout schedule manager + to track the calories burned. +3. Workout schedule manager also allows you to schedule future workouts. +4. For the long term outlook, weight tracker lets you monitor your weight over the length of usage of CLI.ckFit as an + indicator of your progress. +5. In order to load the data of your previous session, you *MUST* press "enter" when prompted with the following + question: + ``` + Would you like to clear all records of your fitness journey? + Key in "y" to clear your records, or press enter keystroke to load in data from + your previous session(s) + Note: Keying in "y" will result in the previous session's data being deleted! + ``` + +## Technical Start Up 1. Ensure that you have Java 11 or above installed. -1. Down the latest version of `Duke` from [here](http://link.to/duke). -## Features +2. Download the latest version of `CLI.ckFit` from [here](https://github.com/AY2122S1-CS2113T-F14-3/tp/releases/tag/v2.1). + +3. Go to the folder you saved the `CLIckFit.jar` file and note the absolute file path. + +4. If you are using Windows, open up a Command prompt terminal cmd.exe or powershell.exe and for + Mac and Linux users, do the same with the terminal of your respective systems. + +5. Navigate to the folder where the `CLIckFit.jar` file is stored. + +6. Execute `java -jar CLIckFit.jar` in the terminal, and the application will start running. + +## Common Terminologies and Definitions + +* Calories are in kcal + +* Weight is in kg + +* Height is in cm + +* Volume is in ml + +* Date is in DD/MM/YYYY (e.g. 26/10/2021) + +* Time is in HH:MM (e.g. 23:59) + +### What is a rep? + +A rep is short for repetitions. Repetitions define the number of times +to perform an exercise. For example if you do 12 squats, then stop, the +12 squats you perform are considered 12 repetitions. + +### What is a set? + +Sets refer to how many times you will repeat that exercise for the set +number of repetitions. For example, you do 12 squats and rest. Then +you do another 12 squats, rest, and then another 12. You have now +completed three sets of 12 reps. + +## Known limitations + +* CLI.ckFit may not handle illogical inputs correctly due to limitations of data types. For instance, if you enter an +overly large and nonsensical integer value for calories such as `2147483647` there may be overflow during computation. + E.g., when calculating total calories, summation may result in an overflow, producing negative calories. + +* Separators such as the date separator `/d`, time separator `/t`, calorie separator `/c`, volume separator `/v` and + activity separator `/a` should be entered in the **same order** as shown in their respective command formats. + CLI.ckFit does not actively support the shuffling of separators when taking user input. + +* The separators as shown in the command format should be input **once**. Typing multiple identical separators + unnecessarily *may* cause incorrect parsing of user input. + +* To ensure correct processing of user inputs you should only enter the **necessary** number of arguments. + For instance if you want to add a meal of `300` calories, only enter a **single** integer for your calories. + E.g., enter `/c 300` instead of something like `/c 300 20 10`. The same applies to other arguments like date and time. + +* Scheduled workouts with the same activity breakdowns in a different order are not considered duplicated + in the current version of CLI.ckFit. If multiple activities with the same name are input in the same activity + breakdown, only the activity quantifier of the latest activity will be taken. This is due to limitations based + on the choice usage of HashMaps in implementation. + +* The current version CLI.ckFit only supports 3 types of distance based activities, + namely swimming, running and cycling. **ALL** other activities are **assumed** to be sets/repetitions based. + (*Read more about Sets and Reps under [**Common Terminologies**](#common-terminologies-and-definitions)*) + +## Table of Contents + +### Calorie Manager + +- #### Add + + - [**Add meal**](#adding-a-meal) + + - [**Add fluid**](#adding-fluids) + + - [**Add weight**](#adding-weight) + +- #### Delete + + - [**Remove meal**](#delete-a-meal) + + - [**Remove fluid**](#delete-a-fluid) + + - [**Remove weight**](#delete-a-weight) + +- #### List + + - [**List meals**](#list-meals) + + - [**List fluids**](#list-fluids) + + - [**List weights**](#list-weights) + + - [**List calories**](#list-calories) + + - [**List volumes**](#list-volumes) + +### Workout Manager + +- #### Add + + - [**Add workout**](#adding-workout) + + - [**Add scheduled workout**](#adding-scheduled-workout) + +- #### Delete + + - [**Remove workout**](#delete-a-workout) + + - [**Remove scheduled workout**](#delete-a-scheduled-workout) + +- #### List + + - [**List workouts**](#list-workouts) + + - [**List scheduled workouts**](#list-scheduled-workouts) + +### Library Manager + +- #### Add + + - [**Add meal to library**](#adding-meal-to-library) + + - [**Add fluid to library**](#adding-fluid-to-library) + +- #### Delete + + - [**Remove meal from library**](#delete-a-meal-from-library) + + - [**Remove fluid from library**](#delete-a-fluid-from-library) + +- #### List + + - [**List meals from library**](#list-meals-stored-in-library) + + - [**List fluids from library**](#list-fluids-stored-in-library) + +### List Manager + +- [**List everything on current date**](#list-everything-on-current-date) +- [**List everything**](#list-everything-on-all-dates) + +### Getting help +- [**Access user help**](#help-command) + +### Exiting CLI.ckFit +- [**Exiting application**](#exiting-the-application) + +### Miscellaneous + +- [**FAQ**](#faq) + +- [**Command Summary**](#command-summary) + +# *Features:* + +## Adding a meal + +Command Word: `add meal` + +Description: Adds a new meal to the list of meals, with its associated calories, date and time of consumption. + +Format: `add meal MEAL NAME ` + +* The `MEAL_NAME` can contain spaces. + +* Prefixes cannot be swapped and must follow the order shown above. No duplicates allowed. + +* Each prefix only accepts one input after it. + +* The `MEAL_CALORIES` can only contain positive integers inclusive of 0. + +* You can only omit putting `MEAL_CALORIES` if you have saved the meal in your [meal library](#adding-meal-to-library) already. + +* The `DATE` is in dd/mm/yyyy. + +* The `TIME` is in hh:mm. + +* If `DATE` or `TIME` is not specified, the system current date and time will be taken. + +* Meals can be added to a future date if desired + +Example of usage: + +`add meal risotto /c 250 /d 14/10/2021 /t 08:30` + +Expected outcome: + +``` +Noted! CLI.ckFit has recorded your meal of risotto on 14/10/2021 and at 08:30. +250 calories has been added to the calorie count! +``` + +## Adding fluids + +Command Word: `add fluid` + +Description: Adds a new fluid to the list of fluid items, with its associated calories, date and time of consumption. + + +Format: `add fluid FLUID_NAME ` + + +* The `FLUID_NAME` can contain spaces. + +* Prefixes cannot be swapped and must follow the order shown above. No duplicates allowed. + +* Each prefix only accepts one input after it. + +* The `FLUID_CALORIES` can only contain positive integers inclusive of 0. + +* You can only omit putting `FLUID_CALORIES` if you have saved the drink in your [fluid library](#adding-fluid-to-library) already. + +* The `VOLUME` can only contain positive integers inclusive of 0. + +* The `DATE` is in dd/mm/yyyy. + +* The `TIME` is in hh:mm. + +* Fluids can be added to a future date if desired + +Example of usage: + +`add fluid milk /c 180 /v 100 /d 08/09/2021 /t 07:40` + +Expected outcome: + +``` +Noted! CLI.ckFit has recorded your drink of milk of 180 calories and 100 ml on 08/09/2021 07:40. +``` + +## Adding weight + +Command Word: `add weight` + +Description: Adds a new weight to the list of weight items, with its associated date. + +Format: `add weight WEIGHT ` + +* The `WEIGHT` cannot contain spaces and must be a non-negative number to maximum of 1 decimal place. + +* Prefixes cannot be swapped and must follow the order shown above. No duplicates allowed. + +* Each prefix only accepts one input after it. + +* The `WEIGHT` has to be realistic (<1000.0 kg) due to limitation of integer overflow. + +* The `DATE` is in dd/mm/yyyy. + +* If `DATE` is not specified, the current system date will be taken. + +* Only 1 `WEIGHT` should be included in your input. + +* If multiple `/d DATE` or `DATE` is written only the first input will be taken. + +Example of usage: + +`add weight 50 /d 03/04/2021` + +Expected outcome: + +``` +Noted! CLI.ckFit has recorded your weight as 50.0 kg on 03/04/2021. Check back for your progress! +``` + +## Adding workout + +Command Word: `add workout` + +Description: Adds a new workout to the list of workout items, with its associated calories, date and time of workout. + +Format: `add workout WORKOUT_NAME /c CALORIES_BURNED ` + +* The `WORKOUT_NAME` can contain spaces. + +* Prefixes cannot be swapped and must follow the order shown above. No duplicates allowed. + +* Each prefix only accepts one input after it. + +* The `CALORIES_BURNT` can only contain positive integers inclusive of 0. + +* The `DATE` is in dd/mm/yyyy. + +* The `TIME` is in hh:mm. + +* If `DATE` or `TIME` is not specified, the system current date and time will be taken. + +* Workouts can be added to a future date if desired + +Example of usage: + +* `add workout jog /c 250 /d 07/08/2021 /t 15:00` records a *workout* with the + + description `jog` and `250` calories burned on `07/08/2021` at `15:00`. + +* `add workout jog /c 250` records a *workout* with the + + description `jog` and `250` calories burned on **today's date and current time**. + +Expected outcome: + +``` +Noted! CLI.ckFit has recorded your workout of description "jog" on 07/08/2021 +at 15:00 where you burned 250 calories! +``` + +## Adding scheduled workout + + +Command Word: `add schedule` + +Description: Adds a new scheduled workout to the list of scheduled workout items with the option to include + +activity breakdowns, with date and time of workout. + +### With no activity breakdown: + +Format: `add schedule WORKOUT_NAME /d DATE /t TIME ` + +* The `WORKOUT_NAME` can contain spaces. + +* Prefixes cannot be swapped and must follow the order shown above. No duplicates allowed. + +* Each prefix only accepts one input after it. + +* The `DATE` is in dd/mm/yyyy. + +* The `TIME` is in hh:mm. + +* The `DATE` or `TIME` is compulsory for schedules. + +* The `/r` flag at the end is an ***optional*** flag for recurrence, which schedules a weekly *recurring* workout. + +* The ***optional*** `/r` flag, **if** included, **must** be at the end of the command. + +Example of usage: + +* `add schedule chest day /d 22/12/2021 /t 15:00` adds a *non-recurring workout* to your schedule with the + description `chest day` on `22/12/2021` at `15:00`. + +* `add schedule weekly chest day /d 07/12/2021 /t 13:50 /r` adds a *recurring* workout to your schedule with the + description `weekly chest day` on `07/12/2021` at `13:50`. + +Expected outcomes: +``` +Noted! CLI.ckFit has scheduled your workout of description "chest day" on 22/12/2021 at 15:00. +``` + +``` +Noted! CLI.ckFit has scheduled your recurring workout of description "weekly chest day" +on 07/12/2021 at 13:50. +``` + +### With activity breakdown + +Format: `add schedule WORKOUT_NAME /d DATE /t TIME ` + +* The `/a` separator is optional. However, it **must** be included if an activity breakdown is to be included in the schedule. + +* The `ACTIVITY_NAME` can contain spaces and `:` ***must*** follow after it. + +* Prefixes cannot be swapped and must follow the order shown above. No duplicates allowed. + +* Each prefix only accepts one input after it. + +* CLI.ckFit supports 3 special **case-sensitive** keywords for `ACTIVITY_NAME`, namely `running/swimming/cycling`. + If any of these 3 keywords are used, then `ACTIVITY_QUANTIFIER` takes in one **positive non-zero integer** `[DISTANCE]` + in **metres** for the activity. + +* For **ALL** other kinds of `ACTIVITY_NAME`, `ACTIVITY_QUANTIFIER` takes in two **positive non-zero integers** + in the form `[SETS]x[REPS]`. In other words it is **assumed** that the activities are sets/repetitions + activities. (*Read more about Sets and Reps under [**Common Terminologies**](#common-terminologies-and-definitions)*) + +* Multiple activities can be entered as long as they are separated by a comma `,`. + +* The `/r` flag at the end is an ***optional*** flag for recurrence, which schedules a weekly recurring workout. + +* The ***optional*** `/r` flag, if included, **must** be at the end of the command. + + +Example of usage: + +* `add schedule weekly chest day /d 07/12/2021 /t 15:00 /a bench press:5x12, pushups:5x20 /r` adds a *recurring* workout + to your schedule with the description `weekly chest day` on `07/12/2021` at `15:00`. Furthermore, an activity breakdown of + `bench press` with `5` sets and `12` reps as well as `pushups` with `5` sets of `20` reps will also be added. +* `add schedule triathlon training /d 07/12/2021 /t 15:00 /a running:3000, swimming:1000, cycling:4000` adds a *non-recurring* workout + to your schedule with the description `traithlon training` on `07/12/2021` at `15:00`. Furthermore, an activity breakdown of + `running` for `3000` metres, `swimming` for `1000` metres as well as `cycling` for `4000` metres will also be added. + +Expected outcomes: +``` +Noted! CLI.ckFit has scheduled your recurring workout of description "weekly chest day" +on 07/12/2021 at 15:00. + +Activities Breakdown: +1. bench press: 5sets x 12reps +2. pushups: 5sets x 20reps +``` + +``` +Noted! CLI.ckFit has scheduled your workout of description "triathlon training" +on 07/12/2021 at 15:00. + +Activities Breakdown: +1. running: 3000metres +2. swimming: 1000metres +3. cycling: 4000metres +``` + +## Adding meal to library + +Command Word: `library addmeal` + +Description: Add a new meal record to the library, with its associated calories. + +Format: `library addmeal MEAL_NAME /c MEAL_CALORIES` + +* The MEAL_NAME can contain spaces. + +* Prefixes cannot be swapped and must follow the order shown above. No duplicates allowed. + +* Each prefix only accepts one input after it. + +* The MEAL_CALORIES can only contain positive integers inclusive of 0. + +Example of usage: + +`library addmeal chocolate cake /c 110` + +Expected output: + +``` +chocolate cake, which has 110 calories, will be added to your library of meals. You now have 2 meals! +``` + +## Adding fluid to library + +Command Word: `library addfluid` + +Description: Adds a new fluid record to the library, with its associated calories. + +Format: `library addfluid FLUID_NAME /c FLUID_CALORIES` + +* The FLUID_NAME can contain spaces. + +* Prefixes cannot be swapped and must follow the order shown above. No duplicates allowed. + +* Each prefix only accepts one input after it. + +* The FLUID_CALORIES can only contain positive integers inclusive of 0. + +Example of usage: + +`library addfluid chocolate milk /c 200` + +Expected outcome: + +`chocolate milk, which has 200 calories, will be added to your library of fluids. You now have 1 fluids!` + +## Delete a meal + +Command Word: `delete meal` + +Description: Remove a meal from the list of meal items. + +Format: `delete meal INDEX` + +* The `INDEX` can only contain integers from the list. + +Example of usage: + +`delete meal 1` + +Expected output: + +``` +rissoto will be removed from your list of meals consumed! +``` + +## Delete a fluid + +Command Word: `delete fluid` + +Description: Removes a fluid from the list of fluid items. + +Format: `delete fluid INDEX` + +* The `INDEX` can only contain integers from the list. + +Example of usage: + +`delete fluid 2` -{Give detailed description of each feature} +Expected outcome: -### Adding a todo: `todo` -Adds a new item to the list of todo items. +``` +Noted! CLI.ckFit has deleted your drink of milk of 180 calories and 100 ml on 08/09/2021 07:40. +``` -Format: `todo n/TODO_NAME d/DEADLINE` +## Delete a weight -* The `DEADLINE` can be in a natural language format. -* The `TODO_NAME` cannot contain punctuation. +Command Word: `delete weight` -Example of usage: +Description: Deletes a meal from the list of meals. -`todo n/Write the rest of the User Guide d/next week` +Format: `delete weight INDEX` -`todo n/Refactor the User Guide to remove passive voice d/13/04/2020` +* Use `list weights all` to determine the index of the meal you wish to delete. + +* The `INDEX` can only contain integers from the list. + +* Only 1 `INDEX` should be included in your input. + +Example of usage: + +`delete weight 1` + +Expected outcome: + +``` +Noted! CLI.ckFit has successfully deleted your weight of 50.0 kg on 03/04/2021. +``` + +## Delete a workout + +Command Word: `delete workout` + +Description: Deletes a workout from the list of workout items. + +Format: `delete workout INDEX` + +* Use `list workouts all` to determine the index of the workout you wish to delete. + +* The `INDEX` can only contain integers from the list. + +Example of usage: + +* `delete workout 3` will delete the workout with index `3` as seen in the list from `list schedule all` if the index is valid. + +Expected outcome: +``` +Noted! CLI.ckFit has successfully deleted your recorded workout of description "jog" +on 07/08/2021 at 15:00 where you burned 250 calories! +``` + +## Delete a scheduled workout + +Command Word: `delete schedule` + +Description: Deletes a workout from the list of workout items. + +Format: `delete schedule INDEX` + +* Use `list schedule all` to determine the index of the scheduled workout you wish to delete. + +* The `INDEX` can only contain integers from the list. + +Example of usage: + +* `delete schedule 3` will delete the schedule workout with index `3` as seen in the list from `list schedule all` if the index is valid. + +Expected outcome: +``` +Noted! CLI.ckFit has successfully deleted your recurring scheduled workout +of description "weekly chest day" on 07/12/2021 at 15:00! +``` + +## Delete a meal from library + +Command Word: `library deletemeal` + +Description: Deletes a meal record from the library. + +Format: `library deletemeal INDEX` + +* Use `library listmeals` to determine the index of the meal you wish to delete. + +Example of usage: + +`library deletemeal 1` + +Expected output: + +``` +chocolate cake will be removed from your list of meals consumed. You now have 0 meals left! +``` + +## Delete a fluid from library + +Command Word: `library deletefluid` + +Description: Deletes a fluid record from the library. + +Format: `library deletefluid INDEX` + +* Use `library listfluids` to determine the index of the meal you wish to delete. + +Example of usage: + +`library deletefluid 2` + +Expected outcome: + +`coke will be removed from your list of fluids consumed. You now have 0 fluids left!` + +## List meals + +Command Word: `list meals` + +Description: Lists all meals, with its associated calories, date and time of consumption. + +Format: `list meals ` + +* The `DATE` is in dd/mm/yyyy. + +* If `DATE` is left empty, the meals recorded **today** will be returned. + +* If the word `all` is written in place of `DATE`, **ALL** stored meals will be listed. + +Example of usage: + +`list meals`, `list meals 22/10/2021`, `list meals all` + +Expected output: + +``` +1. chicken soup +Calories: 150 +Date: 15/09/2021 +Time: 01:16 + +2. risoto +Calories: 12 +Date: 07/11/2021 +Time: 01:16 + +3. rice +Calories: 120 +Date: 14/12/2022 +Time: 15:07 + +Total number of meals: 3 +Total calories: 282 +``` + +## List fluids + +Command Word: `list fluids` + +Description: Lists all fluids, with its associated calories, date and time of consumption. + +Format: `list fluids ` + +* The `DATE` is in dd/mm/yyyy. + +* If `DATE` is left empty, the fluids recorded today will be returned. + +* If the word `all` is written in place of DATE, ALL stored fluids will be listed. + +Example of usage: + +`list fluids`, `list fluids 22/10/2021`, `list fluids all` + +Expected outcome: + +``` +1. cola +Calories: 60 +Volume: 100 +Date: 06/11/2021 +Time: 06:24 + +2. coke + Calories: 60 + Volume: 60 + Date: 07/11/2021 + Time: 00:37 + +Total number of fluids: 2 +Total calories: 120 +``` + +## List weights + +Command Word:`list weights` + +Description: Lists weight depending on date of entry. + +Format: `list weights ` + +* The `DATE` is in dd/mm/yyyy. + +* If `DATE` is left empty, the weights recorded **today** will be returned. + +* If the word `all` is written in place of `DATE`, **ALL** stored weights will be listed. + +* Only 1 `DATE` should be included in your input. + +Example of usage: + +* `list weights` will list the recorded weights for today +* `list weights 06/11/2021` will list the recorded weights on 06/11/2021 +* `list weights all` will list all recorded weights + +Expected outcome: + +``` +1. Weight: 50.0 kg +Total number of weights: 1 +``` + +``` +1. Weight: 52.0 kg +Total number of weights: 1 +``` + +``` +Here are your recorded weights: +1. Weight: 52.0 kg Date: 06/11/2021 + +2. Weight: 50.0 kg Date: 07/11/2021 + +Total number of weights: 2 +``` + +## List workouts + +Command Word:`list workouts` + +Description: Lists out all stored workout descriptions, +calories burned, date and time depending on date of entry. + +Format: `list workouts ` + +* The `DATE` is in dd/mm/yyyy. + +* If `DATE` is left empty, the workouts recorded **today** will be returned. + +* If the word `all` is written in place of `DATE`, **ALL** stored workouts will be listed. + + +Example of usage: + +* `list workouts` will list the recorded workouts for today's date. + +* `list workouts 08/11/2021` will list the recorded workouts on `08/11/2021`. + +* `list workouts all` will list all your recorded workouts. + +Expected outcomes: +``` +Workouts recorded today: +_________________________________________________________ +1. jog +Calories burned: 321 +Date: 07/11/2021 +Time: 01:45 +_________________________________________________________ +Total calories burned: 321 +``` + +``` +Workouts recorded on 08/11/2021: +_________________________________________________________ +1. swimming +Calories burned: 300 +Date: 08/11/2021 +Time: 15:15 +_________________________________________________________ +Total calories burned: 300 +``` + +``` +All recorded workouts: +_________________________________________________________ +1. jog +Calories burned: 321 +Date: 07/11/2021 +Time: 01:45 +_________________________________________________________ +2. swimming +Calories burned: 300 +Date: 08/11/2021 +Time: 15:15 +_________________________________________________________ +3. chest day +Calories burned: 200 +Date: 09/11/2021 +Time: 12:10 +_________________________________________________________ +You have completed a total of 3 workouts. Amazing job! +``` + +## List scheduled workouts + +Command Word: `list schedule` + +Description: Lists out stored scheduled workout descriptions, date and time as well as their activity +breakdowns depending on date + +Format: `list schedule ` + +* The `DATE` is in dd/mm/yyyy. + +* If `DATE` is left empty, the schedule for the **today** will be returned. + +* If the word `all` is written in place of `DATE`, **ALL** stored scheduled workouts will be listed. + +Example of usage: + +* `list schedule` will list all the schedule for today's date. + +* `list schedule 07/12/2021` will list the schedule for `07/12/2021`. + +* `list schedule all` will list your full schedule. + +Expected outcomes: +``` +Today's workout schedule: +_________________________________________________________ +1. triathlon training +Date: 07/11/2021 +Time: 15:00 + +Activities Breakdown: +1. running: 2000metres +2. swimming: 1000metres +3. cycling: 3000metres +_________________________________________________________ +You have 1 scheduled workouts on that day! +``` + +``` +Workout schedule on 07/12/2021: +_________________________________________________________ +1. weekly chest day [R] +Date: 07/12/2021 +Time: 15:00 + +Activities Breakdown: +1. bench press: 5sets x 12reps +2. pushups: 5sets x 20reps +_________________________________________________________ +You have 1 scheduled workouts on that day! +``` + +``` +Full Workout Schedule: +_________________________________________________________ +1. triathlon training +Date: 07/11/2021 +Time: 15:00 + +Activities Breakdown: +1. running: 2000metres +2. swimming: 1000metres +3. cycling: 3000metres +_________________________________________________________ +2. weekly chest day [R] +Date: 07/12/2021 +Time: 15:00 + +Activities Breakdown: +1. bench press: 5sets x 12reps +2. pushups: 5sets x 20reps +_________________________________________________________ +3. triathlon training +Date: 09/12/2021 +Time: 15:00 + +Activities Breakdown: +1. running: 3000metres +2. swimming: 1000metres +3. cycling: 4000metres +_________________________________________________________ +You have a total of 3 workouts in your schedule. +``` + + +## List Volumes + +Command Word: `list volumes` + +Description: List volumes of all fluids consumed for a specific date + +Format: `list volumes ` + +Expected outcome: + +``` +Your total volume consumption for 07/11/2021 is: 60 ml. +``` + +## List Calories + +Command Word: `list calories` + +Description: List total number of calories consumed, total number of calories burned and net calories + +for a chosen date + +Format: `list calories ` + +Expected outcome: + +``` +Your total calorie consumption for 07/11/2021 is: 60 calories. +Your total calories burned for 07/11/2021 is: 0 calories. +Your NET calories for 07/11/2021 is: 60 calories. +``` + +## List meals stored in library + +Command Word: `library listmeals` + +Description: Lists all meals stored in the library. + +Format: `library listmeals` + +Expected output: + +``` +1. pasta +Calories: 120 + +2. chocolate bar +Calories: 90 + +3. canned soup +Calories: 122 + +Total number of meals in library: 3 +``` + +## List fluids stored in library + +Command Word: `library listfluids` + +Description: Lists all fluids stored in the library. + +Format: `library listfluids` + +Expected outcome: + +``` +1. water + Calories: 0 + +2. cola + Calories: 60 + +Total number of fluids in library: 2 +``` + +## List everything on current date + +Command Word: `list` + +Description: Lists all meals, fluids, weight, workouts and scheduled workouts on a particular date + +Format `list ` + +* The `DATE` is in dd/mm/yyyy. + +* If `DATE` is left empty **today's** date will be taken. + +Example of usage: + +* `list` will list all data from meals, fluids, weight, workouts and schedule workouts for today's date. + +* `list 22/10/2021` will list all data on `22/10/2021`. + +Expected outcome: + +``` +Your meal list is empty! +Total number of meals: 0 +Total calories: 0 + +1. milk + Calories: 100 + Volume: 200 + Date: 07/11/2021 + Time: 01:17 + +Total number of fluids: 1 +Total calories: 100 + +1. Weight: 60.0 kg + Total number of weights: 1 + +No workouts recorded for today! + +No workouts scheduled for today! +``` + +## List everything on all dates + +Command Word: `list` + +Description: Lists all meals, fluids, weight, workouts and scheduled workouts on all dates + +Format `list all` + +Example of usage: + +`list all` + +Expected outcome: + +``` +1. risoto +Calories: 12 +Date: 07/11/2021 +Time: 01:16 + +2. rice +Calories: 120 +Date: 14/12/2022 +Time: 15:07 + +3. chicken soup +Calories: 150 +Date: 15/09/2021 +Time: 01:16 + +Total number of meals: 3 +Total calories: 282 + +1. cola +Calories: 123 +Volume: 50 +Date: 07/11/2021 +Time: 01:22 + +Total number of fluids: 1 +Total calories: 123 + +Here are your recorded weights: +1. Weight: 70.0 kg Date: 07/11/2021 + +Total number of weights: 1 + +All recorded workouts: +_________________________________________________________ +1. Hammer curls +Calories burned: 22 +Date: 07/11/2021 +Time: 01:22 +_________________________________________________________ +You have completed a total of 1 workouts. Amazing job! + +Full Workout Schedule: +_________________________________________________________ +1. test123 [R] +Date: 11/11/2021 +Time: 18:59 + +Activities Breakdown: +1. running: 800metres +2. swimming: 800metres +3. squats: 3sets x 10reps +4. cycling: 800metres + +_________________________________________________________ +You have a total of 1 workouts in your schedule. +``` + +## Help Command + +Command Word: `help` + +Description: Lists out the formats of input for meal, fluid, workout, weight and library commands and +provides the hyperlink to the user guide for a comprehensive walkthrough of features and commands. + +Example of usage: + +`help` + +Expected outcome: + +``` +parameters encapsulated by angle brackets "< >" are optional +NOTE: You can only omit putting MEAL_CALORIES if you have saved the meal in your meal library. + +[Add meal] | add meal MEAL_NAME +[Add fluid] | add fluid FLUID_NAME +[Add weight] | add weight WEIGHT /d +[Add workout] | add workout WORKOUT_NAME /c CALORIES_BURNED +[Add scheduled workout] | add schedule WORKOUT_NAME /d DATE /t TIME + | +[Add meal to library] | library addmeal MEAL_NAME /c MEAL_CALORIES +[Add fluid to library] | library addfluid FLUID_NAME /c FLUID_CALORIES + +[Remove meal] | delete meal INDEX +[Remove fluid] | delete fluid INDEX +[Remove weight] | delete weight INDEX +[Remove workout] | delete workout INDEX +[Remove scheduled workout] | delete schedule INDEX +[Remove meal from library] | library deletemeal INDEX +[Remove fluid from library] | library deletefluid INDEX + +[List meals] | list meals +[List fluids] | list fluids +[List weights] | list weights +[List workouts] | list workouts +[List calories] | list calories +[List volume] | list volumes +[List scheduled workouts] | list schedule +[List meals from library] | library listmeals +[List fluids from library] | library listfluids + +[Access user help] | help commands +[Access user guide] | help UG + +Here is the link to our User Guide! https://ay2122s1-cs2113t-f14-3.github.io/tp/UserGuide.html +``` + +## Exiting the application + +Command Word: `bye` + +Description: Terminates CLI.ckFit program. + +Example of usage: + +`bye` + +Expected outcome: + +``` +Thank you for the hardwork today. CLI.ckFit wishes you a good day +Team CLI.ckFit is proudly brought to you by Jiewen, Vishal, Pragyan, Ivy and Edward. +See you soon! +``` ## FAQ -**Q**: How do I transfer my data to another computer? +Q: Are the commands case-sensitive? -**A**: {your answer here} +A: Yes, please follow the specified casing. All commands are to be in lower-case. + +Q: How do I transfer my CLIckFit data to another computer? + +A: Transfer the data text files located in the same directory as your CLIckFit.jar file over +to your other computer. Place it in the same directory as the CLIckFit.jar file on your other computer. Your +data will then be loaded from the text files when you run CLIckFit.jar on your new computer. + +Q: Can I check and alter my CLIckFit data myself? + +A: Yes. You can open up the files, "Weight.txt", "Schedule.txt", "Workout.txt", "Food.txt" and +"FoodBank.txt". These files contain the associated data which you can view and also update manually in the correct format if you wish to. ## Command Summary -{Give a 'cheat sheet' of commands here} +*Psssstttttt click on the commands to skip sections!* + +* Parameters not enclosed in any brackets are compulsory while those enclosed in `<>` are ***optional***. + +* You can only omit putting calories if you have already saved the meal/fluid in their respective libraries. -* Add todo `todo n/TODO_NAME d/DEADLINE` +Command | Format of input +------------ | ------------- +[**Add meal**](#adding-a-meal)| `add meal MEAL_NAME ` +[**Add fluid**](#adding-fluids)| `add fluid FLUID_NAME ` +[**Add weight**](#adding-weight)| `add weight WEIGHT /d ` +[**Add workout**](#adding-workout)| `add workout WORKOUT_NAME /c CALORIES_BURNED ` +[**Add scheduled workout**](#adding-scheduled-workout)| `add schedule WORKOUT_NAME /d DATE /t TIME ` +[**Add meal to library**](#adding-meal-to-library)| `library addmeal MEAL_NAME /c MEAL_CALORIES` +[**Add fluid to library**](#adding-fluid-to-library)| `library addfluid FLUID_NAME /c FLUID_CALORIES` +[**Remove meal**](#delete-a-meal)| `delete meal INDEX` +[**Remove fluid**](#delete-a-fluid)| `delete fluid INDEX` +[**Remove weight**](#delete-a-weight)| `delete weight INDEX` +[**Remove workout**](#delete-a-workout)| `delete workout INDEX` +[**Remove scheduled workout**](#delete-a-scheduled-workout)| `delete schedule INDEX` +[**Remove meal from library**](#delete-a-meal-from-library)| `library deletemeal INDEX` +[**Remove fluid from library**](#delete-a-fluid-from-library)| `library deletefluid INDEX` +[**List meals**](#list-meals)| `list meals ` +[**List fluids**](#list-fluids)| `list fluids ` +[**List weights**](#list-weights)| `list weights ` +[**List workouts**](#list-workouts)| `list workouts ` +[**List calories**](#list-calories)| `list calories ` +[**List volume**](#list-volumes)| `list volumes ` +[**List scheduled workouts**](#list-scheduled-workouts)| `list schedule ` +[**List meals from library**](#list-meals-stored-in-library)| `library listmeals` +[**List fluids from library**](#list-fluids-stored-in-library)| `library listfluids` +[**List everything**](#List-everything-on-current-date)| `list ` +[**Access user help**](#help-command)| `help` +[**Exiting CLI.ckFit**](#exiting-the-application)| `bye` \ No newline at end of file diff --git a/docs/_config.yml b/docs/_config.yml new file mode 100644 index 0000000000..c4192631f2 --- /dev/null +++ b/docs/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-cayman \ No newline at end of file diff --git a/docs/team/arvejw.md b/docs/team/arvejw.md new file mode 100644 index 0000000000..0ae41162c6 --- /dev/null +++ b/docs/team/arvejw.md @@ -0,0 +1,73 @@ +# Teh Jiewen - Project Portfolio Page + +## Overview +CLI.ckFit is a desktop-based fitness app which can be accessed easily via CLI. CLI.ckFit allows you to input your calories, +weight, foods, and workouts throughout the day. It also comes with a BMI and recommended caloric intake calculator +which can give you an idea of your current fitness level. + +### Summary of Contributions +#### Code contributed +[RepoSense Link](https://nus-cs2113-ay2122s1.github.io/tp-dashboard/?search=&sort=groupTitle&sortWithin=title&since=2021-09-25&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=false&tabOpen=true&tabType=authorship&tabAuthor=arvejw&tabRepo=AY2122S1-CS2113T-F14-3%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code~other&authorshipIsBinaryFileTypeChecked=false) + +#### Enhancements implemented +* Weekly recurring workouts and automatic deletion/rescheduling of scheduled workouts + * What it does: Recurring workouts are rescheduled once its date is passed. Similarly non-recurring + workouts whose dates have passed are deleted. + * Justification: This feature ensures the workout schedule remains neat and clean. Furthermore, it does not + make sense for overdue workouts to remain in the list as the `WorkoutTracker` class already helps with the recording + of past workouts. + * Highlights: Workouts are rescheduled by incrementing the date of the workout by a multiple of 7 that makes the + workout's new date be within 7 days from the current date. The deleting and rescheduling of workouts is also done + upon loading of user data. While I had some idea of how I wanted to achieve this feature, it proved harder than + expected. "Rescheduling" by itself was fairly achievable after looking more in `java.time.*` and using the `plusDays()` method + from `java.time.LocalDateTime`. However, integrating this rescheduling in addition to deleting of overdue workouts + took quite a bit of time and effort as various other factors also like conditional checks and exception handling had + to be considered as well. + +* Optional activity breakdown for scheduled workouts + * What it does: Gives an option for users to include a comprehensive activity breakdown for their schedule workout. + * Justification: When scheduling and planning workouts, some users may want a more detailed breakdown of their workout + instead of just a single workout description. For instance if I have an upper body workout scheduled, I want to have a + break down of my activities during the workout, such as how many sets and reps of bench press, etc. + * Highlights: When parsing activities, an activity description is paired with its respective activity quantifiers + using HashMaps where the activity description is the key, and the activity quantifier(s) is/are the value. + For 3 special activity descriptions, namely running/cycling/swimming, the quantifier will take in one integer + to represent the distance in metres. Otherwise, the activity will be assumed to be quantified with [sets]x[reps], + taking in two integers instead. Having some knowledge of HashMaps and how they work, but not having any experience + actually using them in code proved for an interesting learning experience. I had quite a lot of instances of + addresses being printed instead of the values I was looking for. This was when I started out using an Array as my value + in the HashMap in the earlier stages of implementation (or rather attempt at implementation). However, after + some trial and error and learning more about how HashMaps work in Java, I was able to discover the flaws in + my implementation and correct them. + +#### Contributions to UG + +* Wrote add/delete/list workout command sections. +* Wrote add/delete/list schedule commands sections. +* Wrote some parts of known limitations section. + +#### Contributions to DG + +* ScheduleTracker Design & Implementation section. +* UML Class and sequence diagrams associated with ScheduleTracker. +* Some reformatting of the Design & Implementation section to be neater. +* Added appendix sections in the DG as well as some content to them. +* Added manual testing for workouts, scheduling of workouts and saving of data. + +#### Contributions to team-based tasks +* Some reformatting of the Design & implementation section in the DG. +* Added hyperlink support to table of contents in DG. +* Contributed in debugging code. +* Changed some parsing implementation to split user input by any length of whitespace instead of a fixed single whitespace character. + +#### Review/mentoring contributions +* Group mate PR reviews: + * [https://github.com/AY2122S1-CS2113T-F14-3/tp/pull/88](https://github.com/AY2122S1-CS2113T-F14-3/tp/pull/88) + * [https://github.com/AY2122S1-CS2113T-F14-3/tp/pull/77](https://github.com/AY2122S1-CS2113T-F14-3/tp/pull/77) + * [https://github.com/AY2122S1-CS2113T-F14-3/tp/pull/104](https://github.com/AY2122S1-CS2113T-F14-3/tp/pull/104) + +* Peer PR reviews: + * [https://github.com/nus-cs2113-AY2122S1/tp/pull/8/files/dcf009f3710b916496b8a710a3af954637d46056](https://github.com/nus-cs2113-AY2122S1/tp/pull/8/files/dcf009f3710b916496b8a710a3af954637d46056) + * [https://github.com/nus-cs2113-AY2122S1/ip/pull/198/files/f0e08de91b407cf0c41f9055e8eaca2e9004e0ae](https://github.com/nus-cs2113-AY2122S1/ip/pull/198/files/f0e08de91b407cf0c41f9055e8eaca2e9004e0ae) + * [https://github.com/nus-cs2113-AY2122S1/ip/pull/131/files/696012c5533fdfff551896a024445a1a56eb669c](https://github.com/nus-cs2113-AY2122S1/ip/pull/131/files/696012c5533fdfff551896a024445a1a56eb669c) + diff --git a/docs/team/edwardzywang.md b/docs/team/edwardzywang.md new file mode 100644 index 0000000000..d21f6627b1 --- /dev/null +++ b/docs/team/edwardzywang.md @@ -0,0 +1,98 @@ +# Wang Zhao Yu, Edward - Project Portfolio Page + +## Overview +CLI.ckFit is a desktop-based fitness app which can be accessed easily via CLI. CLI.ckFit allows you to input your calories, +weight, foods, and workouts throughout the day. It also comes with a BMI and recommended caloric intake calculator +which can give you an idea of your current fitness level. + +### Summary of Contributions +#### Code contributed +[Reposense Link](https://nus-cs2113-ay2122s1.github.io/tp-dashboard/?search=&sort=groupTitle&sortWithin=title&since=2021-09-25&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=false&tabOpen=true&tabType=authorship&tabAuthor=EdwardZYWang&tabRepo=AY2122S1-CS2113T-F14-3%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false) + +#### Enhancements implemented +* New feature implemented: Command Manager Class: + * What it does: A class that makes sense of a user's inputs. It first takes in the + user's input as string, then splits it in a string array. it takes the sub string to + the left of " /c " as the command and the remaining string is further separated + into substrings based on other separators. The command substring allows the + command manager to decide where to send the user's input to, while the + other substrings are the parameters of the user's input commands. + + * Why is it needed: It is needed so that the numerous commands that CLI.ckFit + allows the user to utilise are all managed and split at a common starting point. + This standardises the logic flow and also how we want the user's inputs to be + formatted in. Through standardisation, it reduces the load on all members + when it comes to debugging as the same basic logic flows can be used and + compared to quickly isolate bugs and resolve them. faster and also + standardise the logic pathway of making sense of the user's inputs. + + * What is noteworthy about it: Due to the numerous commands that CLI.ckFit supports, command manager is further split into different components, + namely foodbank parser, command manager, list parser, add parser, delete parser, to manage different types of commands. This makes for a neater outlook + on our command manager class when it comes to reviewing, and also when users add new commands or functions of the same type, we can easily refer to + past or other similar commands to know what format of inputs or parameters should be accepted. + +* New feature implemented: Miscellaneous classes like userHelp and contributions to Ui and Storage classes + * What it does: userHelp class handles the "help" command and has a unique output when invoked. memoryStartup method in Ui class takes in the user's input and when called, it loads all the information in the text + files as an array list by calling my methods in Storage class. My methods in Storage class then organises them in a reader-friendly + format, before printing the finished summary for the user to read. + * Why is it needed: userHelp class is needed as the help commands calls for a unique output and can be called outside the main program memoryStartup method is needed as it is called outside the main program and accounts for the event when the user does + not want to enter the main program to key in "list" command to see his past progress. It helps greatly in usability and convenience. + * What is special about it:memoryStartup also offers the user the ability to clear the contents in the text files if the user wishes to start a + new session, or if the user passes the program on to another user, and wishes to protect his privacy. + +* New feature: Implemented BMI calculator in calculator class + * What it does: A method which allows the user to calculate his or her BMI, so that the user + can know whether he or she should concentrate on gaining or losing weight. + * Why is it needed: It is needed to let the user know his or her starting point before using CLI.ckFit. It can also be accessed + without entering the main program, so it is useful for users who want to quickly check their BMI. + * What is special about it: It works well with recommended daily caloric intake calculator to give the user an idea of where he + or she stands health-wise. Knowing their starting point helps a lot when it comes to how they use CLIck.Fit for their fitness and health + goals. + +#### Contributions to UG +Wrote the following sections: +* Introduction +* Quick start +* Common terminologies and definitions +* Important FAQs +* Table of contents +* Help command +* Command Summary + +Handled standardisation and formatting for the user guide + +#### Contributions to DG +Wrote the following sections: +* User Interface (Ui) and its subsections +* UML diagrams for Getting BMI and daily caloric intake and Memory startup method +* Explanations for the above diagrams +* Added Manual Testing for BMI and recommended daily caloric intake calculators + +Contributed to: +* Appendix B User Stories + + +#### Contributions to team-based tasks +* Reformatting of the Design & implementation section in the UG. +* Added hyperlink support to table of contents in UG +* Added summary table of commands in UG and its hyperlinks +* Maintaining the issue tracker for PED and others +* Release management for v2.0 +* Wrote meeting agendas and took down notes during meetings for team member's reference +* Took down notes during meetings with professors and tutors for team member's reference + + +#### Review/mentoring contributions +* Group mate PR reviews: + * [https://github.com/AY2122S1-CS2113T-F14-3/tp/pull/21](https://github.com/AY2122S1-CS2113T-F14-3/tp/pull/21) + * [https://github.com/AY2122S1-CS2113T-F14-3/tp/pull/166](https://github.com/AY2122S1-CS2113T-F14-3/tp/pull/166) + * [https://github.com/AY2122S1-CS2113T-F14-3/tp/pull/138](https://github.com/AY2122S1-CS2113T-F14-3/tp/pull/138) + * [https://github.com/AY2122S1-CS2113T-F14-3/tp/pull/123](https://github.com/AY2122S1-CS2113T-F14-3/tp/pull/123) + * [https://github.com/AY2122S1-CS2113T-F14-3/tp/pull/122](https://github.com/AY2122S1-CS2113T-F14-3/tp/pull/122) + * [https://github.com/AY2122S1-CS2113T-F14-3/tp/pull/102](https://github.com/AY2122S1-CS2113T-F14-3/tp/pull/102) + * [https://github.com/AY2122S1-CS2113T-F14-3/tp/pull/83](https://github.com/AY2122S1-CS2113T-F14-3/tp/pull/83) + * [https://github.com/AY2122S1-CS2113T-F14-3/tp/pull/143](https://github.com/AY2122S1-CS2113T-F14-3/tp/pull/143) + +* Peer PR reviews: + * [PED](https://github.com/EdwardZYWang/alpha/issues/1) + * [Review of peer's PR](https://github.com/nus-cs2113-AY2122S1/ip/pull/170) \ No newline at end of file diff --git a/docs/team/johndoe.md b/docs/team/johndoe.md deleted file mode 100644 index ab75b391b8..0000000000 --- a/docs/team/johndoe.md +++ /dev/null @@ -1,6 +0,0 @@ -# John Doe - Project Portfolio Page - -## Overview - - -### Summary of Contributions diff --git a/docs/team/pragyan01.md b/docs/team/pragyan01.md new file mode 100644 index 0000000000..3ed73e6de1 --- /dev/null +++ b/docs/team/pragyan01.md @@ -0,0 +1,67 @@ +# Samal Sthitipragyan - Project Portfolio Page + +## Overview +CLI.ckFit is a desktop-based fitness app which can be accessed easily via CLI. CLI.ckFit allows you to input your calories, +weight, foods, and workouts throughout the day. It also comes with a BMI and recommended caloric intake calculator +which can give you an idea of your current fitness level. + +## Summary of Contributions +### Code contributed +[Reposense link](https://nus-cs2113-ay2122s1.github.io/tp-dashboard/?search=&sort=groupTitle&sortWithin=title&since=2021-09-25&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=false&tabOpen=true&tabType=authorship&tabAuthor=pragyan01&tabRepo=AY2122S1-CS2113T-F14-3%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false) + +### Enhancements implemented +* New feature for Fluid Class-getCalories(): Added ability to obtain total caloric intake due to fluids consumed for any specific date. + * What it does: This method allows the user to view his/her total caloric intake due to fluids for any specific date. This is done by adding up calories for all listed fluid intakes for that specific date. + * Justification: This feature provides convenience to the user as he/she can view and track her total caloric intake from only fluids for any specific date. + * Highlights: This enhancement adds on to its twin method from Meal Class as its intended to add to the cumulative caloric intake from meals to provide the user his total calorie count. For any specific date inputted by the user, the method runs through the fluid array and for any fluid entry in the array containing that specific date, its calorie parameter is extracted and added up to provide the total calorie count for fluids. + + +* New feature for Fluid class-getVolume(): Added ability to obtain total volume consumed due to fluid intakes for any specific date. + * What it does: This method allows the user to view his/her total volume intake due to fluids for any specific date. This is done by adding up calories for all listed fluid intakes for that specific date. + * Justification: This feature provides convenience to the user as he/she can view and track her total volume intake from only fluids for any specific date. + * Highlights: For any specific date inputted by the user, the method runs through the fluid array and for any fluid entry in the array containing that specific date, its volume parameter is extracted and added up to provide the total volume consumed for fluids. + + +* New feature for FoodBank class: Added ability to store fluids and their associated calories in library + * What it does: Users do not have to key in their calories when adding fluids that have already been saved to their fluid library, for repetitive fluids of the same description. +The fluid library also automatically adds the calories associated to that fluid from the meal library. + * Justification: This feature ensures that users do not need to input calories for fluids that they consume regularly, which may be difficult to remember or tedious to type it in again. They only need to input the name of the fluid, and the fluid library takes care of how many calories the fluid contains, provided that they have added this particular fluid its respective calorie count before to the fluid library. + * Highlights: When the user adds a fluid without its calories, the fluid library is checked. The name of the fluid that the user inputs is compared with the names of all the fluids within the fluid library. If the fluid exists, the calories of that particular fluid will be returned. Else, an exception will be thrown. One challenge that was faced was ensuring that no duplicate fluids were stored in the fluid library. Thus, when adding a fluid to the fluid library, the name of the fluid added is compared with all existing fluids within the fluid library. Another challenge was to ensure that the user had input the fluid with a description (name). Thus, the "getDescription()" method is called to obtain the description of the meal. The description was then checked for any separators. If it contained any separators, it was considered invalid, and an exception was thrown. + + +* New feature for DateTracker class: Added ability to sort array list containing dates and times in ascending order. + * What it does: DateTracker array list is now sorted in ascending order in the order of date and then time. + * Justification: Sorted array list allows text to be written to a local .txt file in ascending order for better readability + * Highlights: This feature enhances the saving and loading functionalities to/from a .txt file and supports array lists in other classes such as the Meal class, keep them all organised and sorted. One challenge faced is when the user might not input a date with his entries, making this sortDate() method unnecessary. This hurdle was overcame eventually as the program now inputs the user's system date and time at the time of his entry if the user does not input a date/time manually. + +#### Contributions to UG +* Technical start up section +* Add/delete/list fluids command sections +* Add/delete/list fluids in library command sections +* List calories/volume in the list command section +* Some parts of known limitations in each section + +#### Contributions to DG +* Fluid class diagram +* Adding fluid sequence diagram +* Deleting fluid sequence diagram +* Get total calories for specific date fluid sequence diagram + +#### Contributions to team-based tasks +* Some reformatting of the UG +* Contributed in refactoring of the code +* Contributed in debugging and finding loopholes + +#### Review/mentoring contributions +* Group mate PR reviews: + * [https://github.com/AY2122S1-CS2113T-F14-3/tp/pull/169](https://github.com/AY2122S1-CS2113T-F14-3/tp/pull/169) + * [https://github.com/AY2122S1-CS2113T-F14-3/tp/pull/168](https://github.com/AY2122S1-CS2113T-F14-3/tp/pull/168) + * [https://github.com/AY2122S1-CS2113T-F14-3/tp/pull/138](https://github.com/AY2122S1-CS2113T-F14-3/tp/pull/138) + * [https://github.com/AY2122S1-CS2113T-F14-3/tp/pull/114](https://github.com/AY2122S1-CS2113T-F14-3/tp/pull/114) + * [https://github.com/AY2122S1-CS2113T-F14-3/tp/pull/110](https://github.com/AY2122S1-CS2113T-F14-3/tp/pull/110) + * [https://github.com/AY2122S1-CS2113T-F14-3/tp/pull/107](https://github.com/AY2122S1-CS2113T-F14-3/tp/pull/107) + + +* Peer DG review: + * [https://github.com/nus-cs2113-AY2122S1/tp/pull/29](https://github.com/nus-cs2113-AY2122S1/tp/pull/29) + \ No newline at end of file diff --git a/docs/team/teoziyiivy.md b/docs/team/teoziyiivy.md new file mode 100644 index 0000000000..8efd57d318 --- /dev/null +++ b/docs/team/teoziyiivy.md @@ -0,0 +1,36 @@ +# Teo Ziyi Ivy - Project Portfolio Page + +## Overview +CLI.ckFit is a desktop-based fitness app which can be accessed easily via CLI. CLI.ckFit allows you to input your calories, +weight, foods, and workouts throughout the day. It also comes with a BMI and recommended caloric intake calculator +which can give you an idea of your current fitness level. + +### Summary of Contributions + +#### _New Feature_: +* Added `WeightTracker` class and weight commands/functions/responses. + * What it does: Handles weight commands from user allowing user to add, delete and list their weight. Also handling + the saving of weights to a `Weight.txt` file and loading of weights from the `Weight.txt` file. + * Justification: This feature improves the product significantly as users are able to track their + weights which is important for our target users who need to closely monitor and control their weight. + * Highlights: This enhancement affected existing commands and commands to be added in the future. It required an + in-depth analysis of design alternatives. The implementation was challenging as well as it required changes to + existing commands to ensure consistency between features despite taking in entirely different parameters which was + `double` as compared to the existing features such as the `Fluid` and `Meal` classes which used `String`. + * Credits: Groupmates helped flesh out the ideas for `WeightTracker` class, standardising the user inputs and features. + +#### _Code Contributed_: +* [**RepoSense Link**](https://nus-cs2113-ay2122s1.github.io/tp-dashboard/?search=&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2021-09-25&tabOpen=true&tabType=authorship&tabAuthor=teoziyiivy&tabRepo=AY2122S1-CS2113T-F14-3%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false) + +
+ +#### _Documentation_: +* User Guide: + * Added documentation for the weight related features +* Developer Guide: + * Added implementation details for the weight related features + * Added manual testing details for weight related features + +#### _Community_: +* Reviewed groupmates PRs +* Reported bugs and suggestions for other teams' UG,DG and applications diff --git a/docs/team/vishaljeyaram.md b/docs/team/vishaljeyaram.md new file mode 100644 index 0000000000..4294f0e958 --- /dev/null +++ b/docs/team/vishaljeyaram.md @@ -0,0 +1,78 @@ +# Vishal Jeyaram - Project Portfolio Page + +## Overview +CLI.ckFit is a desktop-based fitness app which can be accessed easily via CLI. CLI.ckFit allows you to input your calories, +weight, foods, and workouts throughout the day. It also comes with a BMI and recommended caloric intake calculator +which can give you an idea of your current fitness level. + +### Summary of Contributions + +#### Enhancements implemented +* New Feature: Added ability to store meals and their associated calories in library + * What it does: Users do not have to key in their calories when adding meals that have already been saved to their meal library. + * The meal library automatically adds the calories associated to that meal from the meal library. + * Justification: This feature ensures that users do not need to input calories for meals that they consume regularly, + * which may be difficult to remember. They only need to input the name of the meal and the meal library takes care of + * how many calories their meal contains, provided that they have added this particular meal and its calories to their + * meal library. + * Highlights: When the user adds a meal without its calories, the meal library is checked. The name of the meal that the user + * inputs is compared with the names of all the meals within the meal library. If the meal exists, the calories of that particular + * meal will be returned. Else, an exception will be thrown. One challenge that was faced was ensuring that no duplicate meals were stored + * in the meal library. Thus, when adding a meal to the meal library, the name of the meal added to compared with all existing + * meals within the meal library. + +* New feature: The current date will be generated if the date has not been keyed in. + * What it does: Obtains user's system's date and uses this date if the user has not input a date for any of their + * fitness parameters like adding their meals, fluids, weight or workouts. + * Justification: When adding meals, fluids, weight or workouts, it is inconvenient for the user to type out the date + * for every single new input. Furthermore, if they do enter a wrong date by any chance, they will need to delete their + * incorrect input. This wastes more time. Hence, in order to quicken the process, the current date will be input, saving + * the user time, if they do not manually input the date. + * Highlights: The user input's is checked to see if the user has input a date separator. If not, the system date will + * be generated using the Java's "getSystemDate()" method. + +* New feature: Optional calculator calculates recommended caloric intake for the user. + * What it does: Generates the ideal recommended caloric intake for users by taking in their activity level, sex, weight, + * height, and age. + * Justification: Users that are into pursuing fitness, especially those who are starting out, may need a rough gauge on + * how many calories they need to consume to maintain their body weight. This will also give them an idea of how many calories + * they will need to consume to either gain or lose weight. Thus, we can allow them to generate this ideal recommended + * caloric intake if they wish to, as it is an optional feature, everytime they launch the application. + * Highlights: A series of questions will be asked, prompting the user to key in their age, height, weight, sex and + * activity level on a scale of 1 - 5. Once the user has input these parameters, the Harris-Benedict Formula is used to + * generate the user's ideal recommended caloric intake. A challenge faced was making sure that the + * user also knew what approach they needed to take ie. gain, lose or maintain their weight. Thus, this calculator was used + * in conjunction with the BMI calculator to give the user a clearer idea of what approach they need to take. + * Credits: Harris-Benedict Equation was used. It is a method used to estimate an individual's basal metabolic rate (BMR). + +#### Code contributed +[Reposense Link](https://nus-cs2113-ay2122s1.github.io/tp-dashboard/?search=vishal&sort=groupTitle&sortWithin=title&since=2021-09-25&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=false&tabOpen=true&tabType=authorship&tabAuthor=VishalJeyaram&tabRepo=AY2122S1-CS2113T-F14-3%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false) + +#### Documentation + +* User Guide + * Parts of product introduction + * Add//delete/list meal command sections + * Add/delete/list meal library commands sections + * "List everything on all dates" command section + * FAQ section + +* Developer's Guide + * Meal Tracker class diagram + * Meal Tracker: List meals sequence diagram and explanation. + * Foodbank: Adding custom meal sequence diagram and explanation. + +#### Contributions to team-based tasks +* Contributed in debugging code. +* Contributed in integrating code together via command manager. +* Helped to refactor code and shorten methods. +* Gave ideas on using class-level attributes for methods. + +#### Review/mentoring contributions +* Group mate PR reviews: + * [#103](https://github.com/AY2122S1-CS2113T-F14-3/tp/pull/103) + * [#108](https://github.com/AY2122S1-CS2113T-F14-3/tp/pull/108) + * [#178](https://github.com/AY2122S1-CS2113T-F14-3/tp/pull/178) + +* Peer PR reviews: + * [[CS2113T-F11-4] CLIvershelf #43](https://github.com/nus-cs2113-AY2122S1/tp/pull/43) diff --git a/scheduleData.txt b/scheduleData.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/main/java/META-INF/MANIFEST.MF b/src/main/java/META-INF/MANIFEST.MF new file mode 100644 index 0000000000..19e86fe56e --- /dev/null +++ b/src/main/java/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: seedu.duke.Duke + diff --git a/src/main/java/seedu/duke/Calculator.java b/src/main/java/seedu/duke/Calculator.java new file mode 100644 index 0000000000..edb200d0e6 --- /dev/null +++ b/src/main/java/seedu/duke/Calculator.java @@ -0,0 +1,81 @@ +package seedu.duke; + +public class Calculator { + + protected double height; + protected double weight; + protected double bmi; + protected int idealCalories; + protected int age; + protected String sex; + protected int activityLevel; + protected double multiplier; + + //@@author teoziyiivy + /** + * Constructs the Calculator object. + */ + public Calculator(String sex, double weight, double height, int age, int activityLevel) { + this.height = height; + this.weight = weight; + this.age = age; + this.sex = sex; + this.activityLevel = activityLevel; + this.bmi = 0; + this.idealCalories = 0; + this.multiplier = 0; + } + + //@@author EdwardZYWang + /** + * method that receives weight, height and calculates the bmi of the user. + * the bmi value is then compared to the bounds and the user's bmi condition + * is returned + * + */ + public void getBmi() { + System.out.println(System.lineSeparator() + "Your BMI outcome is " + System.lineSeparator()); + String getBmiOutcome; + bmi = (weight / ((height / 100) * (height / 100))); + + if (bmi < 18.5) { + getBmiOutcome = "You are underweight"; + } else if ((bmi >= 18.5) && (bmi < 24.9)) { + getBmiOutcome = "You are currently at the optimal weight"; + } else if ((bmi >= 25) && (bmi < 29.9)) { + getBmiOutcome = "You are currently overweight"; + } else { + getBmiOutcome = "You are currently obese. Let CLI.ckFit help!"; + } + System.out.println(getBmiOutcome); + } + + //@@author VishalJeyaram + /** + * Prints out the ideal recommended caloric intake of an individual based on the Harris-Benedict Formula. + */ + public void getIdealCalories() { + System.out.println(System.lineSeparator() + "Your ideal number of calories to maintain your weight is " + + System.lineSeparator()); + if (activityLevel == 1) { + multiplier = 1.2; + } else if (activityLevel == 2) { + multiplier = 1.375; + } else if (activityLevel == 3) { + multiplier = 1.55; + } else if (activityLevel == 4) { + multiplier = 1.725; + } else if (activityLevel == 5) { + multiplier = 1.90; + } else { + System.out.println("go check yourself"); + } + + if (sex.equals("M")) { + idealCalories = (int) ((66.5 + (13.75 * weight) + (5.003 * height) - (6.775 * age)) * multiplier); + } else { + idealCalories = (int) ((655.1 + (9.563 * weight) + (1.850 * height) - (4.676 * age)) * multiplier); + } + System.out.println(idealCalories + " kcal"); + } +} diff --git a/src/main/java/seedu/duke/ClickfitMessages.java b/src/main/java/seedu/duke/ClickfitMessages.java new file mode 100644 index 0000000000..cf9d619da8 --- /dev/null +++ b/src/main/java/seedu/duke/ClickfitMessages.java @@ -0,0 +1,323 @@ +package seedu.duke; + +import seedu.duke.schedule.ScheduledWorkout; + +//@@author { ALL} +public class ClickfitMessages { + + public static final String INITIAL_PROMPT = "What would you like to start with?"; + + public static final String MESSAGE_A = System.lineSeparator() + "Welcome! Here's to your fitness journey! " + + "Type in any commands to get started! Type \"help\" to get started!" + + System.lineSeparator() + + "Lets work hard together in your fitness journey!"; + + public static final String CREDITS = System.lineSeparator() + "Thank you for the hardwork today. " + + "CLI.ckFit wishes you a good day" + System.lineSeparator() + "Team CLI.ckFit is proudly " + + "brought to you by Jiewen, Vishal, Pragyan, Ivy and Edward." + + System.lineSeparator() + "See you soon!"; + + public static final String MEMORY_STARTUP_PROMPT = System.lineSeparator() + "Would you " + + "like to clear all records of your fitness journey?" + System.lineSeparator() + + "Key in \"y\" to clear your records, or press enter keystroke to load in data from your " + + "previous session(s)" + System.lineSeparator() + + "Note: Keying in \"y\" will result in the previous session's data being deleted!"; + + public static final String INCORRECT_INPUT = System.lineSeparator() + "CLI.ckFit has detected a " + + "wrong input, kindly check your inputs or type \"help commands\" for input examples."; + + public static final String MEMORY_STARTUP_N_INPUT = System.lineSeparator() + "Understood! CLI.ckFit " + + "wishes you all the best for the rest of the day"; + + public static final String MEMORY_STARTUP_Y_INPUT = System.lineSeparator() + "Understood! " + + "CLI.ckFit is accessing your storage..."; + + public static final String MEMORY_STARTUP_INCORRECT_INPUT = System.lineSeparator() + "CLI.ckFit " + + "has detected a wrong input, kindly type in either a \"Y\" or a \"N\"."; + + public static final String HELP_COMMANDS = + "parameters encapsulated by angle brackets \"< >\" are optional" + System.lineSeparator() + + "NOTE: You can only omit putting MEAL_CALORIES if you have saved the meal in your meal library." + + System.lineSeparator() + + System.lineSeparator() + "[Add meal] | add meal MEAL_NAME " + + System.lineSeparator() + "[Add fluid] | add fluid FLUID_NAME " + + System.lineSeparator() + "[Add weight] | add weight WEIGHT /d " + + System.lineSeparator() + "[Add workout] | add workout WORKOUT_NAME /c " + + "CALORIES_BURNED " + + System.lineSeparator() + "[Add scheduled workout] | add schedule WORKOUT_NAME /d " + + "DATE /t TIME " + + System.lineSeparator() + "[Add meal to library] | library addmeal MEAL_NAME /c MEAL_CALORIES" + + System.lineSeparator() + "[Add fluid to library] | library addfluid " + + "FLUID_NAME /c FLUID_CALORIES" + + System.lineSeparator() + + System.lineSeparator() + "[Remove meal] | delete meal INDEX" + + System.lineSeparator() + "[Remove fluid] | delete fluid INDEX" + + System.lineSeparator() + "[Remove weight] | delete weight INDEX" + + System.lineSeparator() + "[Remove workout] | delete workout INDEX" + + System.lineSeparator() + "[Remove scheduled workout] | delete schedule INDEX" + + System.lineSeparator() + "[Remove meal from library] | library deletemeal INDEX" + + System.lineSeparator() + "[Remove fluid from library] | library deletefluid INDEX" + + System.lineSeparator() + + System.lineSeparator() + "[List meals] | list meals " + + System.lineSeparator() + "[List fluids] | list fluids " + + System.lineSeparator() + "[List weights] | list weights " + + System.lineSeparator() + "[List workouts] | list workouts " + + System.lineSeparator() + "[List calories] | list calories " + + System.lineSeparator() + "[List volume] | list volumes " + + System.lineSeparator() + "[List scheduled workouts] | list schedule " + + System.lineSeparator() + "[List meals from library] | library listmeals" + + System.lineSeparator() + "[List fluids from library] | library listfluids" + + System.lineSeparator() + + System.lineSeparator() + "[Access user help] | help commands" + + System.lineSeparator() + "[Access user guide] | help UG" + System.lineSeparator() + + System.lineSeparator() + "Here is the link to our User Guide! " + + "https://ay2122s1-cs2113t-f14-3.github.io/tp/UserGuide.html"; + + public static final String DATE_TIME_ERROR = "Please enter your date and time in the right format. " + + "It should be \"DD/MM/YYYY\" and \"HH:MM\" respectively."; + + public static final String NUMBER_ERROR = "Please enter a positive integer."; + + public static final String CALCULATOR_PROMPT = System.lineSeparator() + "Would you " + + "like to check your BMI and recommended caloric intake?" + System.lineSeparator() + + "Key in \"y\" to proceed, or press enter keystroke to skip!"; + + public static final String MEAL_PRINT_FORMAT = System.lineSeparator() + "[Meal Summary:]" + + System.lineSeparator() + "======================"; + + public static final String FLUID_PRINT_FORMAT = System.lineSeparator() + "[Fluid Summary:]" + + System.lineSeparator() + "======================"; + + public static final String WORKOUT_PRINT_FORMAT = System.lineSeparator() + "[Workout Summary:]" + + System.lineSeparator() + "======================"; + + public static final String WEIGHT_PRINT_FORMAT = System.lineSeparator() + "[Weight Summary:]" + + System.lineSeparator() + "======================"; + + public static final String ENDLINE_PRINT_FORMAT = "======================"; + + public static final String SCHEDULE_DATA_NOT_FOUND = "Unable to locate ScheduleTracker data file."; + + public static final String INCORRECT_LOADING_SCHEDULE_DATA = + "There were some errors during loading of ScheduleTracker data, some data may have been lost."; + + public static final String IO_EXCEPTION_MESSAGE = "Error when loading data from files"; + + public static final String FOOD_BANK_EXCEPTION_MESSAGE = "No such food or fluid with " + + "its associated calories is stored " + + "within your library. Please enter calories."; + + public static final String DELETE_OR_UPDATE_SCHEDULE_MESSAGE = "CLI.ckFit has detected some overdue scheduled " + + "workouts and has deleted/rescheduled them!"; + + public static final String WEIGHT_ADD_FORMAT_ERROR = "CLI.ckFit encountered a problem adding your weight." + + System.lineSeparator() + "Please follow the format: add weight WEIGHT_IN_KG "; + + public static final String WEIGHT_DELETE_FORMAT_ERROR = "CLI.ckFit encountered a problem deleting your weight." + + System.lineSeparator() + "Please follow the format: delete weight INDEX"; + + public static final String WEIGHT_DELETE_INDEX_ERROR = "CLI.ckFit encountered a problem deleting your weight." + + System.lineSeparator() + "Please ensure the index is within the list."; + + public static final String WEIGHT_EMPTY_ERROR = "Your weight list is empty!"; + + public static final String EMPTY_SCHEDULE_LIST_MESSAGE = "Your workout schedule is empty!"; + + public static final String EMPTY_WORKOUT_LIST_MESSAGE = "Your workout list is empty!"; + + public static final String WEIGHT_PARAMETERS_ERROR = "CLI.ckFit could not generate your parameters."; + + public static final String DONT_UNDERSTAND = "OOPS!!! I'm sorry, but I don't know what that means"; + + public static final String EMPTY_WORKOUT_LIST_TODAY_MESSAGE = "No workouts recorded for today!"; + + public static final String EMPTY_SCHEDULE_LIST_TODAY_MESSAGE = "No workouts scheduled for today!"; + + public static final String WORKOUT_SCHEDULE_TODAY_MESSAGE = "Today's workout schedule:" + System.lineSeparator() + + Ui.HORIZONTAL_BAR_SHORT; + + public static final String FULL_SCHEDULE_LIST_MESSAGE = "Full Workout Schedule:" + System.lineSeparator() + + Ui.HORIZONTAL_BAR_SHORT; + + public static final String FULL_WORKOUT_LIST_MESSAGE = "All recorded workouts:" + System.lineSeparator() + + Ui.HORIZONTAL_BAR_SHORT; + + public static final String WORKOUTS_RECORDED_TODAY_MESSAGE = "Workouts recorded today:" + + System.lineSeparator() + Ui.HORIZONTAL_BAR_SHORT; + + public static final String UNKNOWN_ERROR_MESSAGE = "Unknown error detected, please contact the devs of CLI.ckFit"; + + public static final String EMPTY_FLUID_LIBRARY = "Your fluids library is empty!"; + + public static final String EMPTY_FLUID_LIST = "Your fluids list is empty!"; + + public static String getScheduledWorkoutCountMessage(int workoutCount) { + return "You have " + workoutCount + " scheduled workouts on that day!"; + } + + public static String getWorkoutScheduleOnDateMessage(String date) { + return "Workout schedule on " + date + ":" + System.lineSeparator() + + Ui.HORIZONTAL_BAR_SHORT; + } + + public static String getEmptyScheduleOnDateMessage(String date) { + return "Workout schedule is empty on the date: " + date; + } + + public static String getAddScheduleSuccessMessage(ScheduledWorkout workoutToAdd) { + return "Noted! CLI.ckFit has scheduled your " + workoutToAdd.isRecurringStatusAsText() + + "workout of description \"" + workoutToAdd.getWorkoutDescription() + "\" on " + + workoutToAdd.getWorkoutDate() + " at " + workoutToAdd.getWorkoutTime() + "."; + } + + public static String getDeleteScheduleSuccessMessage(ScheduledWorkout workoutToDelete) { + return "Noted! CLI.ckFit has successfully deleted your " + + workoutToDelete.isRecurringStatusAsText() + "scheduled workout of description \"" + + workoutToDelete.getWorkoutDescription() + "\" on " + workoutToDelete.getWorkoutDate() + + " at " + workoutToDelete.getWorkoutTime() + "!"; + } + + public static String getAddWorkoutSuccessMessage(String workoutDescription, String workoutDate, + String workoutTime, int caloriesBurned) { + return "Noted! CLI.ckFit has recorded your workout of description \"" + workoutDescription + + "\" on " + workoutDate + " at " + workoutTime + " where you burned " + + caloriesBurned + " calories!"; + } + + public static String getDeleteWorkoutSuccessMessage(String workoutDescription, String workoutDate, + String workoutTime, int caloriesBurned) { + return "Noted! CLI.ckFit has successfully deleted your recorded workout of description \"" + + workoutDescription + "\" on " + workoutDate + " at " + workoutTime + System.lineSeparator() + + "where you burned " + caloriesBurned + " calories!"; + } + + public static String getEmptyWorkoutListOnDateMessage(String date) { + return "No workouts recorded on the date: " + date; + } + + public static String getWorkoutsOnDateMessage(String date) { + return "Workouts recorded on " + date + ":" + + System.lineSeparator() + Ui.HORIZONTAL_BAR_SHORT; + } + + public static String getTotalScheduledWorkoutMessage(int scheduleCount) { + return "You have a total of " + scheduleCount + " workouts in your schedule."; + } + + public static String getTotalCaloriesBurnedMessage(int caloriesBurned) { + return "Total calories burned: " + caloriesBurned; + } + + public static String getTotalWorkoutsDoneMessage(int totalWorkouts) { + return "You have completed a total of " + totalWorkouts + " workouts. Amazing job!"; + } + + //@@author VishalJeyaram + /** + * To notify the user that a meal has been added to their list of meals. + */ + public static void printAddedMealMessage(String description, String date, String time, + int calories) { + System.out.println("Noted! CLI.ckFit has recorded your meal of " + + description + " on " + date + " and at " + time + + ". " + calories + " calories has been added to the calorie count!\n"); + } + + //@@author VishalJeyaram + /** + * To notify the user that a meal has been deleted from their list of meals. + */ + public static void printDeletedMealMessage(String description, String date) { + System.out.println(description + " will be removed from your list of meals consumed!\n"); + } + + //@@author VishalJeyaram + /** + * To notify the user that their meal list is empty. + */ + public static void printEmptyMealList() { + System.out.println("Your meal list is empty!"); + } + + //@@author VishalJeyaram + /** + * To tell the user how many calories and meals they have consumed thus far. + */ + public static void printMealListTotals(int mealNumber, int totalCalories) { + System.out.println("Total number of meals: " + mealNumber); + System.out.println("Total calories: " + totalCalories); + } + + //@@author VishalJeyaram + /** + * To tell the user the details of a single meal such as its name, calories, and date and time of consumption. + */ + public static void printSingleMealDetails(int i, String description, int calories, String date, String time) { + System.out.println(i + ". " + description); + System.out.println("Calories: " + calories); + System.out.println("Date: " + date); + System.out.println("Time: " + time + "\n"); + } + + //@@author VishalJeyaram + /** + * To notify the user that a meal has been added to their library. + */ + public static void printAddedLibraryMeal(String description, int calories, int totalMeals) { + System.out.println(description + ", which has " + calories + + " calories, will be added to your library of meals. You now have " + totalMeals + " meals!\n"); + } + + //@@author VishalJeyaram + /** + * To notify the user that a meal has been deleted from their library. + */ + public static void printDeletedLibraryMeal(String description, int totalMeals) { + System.out.println(description + " will be removed from your meal library. You now have " + + totalMeals + " meals left!\n"); + } + + //@@author VishalJeyaram + /** + * To tell the user the details of a single meal in the library such as its name and calories. + */ + public static void printSingleLibraryMeal(int i, String description, int calories) { + System.out.println(i + ". " + description); + System.out.println("Calories: " + calories + "\n"); + } + + //@@author VishalJeyaram + /** + * To tell the user how many meals are in their meal library. + */ + public static void printMealLibraryTotals(int totalMeals) { + System.out.println("Total number of meals in library: " + totalMeals); + } + + //@@author teoziyiivy + /** + * Prints response for weight has been added to weight array successfully. + * + * @param weight Weight recorded in weight array. + * @param date Date recorded in weight array. + */ + public static void printAddWeightResponse(double weight, String date) { + System.out.println("Noted! CLI.ckFit has recorded your weight as " + + weight + " kg on " + date + ". Check back for your progress!"); + } + + //@@author teoziyiivy + /** + * Prints response for weight has been deleted from weight array successfully. + * + * @param weight Weight recorded in weight array. + * @param date Date recorded in weight array. + */ + public static void printDeleteWeightResponse(double weight, String date) { + System.out.println("Noted! CLI.ckFit has successfully deleted your weight of " + + weight + " kg on " + date + "."); + } +} + + diff --git a/src/main/java/seedu/duke/CommandManager.java b/src/main/java/seedu/duke/CommandManager.java new file mode 100644 index 0000000000..899346d7d9 --- /dev/null +++ b/src/main/java/seedu/duke/CommandManager.java @@ -0,0 +1,395 @@ +package seedu.duke; + +import seedu.duke.exceptions.DukeException; +import seedu.duke.exceptions.fluid.FluidExceptions; +import seedu.duke.exceptions.foodbank.FoodBankException; +import seedu.duke.exceptions.meal.MealException; +import seedu.duke.exceptions.schedule.ScheduleException; +import seedu.duke.exceptions.weight.DeleteWeightException; +import seedu.duke.exceptions.weight.WeightException; +import seedu.duke.exceptions.workout.WorkoutException; +import seedu.duke.schedule.ScheduleTracker; + +import java.io.IOException; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.Objects; +import java.util.Scanner; + +//@@author EdwardZYWang +public class CommandManager { + + protected ScheduleTracker scheduleTracker; + protected WorkoutTracker workoutTracker; + protected Meal meal; + protected Fluid fluid; + protected WeightTracker weightTracker; + protected UserHelp userHelp; + protected Scanner scanner; + protected boolean isExit; + protected String command; + protected String inputArguments; + protected Storage storage; + + public CommandManager(Storage storage, Fluid fluid, Meal meal, + ScheduleTracker scheduleTracker, WorkoutTracker workoutTracker, + WeightTracker weightTracker, UserHelp userHelp) { + this.fluid = fluid; + this.meal = meal; + this.scheduleTracker = scheduleTracker; + this.workoutTracker = workoutTracker; + this.scanner = new Scanner(System.in); + this.weightTracker = weightTracker; + this.userHelp = userHelp; + this.isExit = false; + this.storage = storage; + } + + /** + * A method that makes sense of the user's inputs. The input is taken in, then split using splitResults. + * splitResults[0] is used to identify the command word of the user's input. splitResult[1] is the + * rest of the command. splitResult[1] saved as inputArguments after checking if it is not an empty + * command. inputArgument is then passed on to the other methods through switch cases. + * + * + * @throws DukeException if there is an Duke error + * @throws NullPointerException when an application attempts to use null in a case where an object is required + * @throws MealException when there is an unknown error has occurred in MealTracker + * @throws FluidExceptions when there is an unknown error has occurred in FluidTracker + * @throws FoodBankException when there is an unknown error has occurred + * @throws IOException when there is an input error + * @throws ScheduleException when an unknown error has occurred in ScheduleTracker + * @throws WeightException when an unknown error has occurred in Weight Tracker + * @throws WorkoutException when an unknown error has occurred in WorkoutTracker + */ + public void commandChecker() throws + DukeException, NullPointerException, + MealException, FluidExceptions, + FoodBankException, IOException, + ScheduleException, WeightException, + WorkoutException { + String input = scanner.nextLine(); + System.out.println(Ui.HORIZONTAL_BAR_LONG + System.lineSeparator()); + String[] splitResults = input.trim().split(" ", 2); + command = splitResults[0]; + inputArguments = (splitResults.length == 2) ? splitResults[1] : null; + assert !Objects.equals(inputArguments, ""); + switch (command) { + case Keywords.LIST: + if (inputArguments == null) { + listEverything(Parser.getSystemDate()); + } else { + listParser(inputArguments); + } + break; + case Keywords.LIBRARY: + assert inputArguments != null; + foodBankParser(inputArguments); + break; + case Keywords.ADD: + assert inputArguments != null; + addParser(inputArguments); + break; + case Keywords.DELETE: + assert inputArguments != null; + deleteParser(inputArguments); + break; + case Keywords.INPUT_HELP: + UserHelp.generateUserHelpParameters(inputArguments); + break; + case Keywords.INPUT_BYE: + isExit = true; + System.out.println(ClickfitMessages.CREDITS); + break; + default: + System.out.println(ClickfitMessages.DONT_UNDERSTAND); + break; + } + saveEverything(); + } + + /** + * Calls other methods to save the user's inputs into the respective text files + * so that future sessions can start from where they ended. + * + * @throws IOException when there is an input error + */ + public void saveEverything() throws IOException { + storage.saveFood(fluid, meal); + storage.saveLibrary(); + storage.saveWeight(weightTracker); + storage.saveSchedule(scheduleTracker); + storage.saveWorkout(workoutTracker); + } + + /** + * A method that makes sense of the user's inputs for food bank class. The input is taken in, then split + * using splitResults. splitResults[0] is used to identify the command word of the user's input. + * splitResult[1] is the rest of the command. splitResult[1] saved as inputArguments after + * checking if it is not an empty command. inputArguments is then passed on to the other methods + * through switch cases. + * + * + * @param inputArguments user's input commands' parameters + * @throws NullPointerException when an application attempts to use null in a case where an object is required + * @throws FoodBankException when there is an unknown error has occurred + */ + public void foodBankParser(String inputArguments) throws + NullPointerException, FoodBankException { + String[] splitResults = inputArguments.trim().split(" ", 2); + command = splitResults[0]; + inputArguments = (splitResults.length == 2) ? splitResults[1] : null; + switch (command) { + case Keywords.ADD_FLUID: + FoodBank.addCustomFluid(inputArguments); + break; + case Keywords.DELETE_DRINKS: + FoodBank.deleteCustomFluids(inputArguments); + break; + case Keywords.LIST_DRINKS: + FoodBank.listCustomFluids(); + break; + case Keywords.ADD_MEAL: + FoodBank.addCustomMeal(inputArguments); + break; + case Keywords.DELETE_MEAL: + FoodBank.deleteCustomMeal(inputArguments); + break; + case Keywords.LIST_MEAL: + FoodBank.listCustomMeal(); + break; + default: + System.out.println(ClickfitMessages.DONT_UNDERSTAND); + break; + } + } + + /** + * A method that makes sense of the user's inputs for "list" commands. inputArguments is take in and checked + * if it is empty or not. if it is not empty, it then checks to see if the users has entered any date + * as part of the list commands' parameters. if there is a date, it will list out the respective class' + * contents for that date, else, it will list out the respective class' contents for the system date. + * + * + * @param inputArguments user's input commands' parameters + * @throws NullPointerException when an application attempts to use null in a case where an object is required + * @throws MealException when there is an unknown error has occurred in MealTracker + * @throws FluidExceptions when there is an unknown error has occurred in FluidTracker + * @throws FoodBankException when there is an unknown error has occurred + * @throws WeightException when an unknown error has occurred in Weight Tracker + * @throws WorkoutException when an unknown error has occurred in WorkoutTracker + */ + public void listParser(String inputArguments) throws + NullPointerException, FoodBankException, FluidExceptions, + WeightException, + WorkoutException, MealException { + String[] splitResults = inputArguments.trim().split(" ", 2); + command = splitResults[0]; + String date; + if (splitResults.length == 1) { + if (command.equals("all")) { + listEverything("all"); + return; + } else if (command.contains("/")) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy"); + LocalDate localDate = LocalDate.parse(command, formatter); + date = formatter.format(localDate); + listEverything(date); + return; + } else { + date = Parser.getSystemDate(); + } + } else { + date = splitResults[1]; + if (!date.equals("all")) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy"); + LocalDate localDate = LocalDate.parse(date, formatter); + date = formatter.format(localDate); + } + } + switch (command) { + case Keywords.MEALS: + meal.listMeals(date); + break; + case Keywords.FLUIDS: + fluid.listFluids(date); + break; + case Keywords.CALORIES: + listCalories(date); + break; + case Keywords.VOLUME: + int volCount = fluid.getVolume(date); + System.out.println("\n" + "Your total volume consumption for " + date + " is: " + volCount + " ml."); + break; + case Keywords.WORKOUTS: + workoutTracker.listWorkouts(date); + break; + case Keywords.SCHEDULE: + scheduleTracker.listScheduledWorkouts(date); + break; + case Keywords.WEIGHTS: + weightTracker.listWeights(date); + break; + default: + System.out.println(ClickfitMessages.DONT_UNDERSTAND); + break; + } + } + + /** + * A method that makes sense of the user's inputs for "list calories" commands. it checks to see if the users + * has entered any date as part of the list commands' parameters. if there is a date, it will list out + * the calories gained from consuming food, the calories burnt through workouts and the net calories + * for that date, else, it will list out the calories gained from consuming food, the calories burnt + * through workouts and the net calories for the system date. + * + * + * @param date user's date input + * @throws MealException when there is an unknown error has occurred in MealTracker + * @throws FluidExceptions when there is an unknown error has occurred in FluidTracker + * @throws FoodBankException when there is an unknown error has occurred + * @throws WorkoutException when an unknown error has occurred in WorkoutTracker + */ + private void listCalories(String date) throws + FluidExceptions, FoodBankException, + WorkoutException, MealException { + int calCount = fluid.getCalories(date) + meal.getCalories(date); + int caloriesBurned = workoutTracker.getCaloriesBurned(date); + int netCalories = calCount - caloriesBurned; + System.out.println(System.lineSeparator() + "Your total calorie consumption for " + + date + " is: " + calCount + " calories."); + System.out.println("Your total calories burned for " + date + " is: " + caloriesBurned + " calories."); + System.out.println("Your NET calories for " + date + " is: " + netCalories + " calories."); + } + + /** + * A method that makes sense of the user's inputs. The input is taken in, then split using splitResults. + * splitResults[0] is used to identify the command word of the user's input. splitResult[1] is the + * rest of the command. splitResult[1] saved as inputArguments after checking if it is not an empty + * command. inputArgument is then passed on to the other "add" methods through switch cases. + * + * + * @param input user's input + * @throws NullPointerException when an application attempts to use null in a case where an object is required + * @throws MealException when there is an unknown error has occurred in MealTracker + * @throws FluidExceptions when there is an unknown error has occurred in FluidTracker + * @throws FoodBankException when there is an unknown error has occurred + * @throws WeightException when an unknown error has occurred in Weight Tracker + * @throws WorkoutException when an unknown error has occurred in WorkoutTracker + * @throws ScheduleException when an unknown error has occurred in ScheduleTracker + */ + public void addParser(String input) throws + NullPointerException, FoodBankException, + DukeException, MealException, + FluidExceptions, ScheduleException, + WorkoutException, WeightException { + String[] splitResults = input.trim().split(" ", 2); + command = splitResults[0]; + inputArguments = (splitResults.length == 2) ? splitResults[1] : null; + switch (command) { + case Keywords.MEAL: + meal.addMeal(inputArguments); + DateTracker.sortDateAndTime(meal.meals); + break; + case Keywords.FLUID: + fluid.addFluid(inputArguments); + DateTracker.sortDateAndTime(fluid.fluidArray); + break; + case Keywords.WORKOUT: + workoutTracker.addWorkout(inputArguments, false); + DateTracker.sortDateAndTime(workoutTracker.workouts); + break; + case Keywords.SCHEDULE: + scheduleTracker.addScheduledWorkout(inputArguments, false, true); + break; + case Keywords.WEIGHT: + weightTracker.addWeight(inputArguments); + DateTracker.sortDate(weightTracker.weightsArray); + break; + default: + System.out.println(ClickfitMessages.DONT_UNDERSTAND); + break; + } + } + + /** + * A method that makes sense of the user's inputs. The input is taken in, then split using splitResults. + * splitResults[0] is used to identify the command word of the user's input. splitResult[1] is the + * rest of the command. splitResult[1] saved as inputArguments after checking if it is not an empty + * command. inputArgument is then passed on to the other "delete" methods through switch cases. + * + * + * @param input user's input + * @throws NullPointerException when an application attempts to use null in a case where an object is required + * @throws MealException when there is an unknown error has occurred in MealTracker + * @throws FluidExceptions when there is an unknown error has occurred in FluidTracker + * @throws FoodBankException when there is an unknown error has occurred + * @throws WeightException when an unknown error has occurred in Weight Tracker + * @throws WorkoutException when an unknown error has occurred in WorkoutTracker + * @throws ScheduleException when an unknown error has occurred in ScheduleTracker + */ + public void deleteParser(String input) throws NullPointerException, + FoodBankException, DukeException, + ScheduleException, WorkoutException, + MealException, WeightException, + FluidExceptions { + + String[] splitResults = input.trim().split(" ", 2); + command = splitResults[0]; + inputArguments = (splitResults.length == 2) ? splitResults[1] : null; + switch (command) { + case Keywords.MEAL: + meal.deleteMeal(inputArguments); + break; + case Keywords.FLUID: + fluid.deleteFluid(inputArguments); + break; + case Keywords.WORKOUT: + workoutTracker.deleteWorkout(inputArguments); + break; + case Keywords.SCHEDULE: + scheduleTracker.deleteScheduledWorkout(inputArguments); + break; + case Keywords.WEIGHT: + if (inputArguments == null) { + throw new DeleteWeightException(); + } + weightTracker.deleteWeight(inputArguments); + break; + default: + System.out.println(ClickfitMessages.DONT_UNDERSTAND); + break; + } + DateTracker.deleteDateFromList(inputArguments, fluid, meal, workoutTracker, weightTracker); + } + + /** + * A method that makes sense of the user's inputs for list everything command. it checks to see if the users + * has entered any date as part of the list commands' parameters. if there is a date, it will list everything + * in the text files for all classes for that date, else, it will just it will list everything + * in the text files for all classes for the system date. + * + * + * @param date user's date input + * @throws NullPointerException when an application attempts to use null in a case where an object is required + * @throws MealException when there is an unknown error has occurred in MealTracker + * @throws FluidExceptions when there is an unknown error has occurred in FluidTracker + * @throws FoodBankException when there is an unknown error has occurred + * @throws WeightException when an unknown error has occurred in Weight Tracker + * @throws WorkoutException when an unknown error has occurred in WorkoutTracker + */ + public void listEverything(String date) throws + NullPointerException, FoodBankException, + WorkoutException, + MealException, FluidExceptions, WeightException { + meal.listMeals(date); + System.out.println(); + fluid.listFluids(date); + System.out.println(); + weightTracker.listWeights(date); + System.out.println(); + workoutTracker.listWorkouts(date); + System.out.println(); + scheduleTracker.listScheduledWorkouts(date); + } +} \ No newline at end of file diff --git a/src/main/java/seedu/duke/DateTracker.java b/src/main/java/seedu/duke/DateTracker.java new file mode 100644 index 0000000000..935b84700d --- /dev/null +++ b/src/main/java/seedu/duke/DateTracker.java @@ -0,0 +1,121 @@ +package seedu.duke; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; + +public class DateTracker { + protected static ArrayList dates = new ArrayList<>(); + + //@@author VishalJeyaram + /** + * Checks if a date for any parameter has already been accounted for. + * If is has not been accounted for, the date will be added to the list of dates. + * + * @param newDate Date to be checked + */ + public static void checkIfDateExists(String newDate) { + for (String date : dates) { + if (date.equals(newDate)) { + return; + } + } + dates.add(newDate); + sortDate(); + } + + public static void sortDate() { + Collections.sort(dates); + } + + //@@author pragyan01 + /** + * This method sorts an array list by date. + * + * @param list any array list provided. + * + * @author pragyan01 + */ + public static void sortDate(ArrayList list) { + Collections.sort(list, new Comparator() { + /** + * This method compares two date strings. + * + * @return sorted array list by date. + */ + public int compare(String o1, String o2) { + LocalDate o1Date = LocalDate.parse(Parser.getDateNoDateTracker(o1), + DateTimeFormatter.ofPattern("dd/MM/yyyy")); + LocalDate o2Date = LocalDate.parse(Parser.getDateNoDateTracker(o2), + DateTimeFormatter.ofPattern("dd/MM/yyyy")); + return o1Date.compareTo(o2Date); + } + }); + } + + //@@author VishalJeyaram + /** + * Deletes a date from the list of dates if no meal, workout, weight or fluid occurs on that date. + * + * @param inputArguments User's input + * @param fluid Fluid object + * @param meal Meal object + * @param workoutTracker WorkoutTracker object + * @param weightTracker WeightTracker object + */ + public static void deleteDateFromList(String inputArguments, Fluid fluid, Meal meal, + WorkoutTracker workoutTracker, + WeightTracker weightTracker) { + String date = Parser.getDate(inputArguments); + for (String m : meal.meals) { + if (m.contains(date)) { + return; + } + } + for (String f : fluid.fluidArray) { + if (f.contains(date)) { + return; + } + } + for (String w : workoutTracker.workouts) { + if (w.contains(date)) { + return; + } + } + for (String w : weightTracker.weightsArray) { + if (w.contains(date)) { + return; + } + } + dates.remove(date); + } + + //@@author arvejw + /** + * Sorts ArrayList of String by ascending order of date time. + * + * @param list ArrayList to be sorted. + */ + public static void sortDateAndTime(ArrayList list) { + Collections.sort(list, new Comparator() { + public int compare(String o1, String o2) { + LocalDateTime o1DateTime = LocalDateTime.of( + LocalDate.parse(Parser.getDateNoDateTracker(o1), + DateTimeFormatter.ofPattern("dd/MM/yyyy")), + LocalTime.parse(Parser.getTime(o1), + DateTimeFormatter.ofPattern("HH:mm"))); + LocalDateTime o2DateTime = LocalDateTime.of( + LocalDate.parse(Parser.getDateNoDateTracker(o2), + DateTimeFormatter.ofPattern("dd/MM/yyyy")), + LocalTime.parse(Parser.getTime(o2), + DateTimeFormatter.ofPattern("HH:mm"))); + return o1DateTime.compareTo(o2DateTime); + } + }); + } +} + diff --git a/src/main/java/seedu/duke/Duke.java b/src/main/java/seedu/duke/Duke.java index 5c74e68d59..df2ac4f7a9 100644 --- a/src/main/java/seedu/duke/Duke.java +++ b/src/main/java/seedu/duke/Duke.java @@ -1,21 +1,111 @@ package seedu.duke; -import java.util.Scanner; +import seedu.duke.exceptions.DukeException; +import seedu.duke.exceptions.fluid.FluidExceptions; +import seedu.duke.exceptions.foodbank.FoodBankException; +import seedu.duke.exceptions.meal.MealException; +import seedu.duke.exceptions.schedule.ScheduleException; +import seedu.duke.exceptions.workout.WorkoutException; +import seedu.duke.schedule.ScheduleTracker; +import seedu.duke.exceptions.weight.WeightException; +import seedu.duke.exceptions.LoadException; +import java.io.IOException; +import java.time.format.DateTimeParseException; +import java.util.logging.LogManager; + +import static seedu.duke.ClickfitMessages.IO_EXCEPTION_MESSAGE; +import static seedu.duke.ClickfitMessages.MEMORY_STARTUP_INCORRECT_INPUT; + +//@@author teoziyiivy +@SuppressWarnings("ALL") + +/** + * This Duke class contains the backbone for running our entire application. + */ public class Duke { - /** - * Main entry-point for the java.duke.Duke application. - */ - public static void main(String[] args) { - String logo = " ____ _ \n" - + "| _ \\ _ _| | _____ \n" - + "| | | | | | | |/ / _ \\\n" - + "| |_| | |_| | < __/\n" - + "|____/ \\__,_|_|\\_\\___|\n"; - System.out.println("Hello from\n" + logo); - System.out.println("What is your name?"); + private Meal meal; + private Ui ui; + private Fluid fluid; + private ScheduleTracker scheduleTracker; + private WorkoutTracker workoutTracker; + private WeightTracker weightTracker; + private CommandManager commandManager; + private UserHelp userHelp; + private FoodBank foodbank; + private DateTracker dateTracker; + private Storage storage; + + public static void main(String[] args) throws DukeException, FoodBankException, FluidExceptions { + new Duke().run(); + } + + public Duke() throws DukeException, FoodBankException, FluidExceptions { + try { + meal = new Meal(); + ui = new Ui(); + fluid = new Fluid(); + scheduleTracker = new ScheduleTracker(); + workoutTracker = new WorkoutTracker(); + weightTracker = new WeightTracker(); + userHelp = new UserHelp(); + storage = new Storage(); + foodbank = new FoodBank(); + ui.welcomeMessage(); + ui.getInfo(); + if (ui.memoryStartup()) { + meal.meals = storage.loadMeals(); + fluid.fluidArray = storage.loadFluids(); + weightTracker.weightsArray = storage.loadWeights(); + FoodBank.meals = storage.loadMealLibrary(); + FoodBank.fluids = storage.loadFluidLibrary(); + workoutTracker.workouts = storage.loadWorkouts(); + scheduleTracker.loadScheduleData(); + storage.printLoadedLists(); + System.out.println(ClickfitMessages.INITIAL_PROMPT); + } + commandManager = new CommandManager(storage, fluid, + meal, scheduleTracker, workoutTracker, + weightTracker, userHelp); + } catch (LoadException e) { + System.out.println(MEMORY_STARTUP_INCORRECT_INPUT); + } catch (IOException e) { + System.out.println(IO_EXCEPTION_MESSAGE); + } + } - Scanner in = new Scanner(System.in); - System.out.println("Hello " + in.nextLine()); + public void run() { + while (!commandManager.isExit) { + try { + System.out.println(Ui.HORIZONTAL_BAR_LONG); + System.out.print(Ui.USER_PROMPT); + commandManager.commandChecker(); + } catch (ScheduleException se) { + System.out.println(se.getMessage()); + } catch (WorkoutException we) { + System.out.println(we.getMessage()); + } catch (MealException e) { + System.out.println(e.getMessage()); + } catch (FluidExceptions e) { + System.out.println(e.getMessage()); + } catch (FoodBankException e) { + System.out.println(e.getMessage()); + } catch (IOException e) { + System.out.println(ClickfitMessages.IO_EXCEPTION_MESSAGE); + } catch (WeightException weighte) { + System.out.println(weighte.getMessage()); + } catch (DateTimeParseException e) { + System.out.println(ClickfitMessages.DATE_TIME_ERROR); + } catch (NumberFormatException e) { + System.out.println(ClickfitMessages.NUMBER_ERROR); + } catch (NullPointerException e) { + System.out.println(ClickfitMessages.INCORRECT_INPUT); + } catch (DukeException e) { + System.out.println("I'm sorry"); + } catch (Exception e) { + System.out.println(ClickfitMessages.UNKNOWN_ERROR_MESSAGE); + } + } + LogManager.getLogManager().reset(); } } diff --git a/src/main/java/seedu/duke/Fluid.java b/src/main/java/seedu/duke/Fluid.java new file mode 100644 index 0000000000..d80a64506b --- /dev/null +++ b/src/main/java/seedu/duke/Fluid.java @@ -0,0 +1,264 @@ +package seedu.duke; + +import seedu.duke.exceptions.fluid.NoCaloriesEntered; +import seedu.duke.exceptions.fluid.NoVolumeEntered; +import seedu.duke.exceptions.foodbank.FoodBankException; +import seedu.duke.exceptions.fluid.DeleteEmptyFluidListException; +import seedu.duke.exceptions.fluid.FluidExceptions; +import seedu.duke.exceptions.fluid.InvalidFluidDescription; +import seedu.duke.exceptions.fluid.NoDeleteFluidIndexException; +import seedu.duke.exceptions.fluid.NoFluidToDelete; + +import java.util.ArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +//@@author pragyan01 +/** + * Class responsible for handling fluid entries. + * + * @author pragyan01 + */ +public class Fluid extends Tracker { + + protected ArrayList fluidArray; + protected int fluidNumber; + protected String description; + protected int calories; + protected int volume; + protected String date; + protected String time; + protected int totalCalories; + protected int totalVolume; + private static final Logger logr = Logger.getLogger("FluidLogger"); + + /** + * Constructor of fluid class. + * + */ + public Fluid() { + this.fluidArray = new ArrayList<>(); + this.fluidNumber = 0; + this.totalCalories = 0; + logr.setLevel(Level.SEVERE); + } + + /** + * This method splits user input to extract parameters such as description, calories, volume, date & time. + * + * @param inputArguments user input provided + * @throws FoodBankException if calories are not provided + * @throws FluidExceptions if description not provided + * + * @author pragyan01 + */ + public void generateFluidParameters(String inputArguments) throws FoodBankException, FluidExceptions { + logr.entering(getClass().getName(), "generateFluidParameters"); + logr.info("start of generating fluid parameters"); + description = Parser.getDescription(inputArguments); + calories = Parser.getCalories(inputArguments); + volume = Parser.getVolume(inputArguments); + date = Parser.getDate(inputArguments); + time = Parser.getTime(inputArguments); + logr.info("end of process-generateFluidParameters"); + logr.exiting(getClass().getName(), "generateFluidParameters"); + } + + /** + * This method adds a fluid entry to the fluid array. + * + * @param inputArguments user input provided + * @throws FluidExceptions if description or volume is not provided + * @throws FoodBankException if calories are not provided + * + * @author pragyan01 + */ + public void addFluid(String inputArguments) throws FluidExceptions, FoodBankException { + logr.entering(getClass().getName(), "addFluid"); + logr.info("going to check for fluid parameter errors"); + if (inputArguments == null) { + logr.info("error adding fluid: invalidDescription"); + throw new InvalidFluidDescription(); + } + generateFluidParameters(inputArguments); + if (description.equals("") || Parser.containsSeparators(description)) { + logr.info("error adding fluid: invalidDescription"); + throw new InvalidFluidDescription(); + } + if ((volume < 0) || !inputArguments.contains("/v")) { + logr.info("error adding fluid: no volume provided"); + throw new NoVolumeEntered(); + } + if ((calories < 0)) { + logr.info("error adding fluid: no calories provided"); + throw new NoCaloriesEntered(); + } + logr.info("no fluid parameter errors found!"); + inputArguments = description + " /c " + calories + " /v " + volume + " /d " + date + " /t " + time; + fluidArray.add(inputArguments); + assert fluidArray.size() != 0 : "Fluid array should not be empty"; + logr.info("fluid intake has been added"); + fluidNumber += 1; + totalCalories += calories; + totalVolume += volume; + logr.info("fluidNumber, totalCalories & totalVolume have been updated"); + System.out.println("Noted! CLI.ckFit has recorded your drink of " + description + " of " + calories + + " calories and " + volume + " ml on " + date + " " + time + "." + "\n"); + logr.info("end of process-addFluid"); + logr.exiting(getClass().getName(), "addFluid"); + } + + /** + * This method deletes a fluid entry from the fluid array. + * + * @param inputArguments user input provided + * @throws FluidExceptions if fluid entry index is not provided + * @throws FoodBankException if the fluid entry does not exist + * + * @author pragyan01 + */ + public void deleteFluid(String inputArguments) throws FoodBankException, FluidExceptions { + logr.entering(getClass().getName(), "deleteFluid"); + logr.info("going to check for index errors"); + if (inputArguments == null) { + logr.info("error deleting fluid: invalid index provided"); + throw new NoDeleteFluidIndexException(); + } + if (fluidArray.size() == 0) { + logr.info("error deleting fluid: no fluid entries exist"); + throw new DeleteEmptyFluidListException(); + } + int taskNumber = Parser.parseStringToInteger(inputArguments) - 1; + logr.info("taskNumber extracted from user input"); + if ((taskNumber < 0) || (taskNumber > (fluidNumber - 1))) { + logr.info("error deleting fluid: specified fluid entry does not exist"); + throw new NoFluidToDelete(); + } + generateFluidParameters(fluidArray.get(taskNumber)); + assert fluidArray.size() != 0 : "Fluid array should not be empty"; + fluidArray.remove(taskNumber); + logr.info("fluid entry has been removed"); + fluidNumber -= 1; + totalCalories -= calories; + totalVolume -= volume; + logr.info("fluidNumber, totalCalories & totalVolume have been updated"); + System.out.println("Noted! CLI.ckFit has deleted your drink of " + description + " of " + calories + + " calories and " + volume + " ml on " + date + " " + time + "." + "\n"); + logr.info("end of process-deleteFluid"); + logr.exiting(getClass().getName(), "deleteFluid"); + } + + /** + * This method lists out all fluid entries stored. + * + *@param userDate date provided by user + *@throws FluidExceptions if description for a fluid entry is not found + *@throws FoodBankException if calories for a fluid entry is not found + * + * @author pragyan01 + */ + public void listFluids(String userDate) throws FoodBankException, FluidExceptions { + logr.entering(getClass().getName(), "listFluid"); + if (fluidArray.size() == 0) { + logr.info("error listing fluids: fluid list is empty"); + System.out.println(ClickfitMessages.EMPTY_FLUID_LIST); + } + logr.info("checking if specific date is provided by user or all entries are to be printed"); + if (userDate.equals("all")) { + logr.info("all entries are to be printed"); + int i = 1; + totalCalories = 0; + fluidNumber = 0; + logr.info("totalCalories & fluidNumber have been reset"); + for (String fluid : fluidArray) { + generateFluidParameters(fluid); + System.out.println(i + ". " + description); + System.out.println("Calories: " + calories); + System.out.println("Volume: " + volume); + System.out.println("Date: " + date); + System.out.println("Time: " + time + "\n"); + i += 1; + totalCalories += calories; + fluidNumber += 1; + } + logr.info("all entries have been listed"); + } else { + logr.info("fluid entries are to be listed for a specific date"); + int i = 1; + totalCalories = 0; + fluidNumber = 0; + logr.info("totalCalories & fluidNumber have been reset"); + for (String fluid : fluidArray) { + if (fluid.contains(userDate)) { + logr.log(Level.INFO, "generating fluid parameters"); + generateFluidParameters(fluid); + logr.log(Level.INFO, "fluid parameters generated"); + System.out.println(i + ". " + description); + System.out.println("Calories: " + calories); + System.out.println("Volume: " + volume); + System.out.println("Date: " + date); + System.out.println("Time: " + time + "\n"); + i += 1; + totalCalories += calories; + fluidNumber += 1; + } + } + logr.info("fluid entries have been listed for a specific date"); + } + System.out.println("Total number of fluids: " + fluidNumber); + System.out.println("Total calories: " + totalCalories); + logr.info("fluidNumber and totalCalories have been updated and printed"); + logr.log(Level.INFO, "end of process-listFluid"); + logr.exiting(getClass().getName(), "listFluids"); + } + + /** + * This method sums up the calorie total for a specific date. + * + *@param date date provided by user + *@return total calorie for the specific date + *@throws FluidExceptions if description for a fluid entry is not found + *@throws FoodBankException if calories for a fluid entry is not found + */ + public int getCalories(String date) throws FoodBankException, FluidExceptions { + logr.entering(getClass().getName(), "getCalories"); + int calorieTotal = 0; + logr.log(Level.INFO, "calorieTotal has been reset"); + for (String fluid : fluidArray) { + if (fluid.contains(date)) { + generateFluidParameters(fluid); + calorieTotal += calories; + assert ((calorieTotal > 0) || (calorieTotal == 0)); + } + } + logr.log(Level.INFO, "calorieTotal for specific date has been updated and printed"); + logr.log(Level.INFO, "end of process-getCalories"); + logr.exiting(getClass().getName(), "getCalories"); + return calorieTotal; + } + + /** + * This method sums up the volume total for a specific date. + * + *@param date date provided by user + *@return total volume for the specific date + *@throws FluidExceptions if description for a fluid entry is not found + *@throws FoodBankException if calories for a fluid entry is not found + */ + public int getVolume(String date) throws FoodBankException, FluidExceptions { + logr.entering(getClass().getName(), "getVolume"); + int volumeTotal = 0; + logr.log(Level.INFO, "volumeTotal has been reset"); + for (String fluid : fluidArray) { + if (fluid.contains(date)) { + generateFluidParameters(fluid); + volumeTotal += volume; + assert ((volumeTotal > 0) || (volumeTotal == 0)); + } + } + logr.log(Level.INFO, "volumeTotal for specific date has been updated and printed"); + logr.log(Level.INFO, "end of process-getVolume"); + logr.exiting(getClass().getName(), "getVolume"); + return volumeTotal; + } +} \ No newline at end of file diff --git a/src/main/java/seedu/duke/FoodBank.java b/src/main/java/seedu/duke/FoodBank.java new file mode 100644 index 0000000000..be65e90e9d --- /dev/null +++ b/src/main/java/seedu/duke/FoodBank.java @@ -0,0 +1,255 @@ +package seedu.duke; + +import seedu.duke.exceptions.foodbank.DuplicateFood; +import seedu.duke.exceptions.foodbank.EmptyFluidBankException; +import seedu.duke.exceptions.foodbank.EmptyFoodDescription; +import seedu.duke.exceptions.foodbank.EmptyLibraryDescription; +import seedu.duke.exceptions.foodbank.EmptyMealBankException; +import seedu.duke.exceptions.foodbank.FoodBankException; +import seedu.duke.exceptions.foodbank.InvalidFluidIndexException; +import seedu.duke.exceptions.foodbank.InvalidMealIndexException; +import seedu.duke.exceptions.foodbank.NoFoodFoundException; +import seedu.duke.exceptions.foodbank.NoFoodIndexException; + +import java.util.ArrayList; + +public class FoodBank { + + protected static ArrayList meals; + protected static ArrayList fluids; + protected static int calories; + protected static String description; + protected static int totalMeals; + protected static int totalFluids; + + //@@author VishalJeyaram + /** + * Constructor for FoodBank class used to initialize the instance variables + * such as the arraylist to store the library meals, the arraylist to store + * the library fluids, two integer variables to store the number of library + * meals and the number of library fluids respectively. + */ + public FoodBank() { + meals = new ArrayList<>(); + fluids = new ArrayList<>(); + totalMeals = 0; + totalFluids = 0; + } + + //@@author pragyan01 + /** + * This method splits user input to extract parameters such as description and calories. + * + * @param inputArguments user input provided + * @throws FoodBankException if calories are not provided + * + * @author pragyan01 + */ + public static void generateParameters(String inputArguments) throws FoodBankException { + calories = Parser.getCalories(inputArguments); + description = Parser.getDescription(inputArguments); + } + + //@@author pragyan01 + /** + * This method adds a fluid entry to the fluid library. + * + * @param inputArguments user input provided + * @throws FoodBankException if user input is null + * + * @author pragyan01 + */ + public static void addCustomFluid(String inputArguments) throws FoodBankException { + totalFluids = fluids.size(); + if (inputArguments == null) { + throw new EmptyLibraryDescription(); + } + try { + generateParameters(inputArguments); + } catch (EmptyFoodDescription | NoFoodFoundException e) { + throw new EmptyLibraryDescription(); + } + inputArguments = description + " /c " + calories; + for (String f : fluids) { + if (f.contains(description)) { + throw new DuplicateFood(); + } + } + fluids.add(inputArguments); + totalFluids += 1; + System.out.println(description + ", which has " + calories + + " calories, will be added to your library of fluids." + + " You now have " + totalFluids + " fluids!\n"); + } + + //@@author pragyan01 + /** + * This method deletes a fluid entry from the fluid library. + * + * @param inputArguments user input provided + * @throws FoodBankException if the fluid entry index is invalid or not provided + * + * @author pragyan01 + */ + public static void deleteCustomFluids(String inputArguments) throws FoodBankException { + totalFluids = fluids.size(); + if (inputArguments == null) { + throw new NoFoodIndexException(); + } + if (fluids.size() == 0) { + throw new EmptyFluidBankException(); + } + int fluidIndex = Parser.parseStringToInteger(inputArguments) - 1; + if ((fluidIndex < 0) || (fluidIndex > (fluids.size() - 1))) { + throw new InvalidFluidIndexException(); + } + generateParameters(fluids.get(fluidIndex)); + fluids.remove(fluidIndex); + totalFluids -= 1; + System.out.println(description + " will be removed from your list of fluids consumed." + + " You now have " + totalFluids + " fluids left!\n"); + } + + //@@author pragyan01 + /** + * This method lists out all fluid entries stored in the food library. + * + *@throws FoodBankException if calories for a fluid entry is not found + * + * @author pragyan01 + */ + public static void listCustomFluids() throws FoodBankException { + if (fluids.size() == 0) { + System.out.println(ClickfitMessages.EMPTY_FLUID_LIBRARY); + } + int i = 1; + for (String fluid : fluids) { + generateParameters(fluid); + System.out.println(i + ". " + description); + System.out.println("Calories: " + calories + "\n"); + i += 1; + } + System.out.println("Total number of fluids in library: " + totalFluids); + } + + //@@author VishalJeyaram + /** + * Lists out all the library meals. + * + * @throws FoodBankException if user's food is already within the meal library. + */ + public static void listCustomMeal() throws FoodBankException { + if (meals.size() == 0) { + System.out.println("Your meals library is empty!"); + } + int i = 1; + for (String meal : meals) { + generateParameters(meal); + ClickfitMessages.printSingleLibraryMeal(i,description,calories); + i += 1; + } + ClickfitMessages.printMealLibraryTotals(totalMeals); + } + + //@@author VishalJeyaram + + /** + * Adds a custom meal with its associated calories to the meal library. + * + * @throws FoodBankException If the meal description is empty, or if the meal already exists + * within the library. + */ + public static void addCustomMeal(String inputArguments) throws FoodBankException { + totalMeals = meals.size(); + if (inputArguments == null) { + throw new EmptyLibraryDescription(); + } + try { + generateParameters(inputArguments); + } catch (EmptyFoodDescription | NoFoodFoundException e) { + throw new EmptyLibraryDescription(); + } + for (String m : meals) { + if (m.contains(description)) { + throw new DuplicateFood(); + } + } + inputArguments = description + Parser.CALORIE_SEPARATOR + calories; + meals.add(inputArguments); + totalMeals += 1; + ClickfitMessages.printAddedLibraryMeal(description,calories,totalMeals); + } + + //@@author VishalJeyaram + + /** + * Deletes a custom meal with its associated calories from the meal library. + * + * @throws NumberFormatException If the meal index is not an integer value. + * @throws FoodBankException If meal index is not keyed in, or if the meal library is empty, + * or if the meal index is out of range + */ + public static void deleteCustomMeal(String inputArguments) throws NumberFormatException, FoodBankException { + totalMeals = meals.size(); + if (inputArguments == null) { + throw new NoFoodIndexException(); + } + if (meals.size() == 0) { + throw new EmptyMealBankException(); + } + int mealIndex = Parser.parseStringToInteger(inputArguments) - 1; + if ((mealIndex < 0) || (mealIndex > (meals.size() - 1))) { + throw new InvalidMealIndexException(); + } + generateParameters(meals.get(mealIndex)); + meals.remove(mealIndex); + totalMeals -= 1; + ClickfitMessages.printDeletedLibraryMeal(description,totalMeals); + } + + //@@author pragyan01 + /** + * This method finds calories associated with a specific food entry. + * @return calories associated with the specific food entry + * @throws FoodBankException if specified food entry + */ + public static int findCalories(String name) throws FoodBankException { + for (String meal : meals) { + generateParameters(meal); + if (description.equals(name)) { + return calories; + } + } + for (String fluid : fluids) { + generateParameters(fluid); + if (description.equals(name)) { + return calories; + } + } + throw new NoFoodFoundException(); + } + + //@@author pragyan01 + /** + * This method checks if specified food exists in food bank. + * + * @param name food name specified by user + * @return true if food specified is found in food bank, false otherwise. + * @throws FoodBankException if parameters cannot be generated for food entries stored in food bank. + */ + public static boolean isFoodFound(String name) throws FoodBankException { + for (String meal : meals) { + generateParameters(meal); + if (description.equals(name)) { + return true; + } + } + for (String fluid : fluids) { + generateParameters(fluid); + if (description.equals(name)) { + return true; + } + } + return false; + } +} diff --git a/src/main/java/seedu/duke/Keywords.java b/src/main/java/seedu/duke/Keywords.java new file mode 100644 index 0000000000..b9d00290d7 --- /dev/null +++ b/src/main/java/seedu/duke/Keywords.java @@ -0,0 +1,31 @@ +package seedu.duke; + +//@@author { ALL} +public class Keywords { + public static final String INPUT_ADD_WORKOUT = "workout"; + public static final String DELETE_DRINKS = "deletefluid"; + public static final String LIST_DRINKS = "listfluids"; + public static final String INPUT_ADD_WEIGHT = "addweight"; + public static final String INPUT_BYE = "bye"; + public static final String DELETE_MEAL = "deletemeal"; + public static final String LIST_MEAL = "listmeals"; + public static final String INPUT_HELP = "help"; + public static final String ADD_FLUID = "addfluid"; + public static final String ADD_MEAL = "addmeal"; + public static final String LIBRARY = "library"; + public static final String LIST = "list"; + public static final String MEALS = "meals"; + public static final String FLUIDS = "fluids"; + public static final String CALORIES = "calories"; + public static final String VOLUME = "volumes"; + public static final String WEIGHTS = "weights"; + public static final String WORKOUTS = "workouts"; + public static final String WORKOUT = "workout"; + public static final String SCHEDULE = "schedule"; + public static final String ADD = "add"; + public static final String MEAL = "meal"; + public static final String FLUID = "fluid"; + public static final String DELETE = "delete"; + public static final String WEIGHT = "weight"; + public static final String ALL = "all"; +} \ No newline at end of file diff --git a/src/main/java/seedu/duke/Meal.java b/src/main/java/seedu/duke/Meal.java new file mode 100644 index 0000000000..8329c7d66a --- /dev/null +++ b/src/main/java/seedu/duke/Meal.java @@ -0,0 +1,216 @@ +package seedu.duke; + +import seedu.duke.exceptions.foodbank.EmptyFoodDescription; +import seedu.duke.exceptions.foodbank.FoodBankException; +import seedu.duke.exceptions.meal.EmptyMealListException; +import seedu.duke.exceptions.meal.MealException; +import seedu.duke.exceptions.meal.NoDeleteMealIndexException; +import seedu.duke.exceptions.meal.NoMealDetailsException; +import seedu.duke.exceptions.meal.NoSuchMealIndexException; +import java.time.format.DateTimeParseException; +import java.util.ArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +//@@author VishalJeyaram +/** + * Class used to perform actions related to meals such as adding, + * deleting and listing meals. + */ +public class Meal extends Tracker { + protected ArrayList meals; + protected int mealNumber; + protected int calories; + protected String description; + protected String date; + protected String time; + protected int totalCalories; + private static Logger logger = Logger.getLogger("MealTrackerLogger"); + + /** + * Constructor for Meal class used to initialize the instance variables + * such as the arraylist to store the meals, and two integer variables + * to store the number of meals, and the number of calories respectively. + */ + public Meal() { + this.meals = new ArrayList<>(); + this.mealNumber = 0; + this.totalCalories = 0; + logger.setLevel(Level.SEVERE); + } + + /** + * Extracts the calories, description, date, and time for a particular + * user input. + * + * @param inputArguments User input. + * @throws FoodBankException if user's meal is already within the meal library. + * @throws MealException if no meal description is in input. + */ + public void generateMealParameters(String inputArguments) throws FoodBankException { + this.calories = Parser.getCalories(inputArguments); + this.description = Parser.getDescription(inputArguments); + this.date = Parser.getDate(inputArguments); + this.time = Parser.getTime(inputArguments); + } + + /** + * Adds a meal to list of meals. + * + * @param inputArguments User input. + * @throws DateTimeParseException If date and/or time is not keyed in properly. + * @throws NumberFormatException If the calories is not an integer value. + * @throws MealException If no meal description is in the input. + * @throws FoodBankException If user's meal is already within the meal library. + */ + public void addMeal(String inputArguments) + throws DateTimeParseException, NumberFormatException, MealException, FoodBankException { + logger.entering(getClass().getName(), "addMeal"); + if (inputArguments == null) { + throw new NoMealDetailsException(); + } + mealNumber = meals.size(); + logger.log(Level.INFO, "generating meal parameters"); + generateMealParameters(inputArguments); + logger.log(Level.INFO, "meal parameters generated"); + if (Parser.containsSeparators(description)) { + throw new EmptyFoodDescription(); + } + String input = combineMealParameters(); + meals.add(input); + mealNumber += 1; + ClickfitMessages.printAddedMealMessage(description,date,time,calories); + logger.log(Level.INFO, "meal has been added to meal list"); + logger.exiting(getClass().getName(), "addMeal"); + } + + /** + * Combines multiple parameters into single input argument to be added to meal arraylist. + * + * @return inputArguments. + */ + public String combineMealParameters() { + String inputArguments; + inputArguments = description + Parser.CALORIE_SEPARATOR + calories + + Parser.DATE_SEPARATOR + date + Parser.TIME_SEPARATOR + time; + return inputArguments; + } + + /** + * Deletes a meal from the list of meals. + * + * @param inputArguments User input. + * @throws NumberFormatException If the meal index is not an integer value. + * @throws MealException If meal list is empty or if meal index keyed in by user does not exist. + * @throws FoodBankException If user's meal is already within the meal library + */ + public void deleteMeal(String inputArguments) + throws NumberFormatException, FoodBankException, MealException { + logger.entering(getClass().getName(), "deleteMeal"); + if (inputArguments == null) { + throw new NoDeleteMealIndexException(); + } + mealNumber = meals.size(); + if (meals.size() == 0) { + throw new EmptyMealListException(); + } + int mealIndex = Parser.parseStringToInteger(inputArguments) - 1; + if ((mealIndex < 0) || (mealIndex > (mealNumber - 1))) { + throw new NoSuchMealIndexException(); + } + logger.log(Level.INFO, "generating meal parameters"); + generateMealParameters(meals.get(mealIndex)); + logger.log(Level.INFO, "meal parameters generated"); + meals.remove(mealIndex); + mealNumber -= 1; + logger.log(Level.INFO, "meal has been deleted from meal list"); + ClickfitMessages.printDeletedMealMessage(description, date); + logger.exiting(getClass().getName(), "deleteMeal"); + } + + /** + * Lists meals from the list of meals depending on the date. + * + * @param userDate Date on which to list meals. + * @throws MealException If meal list is empty or if meal index keyed in by user does not exist. + * @throws FoodBankException If user's food is already within the meal library + */ + public void listMeals(String userDate) throws FoodBankException, MealException { + if (meals.size() == 0) { + ClickfitMessages.printEmptyMealList(); + } + mealNumber = 0; + totalCalories = 0; + if (userDate.equals(Keywords.ALL)) { + listAllMeals(); + } else { + listMealsByDate(userDate); + } + ClickfitMessages.printMealListTotals(mealNumber, totalCalories); + logger.log(Level.INFO, "meal list printed"); + logger.exiting(getClass().getName(), "listMeal"); + } + + /** + * Lists all meals from the list of meals on a specific date. + * + * @param userDate Date on which to list meals. + * @throws MealException If meal list is empty or if meal index keyed in by user does not exist. + * @throws FoodBankException If user's food is already within the meal library + */ + private void listMealsByDate(String userDate) throws FoodBankException, MealException { + logger.entering(getClass().getName(), "listMeals"); + int i = 1; + logger.log(Level.INFO, "entering for loop"); + for (String meal : meals) { + if (meal.contains(userDate)) { + logger.log(Level.INFO, "generating meal parameters"); + generateMealParameters(meal); + logger.log(Level.INFO, "meal parameters generated"); + ClickfitMessages.printSingleMealDetails(i,description,calories,date,time); + i += 1; + totalCalories += calories; + mealNumber += 1; + } + } + } + + /** + * Lists all meals from the list of meals on all days. + * + * @throws MealException If meal list is empty or if meal index keyed in by user does not exist. + * @throws FoodBankException If user's food is already within the meal library + */ + private void listAllMeals() throws FoodBankException, MealException { + int i = 1; + for (String meal : meals) { + logger.log(Level.INFO, "generating meal parameters"); + generateMealParameters(meal); + logger.log(Level.INFO, "meal parameters generated"); + ClickfitMessages.printSingleMealDetails(i,description,calories,date,time); + i += 1; + totalCalories += calories; + mealNumber += 1; + } + } + + /** + * Returns total calories from meals consumed from the list of meals depending on the date. + * + * @param date Date on which to list meals. + * @return Lateral location. + * @throws MealException If meal list is empty or if meal index keyed in by user does not exist. + * @throws FoodBankException If user's food is already within the meal library + */ + public int getCalories(String date) throws FoodBankException, MealException { + int calorieTotal = 0; + for (String meal : meals) { + if (meal.contains(date)) { + generateMealParameters(meal); + calorieTotal += calories; + } + } + return calorieTotal; + } +} + diff --git a/src/main/java/seedu/duke/Parser.java b/src/main/java/seedu/duke/Parser.java new file mode 100644 index 0000000000..7af1eef70b --- /dev/null +++ b/src/main/java/seedu/duke/Parser.java @@ -0,0 +1,557 @@ +package seedu.duke; + +import seedu.duke.exceptions.DukeException; +import seedu.duke.exceptions.fluid.FluidExceptions; +import seedu.duke.exceptions.fluid.NegativeVolumeException; +import seedu.duke.exceptions.foodbank.EmptyFoodDescription; +import seedu.duke.exceptions.foodbank.FoodBankException; +import seedu.duke.exceptions.foodbank.NegativeCaloriesException; +import seedu.duke.exceptions.foodbank.NoFoodFoundException; +import seedu.duke.exceptions.schedule.GetActivityException; +import seedu.duke.exceptions.schedule.InvalidActivityFormatException; +import seedu.duke.exceptions.schedule.InvalidScheduleDescriptionException; +import seedu.duke.exceptions.schedule.MissingActivityQuantifierException; +import seedu.duke.exceptions.schedule.MissingActivitySplitterException; +import seedu.duke.exceptions.schedule.UnnecessaryQuantifierException; +import seedu.duke.exceptions.workout.MissingWorkoutCalorieSeparatorException; +import seedu.duke.exceptions.workout.NegativeWorkoutCalorieException; +import seedu.duke.exceptions.schedule.ScheduleException; +import seedu.duke.exceptions.workout.WorkoutException; +import seedu.duke.schedule.WorkoutActivity; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +public class Parser { + + public static final String DATE_SEPARATOR = " /d "; + public static final String TIME_SEPARATOR = " /t "; + public static final String CALORIE_SEPARATOR = " /c "; + public static final String RECURRING_FLAG = " /r"; + public static final String VOLUME_SEPARATOR = " /v "; + public static final String ACTIVITY_SEPARATOR = " /a "; + public static final String MULTIPLE_ACTIVITY_MARKER = ","; + public static final String ACTIVITY_SPLITTER = ":"; + public static final String QUANTIFIER_SPLITTER = "x"; + public static final String EMPTY_STRING = ""; + + //@@author teoziyiivy + /** + * Checks if the user's input contains the date separator ` /d `. + * + * @param inputArguments User's input. + * @return true, if input contains date separator, and false, if input does not contain date separator. + */ + public static boolean containsDateSeparator(String inputArguments) { + return inputArguments.contains(DATE_SEPARATOR); + } + + //@@author teoziyiivy + /** + * Checks if the user's input contains time separator ` /t `. + * + * @param inputArguments User's input. + * @return true, if input contains time separator, and false, if input does not contain time separator. + */ + public static boolean containsTimeSeparator(String inputArguments) { + return inputArguments.contains(TIME_SEPARATOR); + } + + //@@author teoziyiivy + /** + * Checks if the user's input contains calorie separator ' /c '. + * + * @param inputArguments User's input. + * @return true, if input contains calorie separator, and false, if input does not contain calorie separator. + */ + public static boolean containsCalorieSeparator(String inputArguments) { + return inputArguments.contains(CALORIE_SEPARATOR); + } + + //@@author pragyan01 + /** + * Checks if the user's input contains volume separator ' /v '. + * + * @param inputArguments User's input. + * @return true, if input contains calorie separator, and false, if input does not contain calorie separator. + */ + public static boolean containsVolumeSeparator(String inputArguments) { + return inputArguments.contains(VOLUME_SEPARATOR); + } + + //@@author EdwardZYWang + /** + * Checks if the user's input contains recurring workout flag ' /r'. + * + * @param inputArguments User's input. + * @return true, if input contains recurring workout flag, and false otherwise. + */ + public static boolean isRecurringWorkout(String inputArguments) { + String[] splitResults = inputArguments.split(RECURRING_FLAG, 2); + if (splitResults.length == 1) { + return false; + } + return splitResults[1].isEmpty(); // true if /r flag is at the end of the string + } + + //@@author teoziyiivy + /** + * Converts user's String input to Integer. + * + * @param inputArguments User's input. + * @return Integer.parseInt(inputArguments) User's input as Integer. + * @throws NumberFormatException If user's input is not a valid Integer. + */ + public static int parseStringToInteger(String inputArguments) throws NumberFormatException { + return Integer.parseInt(inputArguments); + } + + //@@author teoziyiivy + /** + * Converts user's String input to Double. + * + * @param inputArguments User's input. + * @return Double.parseDouble(inputArguments) User's input as Double. + * @throws NumberFormatException If user's input is not a valid Double. + */ + public static double parseStringToDouble(String inputArguments) throws NumberFormatException { + return Double.parseDouble(inputArguments); + } + + //@@author VishalJeyaram + /** + * Checks if the user's input contains separators. + * + * @param inputArguments User's input. + * @return true, if input contains separators, and false, if input does not contain separators. + */ + public static boolean containsSeparators(String inputArguments) { + if (inputArguments.contains(CALORIE_SEPARATOR.trim())) { + return true; + } else if (inputArguments.contains(DATE_SEPARATOR.trim())) { + return true; + } else if (inputArguments.contains(TIME_SEPARATOR.trim())) { + return true; + } else { + return false; + } + } + + //@@author VishalJeyaram + /** + * Returns calories extracted from user's input or from meal or fluid library. + * + * @param inputArguments User's input. + * @return Calories. + * @throws DukeException If the user's description is empty. + * @throws NumberFormatException If calories is not an integer value. + * @throws FoodBankException If food already exists within either meal or fluid library. + */ + public static int getCalories(String inputArguments) + throws NumberFormatException, FoodBankException { + String description = getDescription(inputArguments); + if (!containsCalorieSeparator(inputArguments)) { + return findCaloriesInLibrary(description); + } else { + return extractCalories(inputArguments); + } + } + + //@@author VishalJeyaram + /** + * Returns calories from user's input. + * + * @param inputArguments User's input. + * @return Calories. + * @throws NumberFormatException If calories is not an integer value. + * @throws FoodBankException If the user has keyed in negative calories. + */ + private static int extractCalories(String inputArguments) throws FoodBankException { + int calories = 0; + String[] userInput = inputArguments.split("\\s+"); + int length = userInput.length; + for (int i = 1; i < length; i++) { + if (userInput[i].equals(CALORIE_SEPARATOR.trim())) { + calories = parseStringToInteger(userInput[i + 1]); + } + } + if (calories < 0) { + throw new NegativeCaloriesException(); + } else { + return calories; + } + } + + //@@author VishalJeyaram + /** + * Returns calories from meal or fluid library. + * + * @param description Name of Food + * @return Calories. + * @throws FoodBankException If the user hasn't keyed in a description for their food, or if their + * meal or fluid is not stored in the meal or fluid library. + */ + private static int findCaloriesInLibrary(String description) throws FoodBankException { + int calories; + if (Parser.containsSeparators(description)) { + throw new EmptyFoodDescription(); + } else if (FoodBank.isFoodFound(description)) { + calories = FoodBank.findCalories(description); + return calories; + } else { + throw new NoFoodFoundException(); + } + } + + //@@author EdwardZYWang + /** + * gets calories burned through workout from the user's inputs. + * + * @param inputArguments arguments of the user's inputs. + * @return calories burned for workout parameter + * @throws WorkoutException if there is an unknown error encountered. + */ + public static int getCaloriesBurnedForWorkout(String inputArguments) throws WorkoutException { + int calories = 0; + boolean isCaloriesParsed = false; + String[] userInput = inputArguments.split("\\s+"); + int length = userInput.length; + for (int i = 0; i < length; i++) { + if (userInput[i].equals(CALORIE_SEPARATOR.trim())) { + try { + calories = parseStringToInteger(userInput[i + 1]); + isCaloriesParsed = true; + break; + } catch (IndexOutOfBoundsException e) { + throw new NumberFormatException(); + } + } + } + if (!isCaloriesParsed) { + throw new MissingWorkoutCalorieSeparatorException(); + } + if (calories < 0) { + throw new NegativeWorkoutCalorieException(); + } else { + return calories; + } + } + + //@@author pragyan01 + /** + * This method extracts volume parameter from user input. + * + *@param inputArguments user input provided + *@return volume parameter + *@throws DukeException if volume entered is negative + */ + public static int getVolume(String inputArguments) throws FluidExceptions { + String[] userInput = inputArguments.split("\\s+"); + int length = userInput.length; + int volume = 0; + for (int i = 1; i < length; i++) { + if (userInput[i].equals(VOLUME_SEPARATOR.trim())) { + volume = parseStringToInteger(userInput[i + 1]); + break; + } + } + if (volume < 0) { + throw new NegativeVolumeException(); + } + return volume; + } + + //@@author pragyan01 + /** + * This method extracts description parameter from user input. + * + *@param inputArguments user input provided + *@return description parameter + * + *@author pragyan01 + */ + public static String getDescription(String inputArguments) { + String[] userInput; + if (containsCalorieSeparator(inputArguments)) { + userInput = inputArguments.split(CALORIE_SEPARATOR); + } else if (containsDateSeparator(inputArguments)) { + userInput = inputArguments.split(DATE_SEPARATOR); + } else if (containsTimeSeparator(inputArguments)) { + userInput = inputArguments.split(TIME_SEPARATOR); + } else if (containsVolumeSeparator(inputArguments)) { + userInput = inputArguments.split(VOLUME_SEPARATOR); + } else { + return inputArguments; + } + String description = userInput[0].trim(); + return description; + } + + //@@author VishalJeyaram + /** + * Returns date extracted from user's input. + * + * @param inputArguments User's input. + * @return Date. + * @throws DateTimeParseException If the date is not entered properly. + */ + public static String getDate(String inputArguments) throws DateTimeParseException { + String[] userInput = inputArguments.split("\\s+"); + int length = userInput.length; + String date = ""; + for (int i = 1; i < length; i++) { + if (userInput[i].equals(DATE_SEPARATOR.trim())) { + date = userInput[i + 1]; + break; + } + } + if (date.equals("")) { + String newDate = getSystemDate(); + DateTracker.checkIfDateExists(newDate); + return newDate; + } + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy"); + LocalDate localDate = LocalDate.parse(date, formatter); + DateTracker.checkIfDateExists(formatter.format(localDate)); + return formatter.format(localDate); + } + + //@@author teoziyiivy + /** + * Returns date extracted from user's input to be used for comparisons without the need + * for tracking. + * + * @param inputArguments User's input. + * @return String Date that user input. + * @throws DateTimeParseException If the date is not entered properly. + */ + public static String getDateNoDateTracker(String inputArguments) throws DateTimeParseException { + String[] userInput = inputArguments.split("\\s+"); + int length = userInput.length; + String date = EMPTY_STRING; + for (int i = 1; i < length; i++) { + if (userInput[i].equals(DATE_SEPARATOR.trim())) { + date = userInput[i + 1]; + break; + } + } + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy"); + LocalDate localDate = LocalDate.parse(date, formatter); + return formatter.format(localDate); + } + + //@@author teoziyiivy + /** + * Extracts the time from the user input. + * + * @param inputArguments User input. + * @return String Time that user input. + * @throws DateTimeParseException If time is not valid. + */ + public static String getTime(String inputArguments) throws DateTimeParseException { + String[] userInput = inputArguments.split("\\s+"); + int length = userInput.length; + String time = ""; + for (int i = 1; i < length; i++) { + if (userInput[i].equals(TIME_SEPARATOR.trim())) { + time = userInput[i + 1]; + break; + } + } + if (time.equals("")) { + time = getSystemTime(); + } + LocalTime localTime = LocalTime.parse(time); + String properTime = localTime.format(DateTimeFormatter.ofPattern("HH:mm")); + return properTime; + } + + //@@author teoziyiivy + /** + * Extracts the weight from the user input. + * + * @param inputArguments User input. + * @return double Weight that user input. + * @throws DukeException If input does not have a weight or (weight < 0) or (weight > 1000). + */ + public static double getWeight(String inputArguments) throws DukeException { + String[] userInput = inputArguments.split(DATE_SEPARATOR); + if (!userInput[0].matches("^\\d+(\\.\\d+)?")) { + throw new DukeException("Invalid weight!"); + } + double weight = parseStringToDouble(userInput[0]); + if (weight < 0) { + throw new DukeException("Negative weight"); + } + + if (weight > 1000) { + throw new DukeException("Exceeded maximum weight"); + } + return weight; + } + + //@@author arvejw + /** + * Returns the description of the scheduled workout. + * + * @param inputArguments Arguments input by the user that come after the command word. + * @return String Description of the workout. + * @throws ScheduleException If unable to extract description. + */ + public static String getScheduleDescription(String inputArguments) throws ScheduleException { + String[] userInput = inputArguments.split(DATE_SEPARATOR); + if (userInput.length == 1) { + throw new InvalidScheduleDescriptionException(); + } + String description = userInput[0].trim(); + return description; + } + + //@@author arvejw + /** + * Returns the parsed breakdown of the workout activities. + * + * @param inputArguments Arguments input by the user that come after the command word. + * @return Map of activity description and activity quantifier pairs. + * @throws ScheduleException If there is missing activity splitter, quantifier or invalid format. + */ + public static Map> getActivities(String inputArguments) throws ScheduleException { + int indexOfActivitySeparator = inputArguments.indexOf(Parser.ACTIVITY_SEPARATOR); + String subSubstringAfterActivitySeparator = EMPTY_STRING; + if (indexOfActivitySeparator != -1) { + subSubstringAfterActivitySeparator = inputArguments.substring( + indexOfActivitySeparator).trim(); + if (isRecurringWorkout(inputArguments)) { + subSubstringAfterActivitySeparator = subSubstringAfterActivitySeparator + .replace(RECURRING_FLAG, "").replace(ACTIVITY_SEPARATOR.trim(), "").trim(); + } else { + subSubstringAfterActivitySeparator = subSubstringAfterActivitySeparator + .replace(ACTIVITY_SEPARATOR.trim(), "").trim(); + } + } + if (subSubstringAfterActivitySeparator.isEmpty()) { + return new HashMap<>(); + } else { + return getActivityArguments(subSubstringAfterActivitySeparator.split(MULTIPLE_ACTIVITY_MARKER)); + } + } + + //@@author arvejw + /** + * Returns the description and arguments for the workout activity. + * + * @param nonParsedActivities The activities which have not been parsed. + * @return Map of activity description and activity quantifier pairs. + * @throws ScheduleException If there is missing activity splitter, quantifier or invalid format. + */ + public static Map> getActivityArguments(String[] nonParsedActivities) + throws ScheduleException { + Map> outputMap = new HashMap<>(); + for (String activity : nonParsedActivities) { + String[] splitResults = activity.split(ACTIVITY_SPLITTER, 2); + if (splitResults.length == 1) { + throw new MissingActivitySplitterException(); + } + String[] quantifierSplitResults = splitResults[1].split(QUANTIFIER_SPLITTER, 2); + if (quantifierSplitResults.length == 1 && !WorkoutActivity.isDistanceActivity(splitResults[0])) { + throw new MissingActivityQuantifierException(); + } + if (quantifierSplitResults.length == 2 && WorkoutActivity.isDistanceActivity(splitResults[0])) { + throw new UnnecessaryQuantifierException(); + } + ArrayList activityQuantifiers = new ArrayList(); + if (WorkoutActivity.isDistanceActivity(splitResults[0])) { + parseDistanceActivityQuantifiers(quantifierSplitResults, activityQuantifiers); + } else if (quantifierSplitResults.length == 2) { + parseNonDistanceActivityQuantifiers(quantifierSplitResults, activityQuantifiers); + } else { + throw new GetActivityException(); + } + outputMap.put(splitResults[0].trim(), activityQuantifiers); + } + return outputMap; + } + + //@@author arvejw + /** + * Adds distance activity quantifiers to array list of activity quantifiers. + * + * @param quantifierSplitResults Array of quantifier split results. + * @param activityQuantifiers ArrayList of activity quantifiers. + * @throws InvalidActivityFormatException If non-integer or integer less than equal to 0 detected. + */ + private static void parseDistanceActivityQuantifiers( + String[] quantifierSplitResults, ArrayList activityQuantifiers) + throws InvalidActivityFormatException { + try { + if (parseStringToInteger(quantifierSplitResults[0].trim()) <= 0) { + throw new NumberFormatException(); + } + activityQuantifiers.add(parseStringToInteger(quantifierSplitResults[0].trim())); + } catch (NumberFormatException e) { + throw new InvalidActivityFormatException(); + } + } + + //@@author arvejw + /** + * Adds non-distance activity quantifiers to array list of activity quantifiers. + * + * @param quantifierSplitResults Array of quantifier split results. + * @param activityQuantifiers ArrayList of activity quantifiers. + * @throws InvalidActivityFormatException If non-integer or integer less than equal to 0 detected. + */ + private static void parseNonDistanceActivityQuantifiers( + String[] quantifierSplitResults, ArrayList activityQuantifiers) + throws InvalidActivityFormatException { + try { + if (parseStringToInteger(quantifierSplitResults[0].trim()) <= 0 + || parseStringToInteger(quantifierSplitResults[1].trim()) <= 0) { + throw new NumberFormatException(); + } + activityQuantifiers.add(parseStringToInteger(quantifierSplitResults[0].trim())); + activityQuantifiers.add(parseStringToInteger(quantifierSplitResults[1].trim())); + } catch (NumberFormatException e) { + throw new InvalidActivityFormatException(); + } + } + + //@@author pragyan01 + /** + * This method obtains current system date of the user. + * + *@return current system date + * + *@author pragyan01 + */ + public static String getSystemDate() { + String systemDate = ""; + DateTimeFormatter dtf = DateTimeFormatter.ofPattern("dd/MM/yyyy", Locale.getDefault()); + LocalDate now = LocalDate.now(); + systemDate = now.format(dtf); + + return systemDate; + } + + //@@author pragyan01 + /** + * This method obtains current system time of the user. + * + *@return current system time + * + *@author pragyan01 + */ + public static String getSystemTime() { + String systemTime = ""; + DateTimeFormatter dtf = DateTimeFormatter.ofPattern("HH:mm", Locale.getDefault()); + LocalTime now = LocalTime.now(); + systemTime = now.format(dtf); + return systemTime; + } +} diff --git a/src/main/java/seedu/duke/Storage.java b/src/main/java/seedu/duke/Storage.java new file mode 100644 index 0000000000..aae2594751 --- /dev/null +++ b/src/main/java/seedu/duke/Storage.java @@ -0,0 +1,611 @@ +package seedu.duke; + +import seedu.duke.exceptions.DukeException; +import seedu.duke.schedule.ScheduleTracker; +import seedu.duke.schedule.ScheduledWorkout; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Scanner; + +import static seedu.duke.ClickfitMessages.MEAL_PRINT_FORMAT; +import static seedu.duke.ClickfitMessages.FLUID_PRINT_FORMAT; +import static seedu.duke.ClickfitMessages.WORKOUT_PRINT_FORMAT; +import static seedu.duke.ClickfitMessages.WEIGHT_PRINT_FORMAT; +import static seedu.duke.ClickfitMessages.ENDLINE_PRINT_FORMAT; + +public class Storage { + + public static final String SCHEDULE_FILE_PATH = "Schedule.txt"; + public static final String WORKOUT_FILE_PATH = "Workout.txt"; + public static final String foodFile = "Food.txt"; + public static final String libraryFile = "FoodBank.txt"; + public static final String weightFile = "Weight.txt"; + + public Storage() { + initializeFoodFile(); + initializeFoodBankFile(); + initializeWeightFile(); + initializeScheduleFile(); + initializeWorkoutFile(); + } + + //@@author VishalJeyaram + /** + * Saves meal and fluid headers and date headers to text file. + * + * @param fluid Fluid object. + * @param meal Meal object. + * @throws IOException If there is a problem with the text file. + */ + public void saveFood(Fluid fluid, Meal meal) throws IOException { + String header; + String filePath = new File(foodFile).getAbsolutePath(); + FileWriter fw = new FileWriter(filePath, false); + header = "Meals" + "\n"; + Files.write(Paths.get(filePath), header.getBytes(), StandardOpenOption.APPEND); + fw.close(); + for (String date : DateTracker.dates) { + saveFoodLists(filePath, fw, date, meal.meals); + } + header = "Fluids" + "\n"; + Files.write(Paths.get(filePath), header.getBytes(), StandardOpenOption.APPEND); + fw.close(); + for (String date : DateTracker.dates) { + saveFoodLists(filePath, fw, date, fluid.fluidArray); + } + } + + //@@author VishalJeyaram + /** + * Saves meal and fluid lists to text file. + * + * @param filePath Name of textfile. + * @param fw FileWriter variable. + * @param date Date of food consumption. + * @param foods List of foods. + * @throws IOException If there is a problem with the text file. + */ + private void saveFoodLists(String filePath, FileWriter fw, + String date, ArrayList foods) throws IOException { + int headerFlag; + String currentDate; + String currentMeal; + headerFlag = 0; + for (String food : foods) { + if (food.contains(date) && (headerFlag == 0)) { + currentDate = "Date: " + date + "\n"; + Files.write(Paths.get(filePath), currentDate.getBytes(), StandardOpenOption.APPEND); + fw.close(); + headerFlag = 1; + } + if (food.contains(date)) { + currentMeal = food + "\n"; + Files.write(Paths.get(filePath), currentMeal.getBytes(), StandardOpenOption.APPEND); + fw.close(); + } + } + } + + //@@author VishalJeyaram + /** + * Saves library meals and fluids to text file. + * + * @throws IOException If there is a problem with the text file. + */ + public void saveLibrary() throws IOException { + String customMeal; + String customFluid; + String header; + String filePath = new File(libraryFile).getAbsolutePath(); + FileWriter fw = new FileWriter(filePath, false); + header = "Meals" + "\n"; + Files.write(Paths.get(filePath), header.getBytes(), StandardOpenOption.APPEND); + fw.close(); + for (String m : FoodBank.meals) { + customMeal = m + "\n"; + Files.write(Paths.get(filePath), customMeal.getBytes(), StandardOpenOption.APPEND); + fw.close(); + + } + header = "Fluids" + "\n"; + Files.write(Paths.get(filePath), header.getBytes(), StandardOpenOption.APPEND); + fw.close(); + for (String f : FoodBank.fluids) { + customFluid = f + "\n"; + Files.write(Paths.get(filePath), customFluid.getBytes(), StandardOpenOption.APPEND); + fw.close(); + } + } + + //@@author teoziyiivy + /** + * Saves weight to Weight.txt file. + * + * @throws IOException If there is a problem with the text file. + */ + public void saveWeight(WeightTracker weight) throws IOException { + String currentWeight; + String header; + String filePath = new File(weightFile).getAbsolutePath(); + FileWriter fw = new FileWriter(filePath, false); + header = "Weights" + "\n"; + Files.write(Paths.get(filePath), header.getBytes(), StandardOpenOption.APPEND); + fw.close(); + for (String w : weight.weightsArray) { + currentWeight = w + "\n"; + Files.write(Paths.get(filePath), currentWeight.getBytes(), StandardOpenOption.APPEND); + fw.close(); + } + } + + //@@author EdwardZYWang + /** + * saves the user's workouts to the text file so that it can be accessed again in future sessions. + * + * @param workoutTracker workout of the user. + * @throws IOException if there is an incorrect input. + */ + public void saveWorkout(WorkoutTracker workoutTracker) throws IOException { + FileWriter fileWriter = new FileWriter(WORKOUT_FILE_PATH, true); + FileWriter fileCleaner = new FileWriter(WORKOUT_FILE_PATH, false); + fileCleaner.write(Parser.EMPTY_STRING); + DateTracker.sortDateAndTime(workoutTracker.workouts); + fileCleaner.close(); + for (String w : workoutTracker.workouts) { + fileWriter.write(w + System.lineSeparator()); + } + fileWriter.close(); + } + + //@@author EdwardZYWang + /** + * saves the user's future workout schedule to the text file so that it can be accessed again in future sessions. + * + * @param scheduleTracker workout of the user. + * @throws IOException if there is an incorrect input. + */ + public void saveSchedule(ScheduleTracker scheduleTracker) throws IOException { + FileWriter fileWriter = new FileWriter(SCHEDULE_FILE_PATH, true); + FileWriter fileCleaner = new FileWriter(SCHEDULE_FILE_PATH, false); + ArrayList currentScheduleStringList = new ArrayList<>(); + for (ScheduledWorkout w : scheduleTracker.getScheduledWorkouts()) { + currentScheduleStringList.add(w.getScheduledWorkoutAsDataString()); + } + DateTracker.sortDateAndTime(currentScheduleStringList); + fileCleaner.write(Parser.EMPTY_STRING); + fileCleaner.close(); + for (String s : currentScheduleStringList) { + if (Parser.isRecurringWorkout(s)) { + fileWriter.write(s + System.lineSeparator()); + continue; + } + if (LocalDate.parse( + Parser.getDateNoDateTracker(s), + DateTimeFormatter.ofPattern("dd/MM/yyyy")) + .isBefore(LocalDate.now())) { + continue; + } + fileWriter.write(s + System.lineSeparator()); + } + fileWriter.close(); + } + + //@@author pragyan01 + /** + * This method loads all meals saved in .txt file to meal array list. + * + * @return meal array list + * @throws IOException if I/O error occurs + */ + public ArrayList loadMeals() throws IOException { + ArrayList meals = new ArrayList<>(); + String newFilePath = new File(foodFile).getAbsolutePath(); + File f = new File(newFilePath); + Scanner s = new Scanner(f); + String textFromFile; + int flag = 0; + while ((s.hasNext()) && (flag == 0)) { + textFromFile = s.nextLine(); + if (textFromFile.equals("Fluids")) { + flag = 1; + } else if (textFromFile.contains(Parser.CALORIE_SEPARATOR)) { + meals.add(textFromFile); + } else if (textFromFile.contains("Date")) { + String[] date = textFromFile.split(" "); + DateTracker.checkIfDateExists(date[1]); + } + } + return meals; + } + + //@@author pragyan01 + /** + * This method loads all fluids saved in .txt file to fluid array list. + * + * @return fluid array list + * @throws IOException if I/O error occurs + */ + public ArrayList loadFluids() throws IOException { + ArrayList fluids = new ArrayList<>(); + String newFilePath = new File(foodFile).getAbsolutePath(); + File f = new File(newFilePath); + Scanner s = new Scanner(f); + String textFromFile; + int flag = 0; + while (s.hasNext()) { + textFromFile = s.nextLine(); + if ((flag == 1) && (textFromFile.contains(Parser.CALORIE_SEPARATOR))) { + fluids.add(textFromFile); + } else if (textFromFile.equals("Fluids")) { + flag = 1; + } else if (textFromFile.contains("Date")) { + String[] date = textFromFile.split(" "); + DateTracker.checkIfDateExists(date[1]); + } + } + return fluids; + } + + //@@author teoziyiivy + /** + * This method loads all weights saved in Weight.txt file to the weights array list. + * + * @return weights arrayList + * @throws IOException If there is a problem with the text file. + */ + public ArrayList loadWeights() throws IOException { + ArrayList weights = new ArrayList<>(); + String newFilePath = new File(weightFile).getAbsolutePath(); + File f = new File(newFilePath); + Scanner s = new Scanner(f); + String textFromFile; + int flag = 0; + while (s.hasNext()) { + textFromFile = s.nextLine(); + if (flag == 1) { + weights.add(textFromFile); + } else if (textFromFile.equals("Weights")) { + flag = 1; + } + } + return weights; + } + + //@@author VishalJeyaram + /** + * Loads meal library from text file to arraylist. + * + * @return Meal library arraylist. + * @throws IOException If there is a problem with the text file. + */ + public ArrayList loadMealLibrary() throws IOException { + ArrayList meals = new ArrayList<>(); + String newFilePath = new File(libraryFile).getAbsolutePath(); + File f = new File(newFilePath); + Scanner s = new Scanner(f); + String textFromFile; + int flag = 0; + while ((s.hasNext()) && (flag == 0)) { + textFromFile = s.nextLine(); + if (textFromFile.equals("Fluids")) { + flag = 1; + } else if (textFromFile.contains(Parser.CALORIE_SEPARATOR)) { + meals.add(textFromFile); + } else if (textFromFile.contains("Date")) { + String[] date = textFromFile.split(" "); + DateTracker.checkIfDateExists(date[1]); + } + } + return meals; + } + + //@@author pragyan01 + /** + * This method loads all fluid entries for foodbank saved in .txt file to fluid array list. + * + * @return fluid array lists + * @throws IOException if I/O error occurs + */ + public ArrayList loadFluidLibrary() throws IOException { + ArrayList fluids = new ArrayList<>(); + String newFilePath = new File(libraryFile).getAbsolutePath(); + File f = new File(newFilePath); + Scanner s = new Scanner(f); + String textFromFile; + int flag = 0; + while (s.hasNext()) { + textFromFile = s.nextLine(); + if ((flag == 1) && (textFromFile.contains(Parser.CALORIE_SEPARATOR))) { + fluids.add(textFromFile); + } else if (textFromFile.equals("Fluids")) { + flag = 1; + } else if (textFromFile.contains("Date")) { + String[] date = textFromFile.split(" "); + DateTracker.checkIfDateExists(date[1]); + } + } + return fluids; + } + + //@@author EdwardZYWang + /** + * Loads all the workouts in the text file and stores them as an array list of strings for workout summary. + * + * @return workout array list. + * @throws IOException if there is an incorrect input. + */ + public ArrayList loadWorkouts() throws IOException { + ArrayList workout = new ArrayList<>(); + File dataFile = new File(WORKOUT_FILE_PATH); + Scanner fileScanner = new Scanner(dataFile); + String textFromFile; + while (fileScanner.hasNext()) { + textFromFile = fileScanner.nextLine(); + if (Parser.containsCalorieSeparator(textFromFile) && Parser.containsDateSeparator(textFromFile) + && Parser.containsTimeSeparator(textFromFile)) { + workout.add(textFromFile); + } + } + return workout; + } + + //@@author EdwardZYWang + /** + * Creates workout schedule file if it hasn't been created already. + * + */ + public static void initializeScheduleFile() { + File dataFile = new File(SCHEDULE_FILE_PATH); + if (!dataFile.exists()) { + try { + dataFile.createNewFile(); + } catch (IOException ioe) { + System.out.println("Error during data file creation for ScheduleTracker."); + } + } + } + + //@@author VishalJeyaram + /** + * Creates food file, "Food.txt" if it hasn't been created already. + */ + public static void initializeFoodFile() { + File dataFile = new File(foodFile); + if (!dataFile.exists()) { + try { + dataFile.createNewFile(); + } catch (IOException ioe) { + System.out.println("Error during data file creation for meals and fluids."); + } + } + } + + //@@author teoziyiivy + /** + * Creates food bank file, "Foodbank.txt" if it hasn't already been created. + */ + public static void initializeFoodBankFile() { + File dataFile = new File(libraryFile); + if (!dataFile.exists()) { + try { + dataFile.createNewFile(); + } catch (IOException ioe) { + System.out.println("Error during data file creation for meals and fluids."); + } + } + } + + //@@author teoziyiivy + /** + * Creates weight file, "Weight.txt" if it hasn't already been created. + */ + public static void initializeWeightFile() { + File dataFile = new File(weightFile); + if (!dataFile.exists()) { + try { + dataFile.createNewFile(); + } catch (IOException ioe) { + System.out.println("Error during data file creation for meals and fluids."); + } + } + } + + //@@author EdwardZYWang + /** + * Creates workout file if it hasn't been created already. + * + */ + public static void initializeWorkoutFile() { + File dataFile = new File(WORKOUT_FILE_PATH); + if (!dataFile.exists()) { + try { + dataFile.createNewFile(); + } catch (IOException ioe) { + System.out.println("Error during data file creation for WorkoutTracker."); + } + } + } + + //@@author EdwardZYWang + /** + * A method that take in the array list, meals, and formats it into the appropriate output + * form to be a useful summary to the user. if loop accounts for the "[" in the first element + * during parsing, else loop accounts for the case when there is no "[" in the parsed string m. + * + */ + public void mealSummary() { + int totalCalories = 0; + int i = 1; + try { + for (String m : loadMeals()) { + if (m.contains("[")) { + String[] descriptor = m.substring(1).split(" /c "); + String[] calorie = descriptor[1].split(" /d "); + String description = descriptor[0]; + System.out.println(i + ". " + description); + String calorieIndiv = calorie[0]; + totalCalories += Integer.parseInt(calorieIndiv); + i++; + } else { + String[] descriptor = m.split(" /c "); + String[] calorie = descriptor[1].split(" /d "); + String description = descriptor[0]; + System.out.println(i + ". " + description); + String calorieIndiv = calorie[0]; + totalCalories += Integer.parseInt(calorieIndiv); + i++; + } + } + System.out.println(System.lineSeparator() + "Total number of meals = " + (i - 1)); + System.out.println("Total calories = " + totalCalories); + + } catch (IOException e) { + System.out.println("Error during printing arrayList"); + } + } + + //@@author EdwardZYWang + /** + * A method that take in the array list, fluids, and formats it into the appropriate output + * form to be a useful summary to the user. if loop accounts for the "[" in the first element + * during parsing, else loop accounts for the case when there is no "[" in the parsed string m. + * + */ + public void fluidSummary() { + int totalCalories = 0; + int totalVolume = 0; + int i = 1; + try { + for (String f : loadFluids()) { + if (f.contains("[")) { + String[] descriptor = f.substring(1).split(" /c "); + String[] calorie = descriptor[1].split(" /v "); + String[] volumeSplitter = calorie[1].split(" /d "); + String description = descriptor[0]; + System.out.println(i + ". " + description); + String calorieIndiv = calorie[0]; + String volumeIndiv = volumeSplitter[0]; + totalCalories += Integer.parseInt(calorieIndiv); + totalVolume += Integer.parseInt((volumeIndiv)); + i++; + } else { + String[] descriptor = f.split(" /c "); + String[] calorie = descriptor[1].split(" /v "); + String[] volumeSplitter = calorie[1].split(" /d "); + String description = descriptor[0]; + System.out.println(i + ". " + description); + String calorieIndiv = calorie[0]; + String volumeIndiv = volumeSplitter[0]; + totalCalories += Integer.parseInt(calorieIndiv); + totalVolume += Integer.parseInt((volumeIndiv)); + i++; + } + } + if (totalVolume > 0) { + System.out.println("Total volume consumed = " + totalVolume); + } else { + System.out.println(System.lineSeparator() + "Total variety of drinks = " + (i - 1)); + } + System.out.println("Total calories = " + totalCalories); + } catch (IOException e) { + System.out.println("Error during printing arrayList"); + } + } + + //@@author teoziyiivy + /** + * A method that takes in the weight array list and formats it into the appropriate output + * form to provide a useful weight summary to the user. It will print out the summary for + * all the weights in the weights array list printing the index in the list, weight, date, + * and total number of weights. + * + * @throws DukeException If encountered error when generating weight parameters + */ + public void weightSummary() throws DukeException { + int i = 1; + double weight; + String date; + try { + for (String w : loadWeights()) { + weight = Parser.getWeight(w); + date = Parser.getDate(w); + System.out.println(i + ". " + weight + " kg " + date); + i++; + } + System.out.println(System.lineSeparator() + "Total number of weights = " + (i - 1)); + } catch (IOException e) { + System.out.println("Error during printing arrayList"); + } + } + + //@@author EdwardZYWang + /** + * A method that take in the array list,workout, and formats it into the appropriate output + * form to be a useful summary to the user. if loop accounts for the "[" in the first element + * during parsing, else loop accounts for the case when there is no "[" in the parsed string m. + * + */ + public void workoutSummary() { + int totalCalories = 0; + int i = 1; + try { + for (String w : loadWorkouts()) { + if (w.contains("[")) { + String[] descriptor = w.substring(1).split(" /c "); + String[] calorie = descriptor[1].split(" /d "); + String description = descriptor[0]; + System.out.println(i + ". " + description); + String calorieIndiv = calorie[0]; + totalCalories += Integer.parseInt(calorieIndiv); + i++; + } else { + String[] descriptor = w.split(" /c "); + String[] calorie = descriptor[1].split(" /d "); + String description = descriptor[0]; + System.out.println(i + ". " + description); + String calorieIndiv = calorie[0]; + totalCalories += Integer.parseInt(calorieIndiv); + i++; + } + } + System.out.println(System.lineSeparator() + "Completed Workouts = " + (i - 1)); + System.out.println("Total calories burned = " + totalCalories); + + } catch (IOException e) { + System.out.println("Error during printing arrayList"); + } + } + + //@@author EdwardZYWang + /** + * A method that take calls the class summaries above and prints them out in the correct format. + * it also prints out the date and time of the summary so that the user knows that he or she is receiving the + * most updated information. + * + * @throws DukeException when there is an error + */ + public void printLoadedLists() throws DukeException { + System.out.println(MEAL_PRINT_FORMAT); + mealSummary(); + System.out.println(ENDLINE_PRINT_FORMAT); + System.out.println(FLUID_PRINT_FORMAT); + fluidSummary(); + System.out.println(ENDLINE_PRINT_FORMAT); + System.out.println(WORKOUT_PRINT_FORMAT); + workoutSummary(); + System.out.println(ENDLINE_PRINT_FORMAT); + System.out.println(WEIGHT_PRINT_FORMAT); + weightSummary(); + System.out.println(ENDLINE_PRINT_FORMAT); + System.out.println(System.lineSeparator() + "Updated as of: " + + Parser.getSystemDate() + " " + Parser.getSystemTime()); + } +} \ No newline at end of file diff --git a/src/main/java/seedu/duke/Tracker.java b/src/main/java/seedu/duke/Tracker.java new file mode 100644 index 0000000000..86d17f8ac9 --- /dev/null +++ b/src/main/java/seedu/duke/Tracker.java @@ -0,0 +1,10 @@ +package seedu.duke; + +//@@author pragyan01 +/** + * Abstract parent class tracker. + * + * @author pragyan01 + */ +public abstract class Tracker { +} diff --git a/src/main/java/seedu/duke/Ui.java b/src/main/java/seedu/duke/Ui.java new file mode 100644 index 0000000000..cba758247e --- /dev/null +++ b/src/main/java/seedu/duke/Ui.java @@ -0,0 +1,230 @@ +package seedu.duke; + +import seedu.duke.exceptions.LoadException; +import java.util.Scanner; + +import static seedu.duke.ClickfitMessages.CALCULATOR_PROMPT; +import static seedu.duke.ClickfitMessages.MEMORY_STARTUP_N_INPUT; +import static seedu.duke.ClickfitMessages.MEMORY_STARTUP_PROMPT; +import static seedu.duke.ClickfitMessages.MEMORY_STARTUP_Y_INPUT; +import static seedu.duke.ClickfitMessages.MESSAGE_A; + + +public class Ui { + + private Scanner uiScanner; + public static final String HORIZONTAL_BAR_LONG = "____________________________________________________________" + + "____________________________________________________________"; + public static final String HORIZONTAL_BAR_SHORT = "_________________________________________________________"; + public static final String USER_PROMPT = "Enter command: "; + protected String sex; + protected double weight; + protected double height; + protected int age; + protected int activityLevel; + + //@@author pragyan01 + /** + * Constructor of UI class. + * + */ + public Ui() { + uiScanner = new Scanner(System.in); + } + + //@@author EdwardZYWang + /** + * Print method with clickFit's logo. + * + */ + public static void welcomeMessage() { + String logo = " ______ _____ _____ __ ________ _ _\n" + + " .' ___ ||_ _| |_ _| [ | _ |_ __ |(_) / |_\n" + + "/ .' \\_| | | | | .---. | | / ] | |_ \\_|__ `| |-'\n" + + "| | | | _ | | / /'`\\] | '' < | _| [ | | |\n" + + "\\ `.___.'\\ _| |__/ | _| |_ _ | \\__. | |`\\ \\ _| |_ | | | |,\n" + + " `.____ .'|________||_____|(_)'.___.'[__| \\_]|_____| [___]\\__/"; + + System.out.println("Greetings from\n" + logo + "\n"); + System.out.println(MESSAGE_A); + } + + //@@author pragyan01 + /** + * This method obtains user's answers to BMI related questions and then calculates user's BMI. + * + * @author pragyan01 + */ + public void getInfo() { + System.out.println(CALCULATOR_PROMPT); + String uiInput; + boolean answerIsCorrect = false; + boolean flag = false; + + while (!flag) { + uiInput = uiScanner.nextLine(); + if (uiInput.isEmpty()) { + System.out.println("Calculator closed!"); + return; + } else if (uiInput.trim().equals("y")) { + flag = true; + } else { + System.out.println("wrong input!"); + } + } + answerIsCorrect = checkGender(answerIsCorrect); + + answerIsCorrect = checkWeight(answerIsCorrect); + + answerIsCorrect = checkHeight(answerIsCorrect); + + answerIsCorrect = checkAge(answerIsCorrect); + + checkActivityLevel(answerIsCorrect); + + Calculator calculator = new Calculator(sex, weight, height, age, activityLevel); + calculator.getBmi(); + calculator.getIdealCalories(); + } + + //@@author pragyan01 + /** + * This method obtains user's answers to BMI related questions and then calculates user's BMI. + * + * @author pragyan01 + */ + private void checkActivityLevel(boolean answerIsCorrect) { + String uiInput; + while (!answerIsCorrect) { + try { + System.out.println("what is your activity level from a scale of 1 - 5? Enter an integer from 1 to 5!"); + uiInput = uiScanner.nextLine(); + if (uiInput.matches("^\\d+(\\.\\d+)?")) { + activityLevel = Integer.parseInt(uiInput); + if ((activityLevel >= 1) && (activityLevel <= 5)) { + answerIsCorrect = true; + } + } + } catch (NumberFormatException e) { + System.out.println("Please enter a valid input!"); + } + } + } + + //@@author pragyan01 + /** + * This method obtains user's age. + * + * @author pragyan01 + */ + private boolean checkAge(boolean answerIsCorrect) { + String uiInput; + while (!answerIsCorrect) { + try { + System.out.println("what is your age in years? Enter an integer!"); + uiInput = uiScanner.nextLine(); + if (uiInput.matches("^\\d+(\\.\\d+)?")) { + age = Integer.parseInt(uiInput); + answerIsCorrect = true; + } + } catch (NumberFormatException e) { + System.out.println("Please enter a valid input!"); + } + } + answerIsCorrect = false; + return answerIsCorrect; + } + + //@@author pragyan01 + /** + * This method obtains user's height. + * + * @author pragyan01 + */ + private boolean checkHeight(boolean answerIsCorrect) { + String uiInput; + while (!answerIsCorrect) { + System.out.println("what is your height in cm? Enter a valid number!"); + uiInput = uiScanner.nextLine(); + if (uiInput.matches("^\\d+(\\.\\d+)?")) { + height = Double.parseDouble(uiInput); + answerIsCorrect = true; + } + } + answerIsCorrect = false; + return answerIsCorrect; + } + + //@@author VishalJeyaram + /** + * This method obtains the user's weight. + * + * @param answerIsCorrect The boolean that checks if the answer is acceptable. + * @return answerIsCorrect. + */ + private boolean checkWeight(boolean answerIsCorrect) { + String uiInput; + while (!answerIsCorrect) { + System.out.println("what is your weight in kg? Enter a valid number!"); + uiInput = uiScanner.nextLine(); + if (uiInput.matches("^\\d+(\\.\\d+)?")) { + weight = Double.parseDouble(uiInput); + answerIsCorrect = true; + } + } + answerIsCorrect = false; + return answerIsCorrect; + } + + //@@author VishalJeyaram + /** + * This method obtains the user's gender. + * + * @param answerIsCorrect The boolean that checks if the answer is acceptable. + * @return answerIsCorrect. + */ + private boolean checkGender(boolean answerIsCorrect) { + String uiInput; + while (!answerIsCorrect) { + System.out.println("what is your SEX : M / F ?"); + uiInput = uiScanner.nextLine(); + sex = uiInput; + if ((uiInput.equals("M")) || (uiInput.equals("F"))) { + answerIsCorrect = true; + } + } + answerIsCorrect = false; + return answerIsCorrect; + } + + //@@author EdwardZYWang + /** + * Checks with the user whether he wishes to load the data of the previous session. + * if "y" is input, the data from the previous session is deleted . if enter keystroke is registered + * the previous session's data is loaded. + * + * @throws LoadException If there are missing text files. + */ + public boolean memoryStartup() throws LoadException { + System.out.println(MEMORY_STARTUP_PROMPT); + String uiInput; + boolean flag = false; + boolean result = false; + while (!flag) { + uiInput = uiScanner.nextLine(); + if (uiInput.isEmpty()) { + System.out.println(MEMORY_STARTUP_Y_INPUT); + System.out.println(System.lineSeparator() + "What would you like to do?"); + result = true; + flag = true; + } else if (uiInput.trim().equals("y")) { + System.out.println(MEMORY_STARTUP_N_INPUT); + result = false; + flag = true; + } else { + System.out.println("wrong input!"); + } + } + return result; + } +} \ No newline at end of file diff --git a/src/main/java/seedu/duke/UserHelp.java b/src/main/java/seedu/duke/UserHelp.java new file mode 100644 index 0000000000..cdd1b640a2 --- /dev/null +++ b/src/main/java/seedu/duke/UserHelp.java @@ -0,0 +1,35 @@ +package seedu.duke; + +import java.util.Objects; +import java.util.logging.Level; +import java.util.logging.Logger; + +import static seedu.duke.ClickfitMessages.HELP_COMMANDS; + +//@@author EdwardZYWang +public class UserHelp { + private static Logger UserHelp_LOGGER = Logger.getLogger("UiLogger"); + + public UserHelp() { + this.UserHelp_LOGGER.setLevel(Level.SEVERE); + } + + /** + * Takes in user input to decide whether to print out help commands. + * + * @param input user's input + */ + public static void generateUserHelpParameters(String input) { + assert !Objects.equals(input, ""); + + UserHelp_LOGGER.log(Level.INFO, "print out Duke commands"); + getCommands(); + } + + public static void getCommands() { + System.out.println(HELP_COMMANDS); + } + + + +} diff --git a/src/main/java/seedu/duke/WeightTracker.java b/src/main/java/seedu/duke/WeightTracker.java new file mode 100644 index 0000000000..ce6d1afba3 --- /dev/null +++ b/src/main/java/seedu/duke/WeightTracker.java @@ -0,0 +1,192 @@ +package seedu.duke; + +import seedu.duke.exceptions.weight.WeightException; +import seedu.duke.exceptions.weight.AddWeightException; +import seedu.duke.exceptions.weight.DeleteWeightException; +import seedu.duke.exceptions.weight.FutureWeightException; +import seedu.duke.exceptions.weight.DeleteWeightIndexException; +import seedu.duke.exceptions.DukeException; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.ArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; + +//@@author teoziyiivy + +/** + * This WeightTracker class contains all the functions related to weight. + * Namely, generating parameters, adding, deleting and listing. + */ +public class WeightTracker extends Tracker { + protected ArrayList weightsArray; + protected int numberOfWeights; + protected double weight; + protected String date; + private static Logger logger = Logger.getLogger("WeightTrackerLogger"); + + /** + * Constructs a WeightTracker object. + */ + public WeightTracker() { + this.weightsArray = new ArrayList<>(); + this.numberOfWeights = 0; + logger.setLevel(Level.SEVERE); + } + + /** + * Generates the parameters for weight and date + * to construct a WeightTracker object. + * + * @param input User input. + * @throws WeightException If DukeException is caught. + */ + public void generateWeightParameters(String input) throws WeightException { + try { + logger.entering(getClass().getName(), "generateWeightParameters"); + logger.log(Level.INFO, "going to generate weight and date parameters from user input"); + weight = Parser.getWeight(input); + date = Parser.getDate(input); + } catch (NumberFormatException e) { + System.out.println(ClickfitMessages.WEIGHT_PARAMETERS_ERROR); + } catch (DukeException e) { + throw new WeightException(); + } + logger.exiting(getClass().getName(), "generateWeightParameters"); + logger.log(Level.INFO, "end of generating weight parameters"); + } + + /** + * Adds a weight to a list of weights. + * + * @param input User input. + * @throws WeightException If the input == null. + * @throws DateTimeParseException If the date is not input correctly or valid. + */ + public void addWeight(String input) throws WeightException, DateTimeParseException { + logger.entering(getClass().getName(), "addWeight"); + logger.log(Level.INFO, "going to add a weight and date to the list"); + numberOfWeights = weightsArray.size(); + generateWeightParameters(input); + String inputDate = date; + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy"); + LocalDate localDate = LocalDate.parse(inputDate, formatter); + LocalDate now = LocalDate.now(); + if (localDate.isAfter(now)) { + throw new FutureWeightException(); + } + logger.log(Level.INFO, "weight parameters generated"); + if (input == null) { + throw new AddWeightException(); + } else { + ClickfitMessages.printAddWeightResponse(weight, date); + input = weight + " /d " + date; + weightsArray.add(input); + numberOfWeights += 1; + assert numberOfWeights > 0 : "number of logged weights should be more than zero"; + } + logger.exiting(getClass().getName(), "addWeight"); + logger.log(Level.INFO, "end of processing addweight command"); + } + + /** + * Deletes a weight from a list of weights using the index of the weight to be deleted + * from the list as given by the user. + * + * @param input User input. + * @throws WeightException If the (weightIndex < 0) or (weightIndex > numberOfWeights - 1). + */ + public void deleteWeight(String input) throws WeightException { + logger.entering(getClass().getName(), "deleteWeight"); + logger.log(Level.INFO, "going to remove a weight and date from the list"); + numberOfWeights = weightsArray.size(); + if (input.isEmpty() || input.matches("delete weight")) { + throw new DeleteWeightException(); + } + int weightIndex = Parser.parseStringToInteger(input) - 1; + if ((weightIndex < 0) || (weightIndex > numberOfWeights - 1)) { + throw new DeleteWeightIndexException(); + } else { + generateWeightParameters(weightsArray.get(weightIndex)); + ClickfitMessages.printDeleteWeightResponse(weight, date); + weightsArray.remove(weightIndex); + numberOfWeights--; + assert numberOfWeights >= 0 : "number of logged weights should be more than or equal to zero"; + } + logger.exiting(getClass().getName(), "deleteWeight"); + logger.log(Level.INFO, "end of processing deleteweight command"); + } + + /** + * Lists weights from the list of weights depending on user input. + * Namely, if user inputs all, listAllWeights() will be called and all + * weights in the weightsArray will be printed. If the user inputs a date (DD/MM/YYYY), + * then listSpecificWeights() will be called instead and only the weights recorded + * on that specific date will be printed. + * + * @param input User input. + * @throws WeightException If the weight in weightsArray is not recorded properly. + */ + public void listWeights(String input) throws WeightException { + numberOfWeights = weightsArray.size(); + if (numberOfWeights == 0) { + System.out.println(ClickfitMessages.WEIGHT_EMPTY_ERROR); + } else if (input.equals("all")) { + listAllWeights(); + } else { + listSpecificWeights(input); + } + } + + /** + * Lists weights from the list of weights for the specific date as specified by the user. + * Namely, if the user inputs a date (DD/MM/YYYY), only the weights recorded on that + * specific date will be printed. + * + * @param date Date input by the user. + * @throws WeightException If the weight in weightsArray is not recorded properly. + */ + public void listSpecificWeights(String date) throws WeightException { + logger.entering(getClass().getName(), "listSpecificWeights"); + logger.log(Level.INFO, "going to list specific logged weights and dates"); + int index = 1; + int numberOfSpecificWeights = 0; + for (String weights : weightsArray) { + if (weights.contains(date)) { + logger.log(Level.INFO, "generating weight parameters"); + generateWeightParameters(weights); + logger.log(Level.INFO, "weight parameters generated"); + System.out.print(index + ". "); + System.out.println("Weight: " + weight + " kg"); + index++; + numberOfSpecificWeights++; + } + } + System.out.println("Total number of weights: " + numberOfSpecificWeights); + logger.exiting(getClass().getName(), "listSpecificWeights"); + logger.log(Level.INFO, "end of processing listSpecificWeights command"); + } + + /** + * Lists every weight in the list of weights. + * + * @throws WeightException If the weight in weightsArray is not recorded properly. + */ + public void listAllWeights() throws WeightException { + logger.entering(getClass().getName(), "listAllWeights"); + logger.log(Level.INFO, "going to list all logged weights and dates"); + int index = 1; + System.out.println("Here are your recorded weights:"); + for (String weights : weightsArray) { + generateWeightParameters(weights); + System.out.print(index + ". "); + System.out.print(" Weight: " + weight + " kg "); + System.out.println(" Date: " + date + System.lineSeparator()); + index++; + } + System.out.println("Total number of weights: " + (index - 1)); + logger.exiting(getClass().getName(), "listAllWeights"); + logger.log(Level.INFO, "end of processing listAllWeights command"); + } +} diff --git a/src/main/java/seedu/duke/WorkoutTracker.java b/src/main/java/seedu/duke/WorkoutTracker.java new file mode 100644 index 0000000000..188fb1a95b --- /dev/null +++ b/src/main/java/seedu/duke/WorkoutTracker.java @@ -0,0 +1,278 @@ +package seedu.duke; + +import seedu.duke.exceptions.workout.DeleteWorkoutException; +import seedu.duke.exceptions.workout.DuplicateWorkoutException; +import seedu.duke.exceptions.workout.MissingWorkoutCalorieSeparatorException; +import seedu.duke.exceptions.workout.MissingWorkoutDescriptionException; +import seedu.duke.exceptions.workout.NoWorkoutIndexException; +import seedu.duke.exceptions.workout.WorkoutException; +import seedu.duke.exceptions.workout.WorkoutNullArgumentException; + +import java.util.ArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +//@@author arvejw + +/** + * Manages the tracking of workouts and various other operations such as adding new workouts and the calories burned + * doing them as well as deleting said workouts. + */ +public class WorkoutTracker { + protected ArrayList workouts; + protected String workoutDescription; + protected int caloriesBurned; + protected String workoutDate; + protected String workoutTime; + private static final String INPUT_ALL = "all"; + private static final int LOWER_BOUND_INDEX_NON_EMPTY_LIST_ONES_INDEXING = 1; + public static final Logger WORKOUT_TRACKER_LOGGER = Logger.getLogger("WorkoutTrackerLogger"); + + public WorkoutTracker() { + this.workouts = new ArrayList<>(); + WORKOUT_TRACKER_LOGGER.setLevel(Level.SEVERE); + } + + /** + * Generates the workout parameters based off the user's input. + * + * @param inputArguments Arguments input by the user that come after the command word. + * @throws WorkoutException If there are issues during generation of workout parameters. + */ + public void generateWorkoutParameters(String inputArguments) throws WorkoutException { + WORKOUT_TRACKER_LOGGER.log(Level.INFO, "Starting generation of parameters for workout."); + workoutDescription = Parser.getDescription(inputArguments); + caloriesBurned = Parser.getCaloriesBurnedForWorkout(inputArguments); + workoutDate = Parser.getDate(inputArguments); + workoutTime = Parser.getTime(inputArguments); + WORKOUT_TRACKER_LOGGER.log(Level.INFO, "Successfully generated parameters for workout."); + } + + /** + * Adds a workout to the workout list. + * + * @param inputArguments Arguments input by the user that come after the command word. + * @param isSquelchAddMessage Flag to determine whether to squelch the message printed during successful adding of + * a workout. true to squelch, false to continue printing + * the message. + * @throws WorkoutException If there are issues during adding the workout. + */ + public void addWorkout(String inputArguments, boolean isSquelchAddMessage) throws WorkoutException { + WORKOUT_TRACKER_LOGGER.log(Level.INFO, "Starting to try and add workout."); + nullArgumentCheck(inputArguments); + assert inputArguments != null : "Exception should already been thrown if argument is null"; + missingDescriptionCheck(inputArguments); + generateWorkoutParameters(inputArguments); + String updatedArguments = workoutDescription + Parser.CALORIE_SEPARATOR + caloriesBurned + + Parser.DATE_SEPARATOR + workoutDate + Parser.TIME_SEPARATOR + workoutTime; + duplicateWorkoutCheck(updatedArguments); + if (!isSquelchAddMessage) { + System.out.println(ClickfitMessages.getAddWorkoutSuccessMessage( + workoutDescription, workoutDate, workoutTime, caloriesBurned)); + } + workouts.add(updatedArguments); + WORKOUT_TRACKER_LOGGER.log(Level.INFO, "Successfully added workout."); + } + + /** + * Deletes a workout from the workout list. + * + * @param inputArguments Arguments input by the user that come after the command word. + * @throws WorkoutException If there are issues during deletion of the workout. + */ + public void deleteWorkout(String inputArguments) throws WorkoutException { + WORKOUT_TRACKER_LOGGER.log(Level.INFO, "Starting to try and delete workout."); + if (inputArguments == null) { + throw new NoWorkoutIndexException(); + } + assert inputArguments != null : "Exception should already been thrown if argument is null"; + if (isWorkoutListEmpty()) { + System.out.println(ClickfitMessages.EMPTY_WORKOUT_LIST_MESSAGE); + return; + } + assert workouts.size() > 0 : "List should be non empty at this point"; + int workoutNumber = Parser.parseStringToInteger(inputArguments); + int workoutIndex = workoutNumber - 1; // 0-indexing + if (isWorkoutNumberWithinRange(workoutNumber)) { + generateWorkoutParameters(workouts.get(workoutIndex)); + System.out.println(ClickfitMessages.getDeleteWorkoutSuccessMessage( + workoutDescription, workoutDate, workoutTime, caloriesBurned)); + workouts.remove(workoutIndex); + WORKOUT_TRACKER_LOGGER.log(Level.INFO, "Successfully deleted workout."); + } else { + WORKOUT_TRACKER_LOGGER.log(Level.WARNING, "Failed to delete workout."); + throw new DeleteWorkoutException(); + } + } + + /** + * Prints out the list of recorded workouts. + * Either a filtered list based off a certain date or the full workout list can be printed. + * + * @param inputArguments Date to use as a filter in the format dd/mm/yyyy. + * If the input is all the full list of all workouts recorded is printed. + */ + public void listWorkouts(String inputArguments) throws WorkoutException { + if (inputArguments.equals(INPUT_ALL)) { + listAllWorkouts(); + } else { + listWorkoutsOnDate(inputArguments); + } + } + + /** + * Prints out the full list of all recorded workouts. + * + * @throws WorkoutException If there are issues during generation of parameters when printing all workouts. + */ + public void listAllWorkouts() throws WorkoutException { + WORKOUT_TRACKER_LOGGER.log(Level.INFO, "Starting to try and list workouts."); + if (isWorkoutListEmpty()) { + System.out.println(ClickfitMessages.EMPTY_WORKOUT_LIST_MESSAGE); + return; + } + assert workouts.size() > 0 : "List should be non empty at this point"; + System.out.println(ClickfitMessages.FULL_WORKOUT_LIST_MESSAGE); + int currentIndex = 1; + for (String workout : workouts) { + generateWorkoutParameters(workout); + System.out.println(currentIndex + ". " + workoutDescription); + System.out.println("Calories burned: " + caloriesBurned); + System.out.println("Date: " + workoutDate); + System.out.println("Time: " + workoutTime + System.lineSeparator() + + Ui.HORIZONTAL_BAR_SHORT); + currentIndex++; + } + System.out.println(ClickfitMessages.getTotalWorkoutsDoneMessage(workouts.size())); + WORKOUT_TRACKER_LOGGER.log(Level.INFO, "Successfully listed workouts."); + } + + /** + * Prints out a filtered workout list based off a certain date. + * + * @param inputArguments Date to use as a filter in the format dd/mm/yyyy. + * If the input is all the full list of all workouts recorded is printed. + * @throws WorkoutException If there are issues during generation of parameters when printing all workouts. + */ + public void listWorkoutsOnDate(String inputArguments) throws WorkoutException { + ArrayList filteredWorkoutList = (ArrayList) workouts.stream() + .filter((t) -> Parser.getDate(t).equals(inputArguments)).collect(Collectors.toList()); + if (filteredWorkoutList.isEmpty()) { + if (inputArguments.equals(Parser.getSystemDate())) { + System.out.println(ClickfitMessages.EMPTY_WORKOUT_LIST_TODAY_MESSAGE); + } else { + System.out.println(ClickfitMessages.getEmptyWorkoutListOnDateMessage(inputArguments)); + } + } else { + if (inputArguments.equals(Parser.getSystemDate())) { + System.out.println(ClickfitMessages.WORKOUTS_RECORDED_TODAY_MESSAGE); + } else { + System.out.println(ClickfitMessages.getWorkoutsOnDateMessage(inputArguments)); + } + int currentIndex = 1; + int totalCaloriesBurned = 0; + for (String workout : filteredWorkoutList) { + generateWorkoutParameters(workout); + System.out.println(currentIndex + ". " + workoutDescription); + System.out.println("Calories burned: " + caloriesBurned); + System.out.println("Date: " + workoutDate); + System.out.println("Time: " + workoutTime + System.lineSeparator() + + Ui.HORIZONTAL_BAR_SHORT); + totalCaloriesBurned += caloriesBurned; + currentIndex++; + } + System.out.println(ClickfitMessages.getTotalCaloriesBurnedMessage(totalCaloriesBurned)); + } + } + + /** + * Returns the calories burned on a particular date based on the workouts recorded in the workout list. + * + * @param date Date to use as a filter in the format dd/mm/yyyy when getting total calories. + * @return int The total calories burned on the date. If no workouts recorded on total calories burned is 0. + * @throws WorkoutException If there are issues getting calories burned from workouts. + */ + public int getCaloriesBurned(String date) throws WorkoutException { + ArrayList filteredWorkoutList = (ArrayList) workouts.stream() + .filter((t) -> Parser.getDateNoDateTracker(t).equals(date)).collect(Collectors.toList()); + int totalCaloriesBurned = 0; + if (!filteredWorkoutList.isEmpty()) { + for (String workout : workouts) { + totalCaloriesBurned += Parser.getCaloriesBurnedForWorkout(workout); + } + } + return totalCaloriesBurned; + } + + /** + * Checks whether input argument is null. + * + * @param inputArguments Arguments input by the user that come after the command word. + * @throws WorkoutException If input argument is null. + */ + public void nullArgumentCheck(String inputArguments) throws WorkoutException { + if (inputArguments == null) { + WORKOUT_TRACKER_LOGGER.log(Level.WARNING, "User input argument(s) is null."); + throw new WorkoutNullArgumentException(); + } + WORKOUT_TRACKER_LOGGER.log(Level.INFO, "User input argument(s) is not null."); + } + + /** + * Checks whether the list of recorded workouts is empty. + * + * @return boolean true if workout list is empty, false otherwise. + */ + public boolean isWorkoutListEmpty() { + return workouts.isEmpty(); + } + + /** + * Checks whether the workout index is within range. + * This check is done under the assumption that ones-indexing is used. + * + * @param workoutNumber Index of the workout. + * @return boolean true if within range, false otherwise. + */ + public boolean isWorkoutNumberWithinRange(int workoutNumber) { + int upperBound = workouts.size(); + int lowerBound = LOWER_BOUND_INDEX_NON_EMPTY_LIST_ONES_INDEXING; + return (workoutNumber >= lowerBound) && (workoutNumber <= upperBound); + } + + /** + * Checks whether the description of the workout is missing in the user input. + * + * @param inputArguments Arguments input by the user that come after the command word. + * @throws WorkoutException If unable to find description or find separators. + */ + public void missingDescriptionCheck(String inputArguments) throws WorkoutException { + int indexOfFirstCalorieSeparator = inputArguments.indexOf(Parser.CALORIE_SEPARATOR.trim()); + String subStringBeforeCalorieSeparator = ""; + if (indexOfFirstCalorieSeparator != -1) { // calorie separator not found + subStringBeforeCalorieSeparator = inputArguments.substring(0, indexOfFirstCalorieSeparator).trim(); + } else { + throw new MissingWorkoutCalorieSeparatorException(); + } + if (subStringBeforeCalorieSeparator.isEmpty()) { + WORKOUT_TRACKER_LOGGER.log(Level.WARNING, "Description is missing in user input arguments."); + throw new MissingWorkoutDescriptionException(); + } + WORKOUT_TRACKER_LOGGER.log(Level.INFO, "Description is present in user input arguments."); + } + + /** + * Checks whether a duplicate workout already exists in the list. + * + * @param inputArguments The arguments of the workout to be potentially added. + * @throws WorkoutException If there are duplicate workouts detected. + */ + public void duplicateWorkoutCheck(String inputArguments) throws WorkoutException { + for (String w : workouts) { + if (inputArguments.trim().equals(w)) { + throw new DuplicateWorkoutException(); + } + } + } +} diff --git a/src/main/java/seedu/duke/exceptions/DukeException.java b/src/main/java/seedu/duke/exceptions/DukeException.java new file mode 100644 index 0000000000..c83d4f3c07 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/DukeException.java @@ -0,0 +1,8 @@ +package seedu.duke.exceptions; + +public class DukeException extends Exception { + public DukeException(String errorMessage) { + super(errorMessage); + System.out.println(errorMessage); + } +} \ No newline at end of file diff --git a/src/main/java/seedu/duke/exceptions/LoadException.java b/src/main/java/seedu/duke/exceptions/LoadException.java new file mode 100644 index 0000000000..2c9bddfcad --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/LoadException.java @@ -0,0 +1,4 @@ +package seedu.duke.exceptions; + +public class LoadException extends Exception { +} diff --git a/src/main/java/seedu/duke/exceptions/fluid/DeleteEmptyFluidListException.java b/src/main/java/seedu/duke/exceptions/fluid/DeleteEmptyFluidListException.java new file mode 100644 index 0000000000..c9711930e8 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/fluid/DeleteEmptyFluidListException.java @@ -0,0 +1,14 @@ +package seedu.duke.exceptions.fluid; + +//@@author pragyan01 +/** + * Custom exception for when user tries to delete a fluid entry but fluid array is empty. + * + * @author pragyan01 + */ +public class DeleteEmptyFluidListException extends FluidExceptions { + @Override + public String getMessage() { + return "You don't have any fluid entries to delete. You may wish to add a fluid entry first."; + } +} diff --git a/src/main/java/seedu/duke/exceptions/fluid/FluidExceptions.java b/src/main/java/seedu/duke/exceptions/fluid/FluidExceptions.java new file mode 100644 index 0000000000..5b12c6710f --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/fluid/FluidExceptions.java @@ -0,0 +1,15 @@ +package seedu.duke.exceptions.fluid; + +//@@author pragyan01 +/** + * Custom exception for when unknown error related to fluid tracker occurs. + * Parent exception class for fluid exceptions. + * + * @author pragyan01 + */ +public class FluidExceptions extends Exception { + @Override + public String getMessage() { + return "An unknown error has occurred in FluidTracker"; + } +} diff --git a/src/main/java/seedu/duke/exceptions/fluid/InvalidFluidDescription.java b/src/main/java/seedu/duke/exceptions/fluid/InvalidFluidDescription.java new file mode 100644 index 0000000000..274ba47205 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/fluid/InvalidFluidDescription.java @@ -0,0 +1,14 @@ +package seedu.duke.exceptions.fluid; + +//@@author pragyan01 +/** + * Custom exception for when user tries to add a fluid entry without providing a description. + * + * @author pragyan01 + */ +public class InvalidFluidDescription extends FluidExceptions { + @Override + public String getMessage() { + return "Please enter a valid fluid description"; + } +} diff --git a/src/main/java/seedu/duke/exceptions/fluid/NegativeVolumeException.java b/src/main/java/seedu/duke/exceptions/fluid/NegativeVolumeException.java new file mode 100644 index 0000000000..68a305c048 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/fluid/NegativeVolumeException.java @@ -0,0 +1,8 @@ +package seedu.duke.exceptions.fluid; + +public class NegativeVolumeException extends FluidExceptions { + @Override + public String getMessage() { + return "Please input a positive integer value for your volume!"; + } +} diff --git a/src/main/java/seedu/duke/exceptions/fluid/NoCaloriesEntered.java b/src/main/java/seedu/duke/exceptions/fluid/NoCaloriesEntered.java new file mode 100644 index 0000000000..f5d1ba3a4b --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/fluid/NoCaloriesEntered.java @@ -0,0 +1,14 @@ +package seedu.duke.exceptions.fluid; + +//@@author pragyan01 +/** + * Custom exception for when user tries to add a fluid entry without providing its calorie intake. + * + * @author pragyan01 + */ +public class NoCaloriesEntered extends FluidExceptions { + @Override + public String getMessage() { + return "Please enter calories."; + } +} diff --git a/src/main/java/seedu/duke/exceptions/fluid/NoDeleteFluidIndexException.java b/src/main/java/seedu/duke/exceptions/fluid/NoDeleteFluidIndexException.java new file mode 100644 index 0000000000..4d81255a91 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/fluid/NoDeleteFluidIndexException.java @@ -0,0 +1,14 @@ +package seedu.duke.exceptions.fluid; + +//@@author pragyan01 +/** + * Custom exception for when user tries to delete a fluid entry of an invalid index. + * + * @author pragyan01 + */ +public class NoDeleteFluidIndexException extends FluidExceptions { + @Override + public String getMessage() { + return "Please enter a valid fluid index. You may wish to list to check the index numbers."; + } +} diff --git a/src/main/java/seedu/duke/exceptions/fluid/NoFluidToDelete.java b/src/main/java/seedu/duke/exceptions/fluid/NoFluidToDelete.java new file mode 100644 index 0000000000..94f76d7404 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/fluid/NoFluidToDelete.java @@ -0,0 +1,14 @@ +package seedu.duke.exceptions.fluid; + +//@@author pragyan01 +/** + * Custom exception for when user tries to delete a fluid entry which does not exist. + * + * @author pragyan01 + */ +public class NoFluidToDelete extends FluidExceptions { + @Override + public String getMessage() { + return "This fluid entry does not exist. You may try again or wish to add a fluid entry first."; + } +} diff --git a/src/main/java/seedu/duke/exceptions/fluid/NoVolumeEntered.java b/src/main/java/seedu/duke/exceptions/fluid/NoVolumeEntered.java new file mode 100644 index 0000000000..4254ddbb50 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/fluid/NoVolumeEntered.java @@ -0,0 +1,14 @@ +package seedu.duke.exceptions.fluid; + +//@@author pragyan01 +/** + * Custom exception for when user tries to add a fluid entry without providing its volume. + * + * @author pragyan01 + */ +public class NoVolumeEntered extends FluidExceptions { + @Override + public String getMessage() { + return "Please enter volume."; + } +} diff --git a/src/main/java/seedu/duke/exceptions/foodbank/DuplicateFood.java b/src/main/java/seedu/duke/exceptions/foodbank/DuplicateFood.java new file mode 100644 index 0000000000..8fec16f052 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/foodbank/DuplicateFood.java @@ -0,0 +1,9 @@ +package seedu.duke.exceptions.foodbank; + +//@@author pragyan01 +public class DuplicateFood extends FoodBankException { + @Override + public String getMessage() { + return "This food entry already exists."; + } +} diff --git a/src/main/java/seedu/duke/exceptions/foodbank/EmptyFluidBankException.java b/src/main/java/seedu/duke/exceptions/foodbank/EmptyFluidBankException.java new file mode 100644 index 0000000000..48f0b70654 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/foodbank/EmptyFluidBankException.java @@ -0,0 +1,9 @@ +package seedu.duke.exceptions.foodbank; + +//@@author pragyan01 +public class EmptyFluidBankException extends FoodBankException { + @Override + public String getMessage() { + return "You don't have any fluids in your library!"; + } +} diff --git a/src/main/java/seedu/duke/exceptions/foodbank/EmptyFoodDescription.java b/src/main/java/seedu/duke/exceptions/foodbank/EmptyFoodDescription.java new file mode 100644 index 0000000000..2e6eea212b --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/foodbank/EmptyFoodDescription.java @@ -0,0 +1,14 @@ +package seedu.duke.exceptions.foodbank; + +//@@author pragyan01 +public class EmptyFoodDescription extends FoodBankException { + @Override + public String getMessage() { + return "Please enter a meal/fluid description, volume for fluids," + + " and optionally, calories if not previously added to the library! " + + System.lineSeparator() + + "e.g \"add meal {DESCRIPTION} /c {CALORIES}\" OR \"add meal {DESCRIPTION}\"" + + System.lineSeparator() + + "e.g \"add fluid {DESCRIPTION} /c {CALORIES} /v {volume}\" OR \"add fluid {DESCRIPTION}\""; + } +} diff --git a/src/main/java/seedu/duke/exceptions/foodbank/EmptyLibraryDescription.java b/src/main/java/seedu/duke/exceptions/foodbank/EmptyLibraryDescription.java new file mode 100644 index 0000000000..a7ad53286c --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/foodbank/EmptyLibraryDescription.java @@ -0,0 +1,13 @@ +package seedu.duke.exceptions.foodbank; + +public class EmptyLibraryDescription extends FoodBankException { + @Override + public String getMessage() { + return "Please enter a food description," + + " and its associated calories" + + System.lineSeparator() + + "e.g \"library addmeal {DESCRIPTION} /c {CALORIES}\"" + + System.lineSeparator() + + "e.g \"library addfluid {DESCRIPTION} /c {CALORIES}\""; + } +} diff --git a/src/main/java/seedu/duke/exceptions/foodbank/EmptyMealBankException.java b/src/main/java/seedu/duke/exceptions/foodbank/EmptyMealBankException.java new file mode 100644 index 0000000000..368ced6a52 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/foodbank/EmptyMealBankException.java @@ -0,0 +1,9 @@ +package seedu.duke.exceptions.foodbank; + +//@@author pragyan01 +public class EmptyMealBankException extends FoodBankException { + @Override + public String getMessage() { + return "You don't have any meals in your library!"; + } +} diff --git a/src/main/java/seedu/duke/exceptions/foodbank/FoodBankException.java b/src/main/java/seedu/duke/exceptions/foodbank/FoodBankException.java new file mode 100644 index 0000000000..46dd19e4f8 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/foodbank/FoodBankException.java @@ -0,0 +1,9 @@ +package seedu.duke.exceptions.foodbank; + +//@@author pragyan01 +public class FoodBankException extends Exception { + @Override + public String getMessage() { + return "Unknown Error occurred"; + } +} diff --git a/src/main/java/seedu/duke/exceptions/foodbank/IncorrectLibraryAddFormatException.java b/src/main/java/seedu/duke/exceptions/foodbank/IncorrectLibraryAddFormatException.java new file mode 100644 index 0000000000..24a85de438 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/foodbank/IncorrectLibraryAddFormatException.java @@ -0,0 +1,8 @@ +package seedu.duke.exceptions.foodbank; + +public class IncorrectLibraryAddFormatException extends FoodBankException { + @Override + public String getMessage() { + return "Please key in \"NAME_OF_FOOD /c CALORIES\" to add your food to the library!"; + } +} diff --git a/src/main/java/seedu/duke/exceptions/foodbank/InvalidFluidIndexException.java b/src/main/java/seedu/duke/exceptions/foodbank/InvalidFluidIndexException.java new file mode 100644 index 0000000000..e69638016a --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/foodbank/InvalidFluidIndexException.java @@ -0,0 +1,13 @@ +package seedu.duke.exceptions.foodbank; + +//@@author VishalJeyaram +/** + * Custom exception for when the user keys in a fluid index that does not exist or lies out of the + * range of allowed fluid indexes in the fluid library. + */ +public class InvalidFluidIndexException extends FoodBankException { + @Override + public String getMessage() { + return "Please enter a valid fluid index! Use \"library listfluids\" to see the fluid indexes"; + } +} diff --git a/src/main/java/seedu/duke/exceptions/foodbank/InvalidMealIndexException.java b/src/main/java/seedu/duke/exceptions/foodbank/InvalidMealIndexException.java new file mode 100644 index 0000000000..88c447ef99 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/foodbank/InvalidMealIndexException.java @@ -0,0 +1,13 @@ +package seedu.duke.exceptions.foodbank; + +//@@author VishalJeyaram +/** + * Custom exception for when the user keys in a meal index that does not exist or lies out of the + * range of allowed meal indexes in the meal library. + */ +public class InvalidMealIndexException extends FoodBankException { + @Override + public String getMessage() { + return "Please enter a valid meal index! Use \"library listmeals\" to see the meal indexes"; + } +} diff --git a/src/main/java/seedu/duke/exceptions/foodbank/NegativeCaloriesException.java b/src/main/java/seedu/duke/exceptions/foodbank/NegativeCaloriesException.java new file mode 100644 index 0000000000..d6a82a2975 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/foodbank/NegativeCaloriesException.java @@ -0,0 +1,8 @@ +package seedu.duke.exceptions.foodbank; + +public class NegativeCaloriesException extends FoodBankException { + @Override + public String getMessage() { + return "Please input a positive integer value for your calories!"; + } +} diff --git a/src/main/java/seedu/duke/exceptions/foodbank/NoCaloriesException.java b/src/main/java/seedu/duke/exceptions/foodbank/NoCaloriesException.java new file mode 100644 index 0000000000..856ec4163e --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/foodbank/NoCaloriesException.java @@ -0,0 +1,8 @@ +package seedu.duke.exceptions.foodbank; + +public class NoCaloriesException extends FoodBankException { + @Override + public String getMessage() { + return "Please enter the calories of your food!"; + } +} diff --git a/src/main/java/seedu/duke/exceptions/foodbank/NoFoodFoundException.java b/src/main/java/seedu/duke/exceptions/foodbank/NoFoodFoundException.java new file mode 100644 index 0000000000..32c3f34d9b --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/foodbank/NoFoodFoundException.java @@ -0,0 +1,15 @@ +package seedu.duke.exceptions.foodbank; + +import seedu.duke.ClickfitMessages; + +//@@author VishalJeyaram +/** + * Custom exception for when the user keys in a meal or fluid that does not exist within the + * meal or fluid library. + */ +public class NoFoodFoundException extends FoodBankException { + @Override + public String getMessage() { + return ClickfitMessages.FOOD_BANK_EXCEPTION_MESSAGE; + } +} diff --git a/src/main/java/seedu/duke/exceptions/foodbank/NoFoodIndexException.java b/src/main/java/seedu/duke/exceptions/foodbank/NoFoodIndexException.java new file mode 100644 index 0000000000..178fc8b889 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/foodbank/NoFoodIndexException.java @@ -0,0 +1,13 @@ +package seedu.duke.exceptions.foodbank; + +//@@author VishalJeyaram +/** + * Custom exception for when the user keys in a meal ot fluid that already exists within the + * meal or fluid library. + */ +public class NoFoodIndexException extends FoodBankException { + @Override + public String getMessage() { + return "Please enter a food index!"; + } +} diff --git a/src/main/java/seedu/duke/exceptions/meal/EmptyMealListException.java b/src/main/java/seedu/duke/exceptions/meal/EmptyMealListException.java new file mode 100644 index 0000000000..bddb87d959 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/meal/EmptyMealListException.java @@ -0,0 +1,12 @@ +package seedu.duke.exceptions.meal; + +//@@author VishalJeyaram +/** + * Custom exception for when user attempts to delete a meal despite the meal list being empty. + */ +public class EmptyMealListException extends MealException { + @Override + public String getMessage() { + return "Your meal list is empty!"; + } +} diff --git a/src/main/java/seedu/duke/exceptions/meal/MealException.java b/src/main/java/seedu/duke/exceptions/meal/MealException.java new file mode 100644 index 0000000000..1fef4ef162 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/meal/MealException.java @@ -0,0 +1,12 @@ +package seedu.duke.exceptions.meal; + +//@@author VishalJeyaram +/** + * Custom exception for all errors that occur with respect to the Meal class. + */ +public class MealException extends Exception { + @Override + public String getMessage() { + return "An unknown error has occurred"; + } +} diff --git a/src/main/java/seedu/duke/exceptions/meal/NoDeleteMealIndexException.java b/src/main/java/seedu/duke/exceptions/meal/NoDeleteMealIndexException.java new file mode 100644 index 0000000000..cef66585f2 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/meal/NoDeleteMealIndexException.java @@ -0,0 +1,12 @@ +package seedu.duke.exceptions.meal; + +//@@author VishalJeyaram +/** + * Custom exception for when user does not key in the index of the meal he or she wishes to delete. + */ +public class NoDeleteMealIndexException extends MealException { + @Override + public String getMessage() { + return "Please enter the index of the meal you wish to delete!"; + } +} diff --git a/src/main/java/seedu/duke/exceptions/meal/NoMealDetailsException.java b/src/main/java/seedu/duke/exceptions/meal/NoMealDetailsException.java new file mode 100644 index 0000000000..351274d958 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/meal/NoMealDetailsException.java @@ -0,0 +1,13 @@ +package seedu.duke.exceptions.meal; + +//@@author VishalJeyaram +/** + * Custom exception for when user does not key any of the details of the meal he or she wishes to add, + * such as the description and calories. + */ +public class NoMealDetailsException extends MealException { + @Override + public String getMessage() { + return "Please enter the details of the meal you wish to add!"; + } +} diff --git a/src/main/java/seedu/duke/exceptions/meal/NoSuchMealIndexException.java b/src/main/java/seedu/duke/exceptions/meal/NoSuchMealIndexException.java new file mode 100644 index 0000000000..146169206a --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/meal/NoSuchMealIndexException.java @@ -0,0 +1,13 @@ +package seedu.duke.exceptions.meal; + +//@@author VishalJeyaram +/** + * Custom exception for when the user keys in a meal index that does not exist or lies out of the + * range of allowed meal indexes. + */ +public class NoSuchMealIndexException extends MealException { + @Override + public String getMessage() { + return "Please enter a proper meal index. Use \"list meals all\" to view each meal's index"; + } +} diff --git a/src/main/java/seedu/duke/exceptions/schedule/DeleteScheduleException.java b/src/main/java/seedu/duke/exceptions/schedule/DeleteScheduleException.java new file mode 100644 index 0000000000..1ff24a0ed5 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/schedule/DeleteScheduleException.java @@ -0,0 +1,8 @@ +package seedu.duke.exceptions.schedule; + +public class DeleteScheduleException extends ScheduleException { + @Override + public String getMessage() { + return "Failed to delete that scheduled workout! Please enter an Integer within range."; + } +} diff --git a/src/main/java/seedu/duke/exceptions/schedule/DuplicateRescheduledWorkoutException.java b/src/main/java/seedu/duke/exceptions/schedule/DuplicateRescheduledWorkoutException.java new file mode 100644 index 0000000000..a31b3890ee --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/schedule/DuplicateRescheduledWorkoutException.java @@ -0,0 +1,9 @@ +package seedu.duke.exceptions.schedule; + +public class DuplicateRescheduledWorkoutException extends ScheduleException { + @Override + public String getMessage() { + return "Failed to reschedule that scheduled workout. " + System.lineSeparator() + + "A duplicate scheduled workout with the same arguments is already in your schedule."; + } +} diff --git a/src/main/java/seedu/duke/exceptions/schedule/DuplicateScheduledWorkoutException.java b/src/main/java/seedu/duke/exceptions/schedule/DuplicateScheduledWorkoutException.java new file mode 100644 index 0000000000..86b7792e64 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/schedule/DuplicateScheduledWorkoutException.java @@ -0,0 +1,9 @@ +package seedu.duke.exceptions.schedule; + +public class DuplicateScheduledWorkoutException extends ScheduleException { + @Override + public String getMessage() { + return "Failed to add that scheduled workout. " + System.lineSeparator() + + "A duplicate scheduled workout with the same arguments is already in your schedule."; + } +} diff --git a/src/main/java/seedu/duke/exceptions/schedule/GetActivityException.java b/src/main/java/seedu/duke/exceptions/schedule/GetActivityException.java new file mode 100644 index 0000000000..29e7b319c0 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/schedule/GetActivityException.java @@ -0,0 +1,8 @@ +package seedu.duke.exceptions.schedule; + +public class GetActivityException extends ScheduleException { + @Override + public String getMessage() { + return "Error getting activity quantifiers."; + } +} diff --git a/src/main/java/seedu/duke/exceptions/schedule/InvalidActivityFormatException.java b/src/main/java/seedu/duke/exceptions/schedule/InvalidActivityFormatException.java new file mode 100644 index 0000000000..5d7b5e2c57 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/schedule/InvalidActivityFormatException.java @@ -0,0 +1,13 @@ +package seedu.duke.exceptions.schedule; + +public class InvalidActivityFormatException extends ScheduleException { + @Override + public String getMessage() { + return "There was an issue getting your activity breakdown." + System.lineSeparator() + + "Please enter a positive integer [distance in metres] for distance based " + + "activities(swimming/running/cycling)." + System.lineSeparator() + "E.g. running:8000" + "" + + System.lineSeparator() + "Enter two positive integers in the format [set]x[reps] for everything else." + + System.lineSeparator() + "E.g. bench press:3x12" + System.lineSeparator() + + "For multiple activities please separate them by \",\""; + } +} diff --git a/src/main/java/seedu/duke/exceptions/schedule/InvalidScheduleDescriptionException.java b/src/main/java/seedu/duke/exceptions/schedule/InvalidScheduleDescriptionException.java new file mode 100644 index 0000000000..2425a7726c --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/schedule/InvalidScheduleDescriptionException.java @@ -0,0 +1,9 @@ +package seedu.duke.exceptions.schedule; + +public class InvalidScheduleDescriptionException extends ScheduleException { + @Override + public String getMessage() { + return "Invalid schedule description detected!" + System.lineSeparator() + + "Please enter a valid description for your workout!"; + } +} diff --git a/src/main/java/seedu/duke/exceptions/schedule/MissingActivityQuantifierException.java b/src/main/java/seedu/duke/exceptions/schedule/MissingActivityQuantifierException.java new file mode 100644 index 0000000000..915efdf4af --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/schedule/MissingActivityQuantifierException.java @@ -0,0 +1,9 @@ +package seedu.duke.exceptions.schedule; + +public class MissingActivityQuantifierException extends ScheduleException { + @Override + public String getMessage() { + return "Missing activity quantifier \"x\" detected." + System.lineSeparator() + + "Please enter your [sets]x[reps] for your non-distance based workout activities."; + } +} diff --git a/src/main/java/seedu/duke/exceptions/schedule/MissingActivitySplitterException.java b/src/main/java/seedu/duke/exceptions/schedule/MissingActivitySplitterException.java new file mode 100644 index 0000000000..12bd999e2f --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/schedule/MissingActivitySplitterException.java @@ -0,0 +1,10 @@ +package seedu.duke.exceptions.schedule; + +public class MissingActivitySplitterException extends ScheduleException { + @Override + public String getMessage() { + return "Missing activity splitter \":\" detected." + System.lineSeparator() + + "Please enter [activity name]:[sets]x[reps] " + + "or [activity name]:[distance in metres] for your workout activities"; + } +} diff --git a/src/main/java/seedu/duke/exceptions/schedule/MissingScheduleDescriptionException.java b/src/main/java/seedu/duke/exceptions/schedule/MissingScheduleDescriptionException.java new file mode 100644 index 0000000000..5d0665c35c --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/schedule/MissingScheduleDescriptionException.java @@ -0,0 +1,9 @@ +package seedu.duke.exceptions.schedule; + +public class MissingScheduleDescriptionException extends ScheduleException { + @Override + public String getMessage() { + return "I am sorry... it appears the description is missing." + System.lineSeparator() + + "Please enter a description for your scheduled workout!"; + } +} diff --git a/src/main/java/seedu/duke/exceptions/schedule/MissingScheduleSeparatorException.java b/src/main/java/seedu/duke/exceptions/schedule/MissingScheduleSeparatorException.java new file mode 100644 index 0000000000..62b3aad893 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/schedule/MissingScheduleSeparatorException.java @@ -0,0 +1,10 @@ +package seedu.duke.exceptions.schedule; + +public class MissingScheduleSeparatorException extends ScheduleException { + @Override + public String getMessage() { + return "CLI.ckFit is having difficulties finding the separators..." + System.lineSeparator() + + "Please enter in the format: add schedule [workout_description] /d [dd/mm/yyyy] /t [hh:mm]" + + System.lineSeparator() + "Do remember to put spaces between your separators."; + } +} diff --git a/src/main/java/seedu/duke/exceptions/schedule/NoScheduleIndexException.java b/src/main/java/seedu/duke/exceptions/schedule/NoScheduleIndexException.java new file mode 100644 index 0000000000..f7c8b958de --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/schedule/NoScheduleIndexException.java @@ -0,0 +1,8 @@ +package seedu.duke.exceptions.schedule; + +public class NoScheduleIndexException extends ScheduleException { + @Override + public String getMessage() { + return "Please enter the schedule index in the format: delete schedule [index]"; + } +} diff --git a/src/main/java/seedu/duke/exceptions/schedule/ScheduleException.java b/src/main/java/seedu/duke/exceptions/schedule/ScheduleException.java new file mode 100644 index 0000000000..342eaaee3e --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/schedule/ScheduleException.java @@ -0,0 +1,8 @@ +package seedu.duke.exceptions.schedule; + +public class ScheduleException extends Exception { + @Override + public String getMessage() { + return "An unknown error has occurred in ScheduleTracker"; + } +} diff --git a/src/main/java/seedu/duke/exceptions/schedule/ScheduleNullArgumentException.java b/src/main/java/seedu/duke/exceptions/schedule/ScheduleNullArgumentException.java new file mode 100644 index 0000000000..39f2369c8f --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/schedule/ScheduleNullArgumentException.java @@ -0,0 +1,11 @@ +package seedu.duke.exceptions.schedule; + +public class ScheduleNullArgumentException extends ScheduleException { + @Override + public String getMessage() { + return "Please enter arguments in the format: add schedule [workout_description] " + + "/d [dd/mm/yyyy] /t [hh:mm]" + System.lineSeparator() + "Add /a followed by " + + "[activity_name]:[distance] " + + "or [activity_name]:[sets]x[reps] to schedule a more detailed workout!"; + } +} diff --git a/src/main/java/seedu/duke/exceptions/schedule/UnnecessaryQuantifierException.java b/src/main/java/seedu/duke/exceptions/schedule/UnnecessaryQuantifierException.java new file mode 100644 index 0000000000..2f22eb714c --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/schedule/UnnecessaryQuantifierException.java @@ -0,0 +1,11 @@ +package seedu.duke.exceptions.schedule; + +public class UnnecessaryQuantifierException extends ScheduleException { + @Override + public String getMessage() { + return "Unnecessary activity quantifier splitter \"x\" detected." + System.lineSeparator() + + "Please enter [activity name]:[distance in metres] for distance based workout activities if your" + + System.lineSeparator() + "activity name is either running/swimming/cycling." + + System.lineSeparator() + "E.g. running:8000"; + } +} diff --git a/src/main/java/seedu/duke/exceptions/weight/AddWeightException.java b/src/main/java/seedu/duke/exceptions/weight/AddWeightException.java new file mode 100644 index 0000000000..86969106e0 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/weight/AddWeightException.java @@ -0,0 +1,10 @@ +package seedu.duke.exceptions.weight; + +import seedu.duke.ClickfitMessages; + +public class AddWeightException extends WeightException { + @Override + public String getMessage() { + return ClickfitMessages.WEIGHT_ADD_FORMAT_ERROR; + } +} diff --git a/src/main/java/seedu/duke/exceptions/weight/DeleteWeightException.java b/src/main/java/seedu/duke/exceptions/weight/DeleteWeightException.java new file mode 100644 index 0000000000..d50ad660f6 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/weight/DeleteWeightException.java @@ -0,0 +1,10 @@ +package seedu.duke.exceptions.weight; + +import seedu.duke.ClickfitMessages; + +public class DeleteWeightException extends WeightException { + @Override + public String getMessage() { + return ClickfitMessages.WEIGHT_DELETE_FORMAT_ERROR; + } +} diff --git a/src/main/java/seedu/duke/exceptions/weight/DeleteWeightIndexException.java b/src/main/java/seedu/duke/exceptions/weight/DeleteWeightIndexException.java new file mode 100644 index 0000000000..4186fbb417 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/weight/DeleteWeightIndexException.java @@ -0,0 +1,10 @@ +package seedu.duke.exceptions.weight; + +import seedu.duke.ClickfitMessages; + +public class DeleteWeightIndexException extends WeightException { + @Override + public String getMessage() { + return ClickfitMessages.WEIGHT_DELETE_INDEX_ERROR; + } +} diff --git a/src/main/java/seedu/duke/exceptions/weight/FutureWeightException.java b/src/main/java/seedu/duke/exceptions/weight/FutureWeightException.java new file mode 100644 index 0000000000..f78c2fa53d --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/weight/FutureWeightException.java @@ -0,0 +1,10 @@ +package seedu.duke.exceptions.weight; + +import seedu.duke.ClickfitMessages; + +public class FutureWeightException extends WeightException { + @Override + public String getMessage() { + return "Please input a date before or equal to today's date!"; + } +} diff --git a/src/main/java/seedu/duke/exceptions/weight/NoWeightsException.java b/src/main/java/seedu/duke/exceptions/weight/NoWeightsException.java new file mode 100644 index 0000000000..b2bb3054b8 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/weight/NoWeightsException.java @@ -0,0 +1,10 @@ +package seedu.duke.exceptions.weight; + +import seedu.duke.ClickfitMessages; + +public class NoWeightsException extends WeightException { + @Override + public String getMessage() { + return ClickfitMessages.WEIGHT_EMPTY_ERROR; + } +} diff --git a/src/main/java/seedu/duke/exceptions/weight/WeightException.java b/src/main/java/seedu/duke/exceptions/weight/WeightException.java new file mode 100644 index 0000000000..550637d99a --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/weight/WeightException.java @@ -0,0 +1,8 @@ +package seedu.duke.exceptions.weight; + +public class WeightException extends Exception { + @Override + public String getMessage() { + return "An unknown error has occurred in Weight Tracker"; + } +} diff --git a/src/main/java/seedu/duke/exceptions/workout/DeleteWorkoutException.java b/src/main/java/seedu/duke/exceptions/workout/DeleteWorkoutException.java new file mode 100644 index 0000000000..bee5c91f50 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/workout/DeleteWorkoutException.java @@ -0,0 +1,8 @@ +package seedu.duke.exceptions.workout; + +public class DeleteWorkoutException extends WorkoutException { + @Override + public String getMessage() { + return "Failed to delete that workout! Please enter an Integer within range."; + } +} diff --git a/src/main/java/seedu/duke/exceptions/workout/DuplicateWorkoutException.java b/src/main/java/seedu/duke/exceptions/workout/DuplicateWorkoutException.java new file mode 100644 index 0000000000..7840dff660 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/workout/DuplicateWorkoutException.java @@ -0,0 +1,9 @@ +package seedu.duke.exceptions.workout; + +public class DuplicateWorkoutException extends WorkoutException { + @Override + public String getMessage() { + return "Failed to add that workout. " + System.lineSeparator() + + "A duplicate workout with the same arguments is already in your list."; + } +} diff --git a/src/main/java/seedu/duke/exceptions/workout/MissingWorkoutCalorieSeparatorException.java b/src/main/java/seedu/duke/exceptions/workout/MissingWorkoutCalorieSeparatorException.java new file mode 100644 index 0000000000..b158ce1529 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/workout/MissingWorkoutCalorieSeparatorException.java @@ -0,0 +1,10 @@ +package seedu.duke.exceptions.workout; + +public class MissingWorkoutCalorieSeparatorException extends WorkoutException { + @Override + public String getMessage() { + return "CLI.ckFit is having difficulties finding the calorie separator /c" + System.lineSeparator() + + "Please minimally have the format: add workout [workout_description] /c [calories]" + + System.lineSeparator() + "Do remember to put spaces between your separators!"; + } +} diff --git a/src/main/java/seedu/duke/exceptions/workout/MissingWorkoutDescriptionException.java b/src/main/java/seedu/duke/exceptions/workout/MissingWorkoutDescriptionException.java new file mode 100644 index 0000000000..6ec31cb782 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/workout/MissingWorkoutDescriptionException.java @@ -0,0 +1,9 @@ +package seedu.duke.exceptions.workout; + +public class MissingWorkoutDescriptionException extends WorkoutException { + @Override + public String getMessage() { + return "I am sorry... it appears the description is missing." + System.lineSeparator() + + "Please enter a workout description!"; + } +} diff --git a/src/main/java/seedu/duke/exceptions/workout/NegativeWorkoutCalorieException.java b/src/main/java/seedu/duke/exceptions/workout/NegativeWorkoutCalorieException.java new file mode 100644 index 0000000000..b15d607056 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/workout/NegativeWorkoutCalorieException.java @@ -0,0 +1,8 @@ +package seedu.duke.exceptions.workout; + +public class NegativeWorkoutCalorieException extends WorkoutException { + @Override + public String getMessage() { + return "Negative calories detected... Please enter a positive integer for your calories burned"; + } +} diff --git a/src/main/java/seedu/duke/exceptions/workout/NoWorkoutIndexException.java b/src/main/java/seedu/duke/exceptions/workout/NoWorkoutIndexException.java new file mode 100644 index 0000000000..8c4a62f923 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/workout/NoWorkoutIndexException.java @@ -0,0 +1,8 @@ +package seedu.duke.exceptions.workout; + +public class NoWorkoutIndexException extends WorkoutException { + @Override + public String getMessage() { + return "Please enter the workout index in the format: delete workout [index]"; + } +} diff --git a/src/main/java/seedu/duke/exceptions/workout/WorkoutException.java b/src/main/java/seedu/duke/exceptions/workout/WorkoutException.java new file mode 100644 index 0000000000..93fe9020d8 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/workout/WorkoutException.java @@ -0,0 +1,8 @@ +package seedu.duke.exceptions.workout; + +public class WorkoutException extends Exception { + @Override + public String getMessage() { + return "An unknown error has occurred in WorkoutTracker"; + } +} diff --git a/src/main/java/seedu/duke/exceptions/workout/WorkoutNullArgumentException.java b/src/main/java/seedu/duke/exceptions/workout/WorkoutNullArgumentException.java new file mode 100644 index 0000000000..0ec2bbb787 --- /dev/null +++ b/src/main/java/seedu/duke/exceptions/workout/WorkoutNullArgumentException.java @@ -0,0 +1,10 @@ +package seedu.duke.exceptions.workout; + +public class WorkoutNullArgumentException extends WorkoutException { + public String getMessage() { + return "Please enter arguments in the format: " + + "add workout [workout_description] /c [calories] /d [date] /t [time]" + + System.lineSeparator() + "If [date] and [time] not specified, " + + "the system's current date and time will be taken instead."; + } +} diff --git a/src/main/java/seedu/duke/schedule/ScheduleTracker.java b/src/main/java/seedu/duke/schedule/ScheduleTracker.java new file mode 100644 index 0000000000..e122bde9cf --- /dev/null +++ b/src/main/java/seedu/duke/schedule/ScheduleTracker.java @@ -0,0 +1,431 @@ +package seedu.duke.schedule; + +import seedu.duke.ClickfitMessages; +import seedu.duke.Storage; +import seedu.duke.Parser; +import seedu.duke.exceptions.schedule.DuplicateRescheduledWorkoutException; +import seedu.duke.exceptions.schedule.DuplicateScheduledWorkoutException; +import seedu.duke.exceptions.schedule.InvalidActivityFormatException; +import seedu.duke.exceptions.schedule.DeleteScheduleException; +import seedu.duke.exceptions.schedule.MissingScheduleDescriptionException; +import seedu.duke.exceptions.schedule.MissingScheduleSeparatorException; +import seedu.duke.exceptions.schedule.NoScheduleIndexException; +import seedu.duke.exceptions.schedule.ScheduleException; +import seedu.duke.exceptions.schedule.ScheduleNullArgumentException; + +import java.io.File; +import java.io.FileNotFoundException; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Map; +import java.util.Scanner; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +//@@author arvejw + +/** + * Manages the tracking of scheduled workouts and various other operations such as adding new scheduled workouts and + * deleting them. + */ +public class ScheduleTracker { + + private ArrayList scheduledWorkouts; + private static final int LOWER_BOUND_INDEX_NON_EMPTY_LIST_ONES_INDEXING = 1; + private static final int FIRST_INDEX_IN_LIST = 0; + private static final int DAYS_IN_A_WEEK = 7; + private static final String INPUT_ALL = "all"; + public static final Logger SCHEDULE_TRACKER_LOGGER = Logger.getLogger("ScheduleTrackerLogger"); + + public ScheduleTracker() { + scheduledWorkouts = new ArrayList<>(); + SCHEDULE_TRACKER_LOGGER.setLevel(Level.SEVERE); + } + + /** + * Returns the private attribute scheduledWorkouts. + * + * @return Array List of scheduled workouts. + */ + public ArrayList getScheduledWorkouts() { + return scheduledWorkouts; + } + + /** + * Loads the schedule data from the data file. + */ + public void loadScheduleData() { + File dataFile = new File(Storage.SCHEDULE_FILE_PATH); + if (dataFile.length() == 0) { + return; + } + Scanner fileScanner; + try { + fileScanner = new Scanner(dataFile); + } catch (FileNotFoundException e) { + System.out.println(ClickfitMessages.SCHEDULE_DATA_NOT_FOUND); + return; + } + String currentLine = Parser.EMPTY_STRING; + boolean isDataLoadCorrectly = true; + while (fileScanner.hasNext()) { + currentLine = fileScanner.nextLine(); + if (currentLine.isEmpty()) { + continue; + } + try { + addScheduledWorkout(currentLine, true, false); + } catch (Exception e) { + isDataLoadCorrectly = false; + } + } + cleanUpScheduleList(); + if (!isDataLoadCorrectly) { + System.out.println(ClickfitMessages.INCORRECT_LOADING_SCHEDULE_DATA); + } + } + + /** + * Generates the parameters to be used to construct a ScheduledWorkout object. + * Parameters will be returned as a String[] of size 3. String[0] contains the workout description, + * String[1] contains the workout date, String[2] contains the workout time. + * + * @param inputArguments Arguments input by the user that come after the command word. + * @return String[] The generated parameters in a String array of size 3. + * @throws ScheduleException If there are issues generating schedule description. + */ + public String[] generateScheduledWorkoutParameters(String inputArguments) throws ScheduleException { + SCHEDULE_TRACKER_LOGGER.log(Level.INFO, "Starting generation of parameters for scheduled workout."); + String workoutDescription = Parser.getScheduleDescription(inputArguments); + String workoutDate = Parser.getDateNoDateTracker(inputArguments); + String workoutTime = Parser.getTime(inputArguments); + String[] generatedParameters = {workoutDescription, workoutDate, workoutTime}; + SCHEDULE_TRACKER_LOGGER.log(Level.INFO, "Successfully generated parameters for scheduled workout."); + return generatedParameters; + } + + /** + * Adds a scheduled workout to the list of scheduled workouts. + * + * @param inputArguments Arguments input by the user that come after the command word. + * @param isSquelchAddMessage Flag that determines whether to squelch the message printed to the user + * during successful adding of a scheduled workout. true to squelch, + * false to continue printing the message. + * @param isCleanUp Flag that determines whether to clean up the schedule list after successfully + * adding a scheduled workout. + * @throws ScheduleException If there are issues adding a scheduled workout. + */ + public void addScheduledWorkout(String inputArguments, boolean isSquelchAddMessage, boolean isCleanUp) + throws ScheduleException { + SCHEDULE_TRACKER_LOGGER.log(Level.INFO, "Starting to try and add scheduled workout."); + nullArgumentCheck(inputArguments); + assert inputArguments != null : "Exception should already been thrown if argument is null"; + missingDescriptionCheck(inputArguments); + scheduledWorkoutSeparatorCheck(inputArguments); + // index 0: description | index 1: date | index 2: time + String[] generatedParameters = generateScheduledWorkoutParameters(inputArguments); + assert generatedParameters.length == 3 : "Exactly 3 parameters should be generated"; + String workoutDescription = generatedParameters[0]; + String workoutDate = generatedParameters[1]; + String workoutTime = generatedParameters[2]; + Map> activityMap; + try { + activityMap = Parser.getActivities(inputArguments); + } catch (NumberFormatException nfe) { + throw new InvalidActivityFormatException(); + } + boolean isRecurringWorkout = Parser.isRecurringWorkout(inputArguments); + ScheduledWorkout workoutToAdd = new ScheduledWorkout( + workoutDescription, workoutDate, workoutTime, activityMap, isRecurringWorkout); + duplicateScheduledWorkoutCheck(workoutToAdd); + scheduledWorkouts.add(workoutToAdd); + if (!isSquelchAddMessage) { + System.out.println(ClickfitMessages.getAddScheduleSuccessMessage(workoutToAdd)); + if (workoutToAdd.hasActivities()) { + System.out.println(workoutToAdd.getActivitiesAsStringToPrint(false)); + } + } + if (isCleanUp) { + cleanUpScheduleList(); + } + SCHEDULE_TRACKER_LOGGER.log(Level.INFO, "Successfully added workout to schedule."); + } + + /** + * Checks whether the scheduled workout index is within range. + * This check is done under the assumption that ones-indexing is used. + * + * @param workoutNumber Index of the scheduled workout. + * @return boolean true if within range, false otherwise. + */ + public boolean isScheduledWorkoutNumberWithinRange(int workoutNumber) { + int upperBound = scheduledWorkouts.size(); + int lowerBound = LOWER_BOUND_INDEX_NON_EMPTY_LIST_ONES_INDEXING; + return (workoutNumber >= lowerBound) && (workoutNumber <= upperBound); + } + + /** + * Deletes a scheduled workout from the list of scheduled workouts. + * + * @param inputArguments Arguments input by the user that come after the command word. + * @throws ScheduleException If there are issues deleting a scheduled workout + */ + public void deleteScheduledWorkout(String inputArguments) throws ScheduleException { + SCHEDULE_TRACKER_LOGGER.log(Level.INFO, "Starting to try and delete scheduled workout."); + if (inputArguments == null) { + throw new NoScheduleIndexException(); + } + assert inputArguments != null : "Exception should already been thrown if argument is null"; + if (isScheduledWorkoutListEmpty()) { + System.out.println(ClickfitMessages.EMPTY_SCHEDULE_LIST_MESSAGE); + return; + } + assert scheduledWorkouts.size() > 0 : "List should be non empty at this point"; + int workoutNumber = Parser.parseStringToInteger(inputArguments); + int workoutIndex = workoutNumber - 1; // 0-indexing + if (isScheduledWorkoutNumberWithinRange(workoutNumber)) { + ScheduledWorkout workoutToDelete = scheduledWorkouts.get(workoutIndex); + System.out.println(ClickfitMessages.getDeleteScheduleSuccessMessage(workoutToDelete)); + scheduledWorkouts.remove(workoutIndex); + SCHEDULE_TRACKER_LOGGER.log(Level.INFO, "Successfully deleted scheduled workout."); + } else { + SCHEDULE_TRACKER_LOGGER.log(Level.WARNING, "Failed to delete scheduled workout."); + throw new DeleteScheduleException(); + } + } + + /** + * Prints out the list of scheduled workouts. + * Either a filtered list based off a certain date or the full schedule list can be printed. + * + * @param inputArguments Date to use as a filter in the format dd/mm/yyyy. + * If the input is all the full list of all scheduled workouts is printed. + */ + public void listScheduledWorkouts(String inputArguments) { + cleanUpScheduleList(); + if (inputArguments.equals(INPUT_ALL)) { + listAllScheduledWorkouts(); + } else { + listScheduledWorkoutsOnDate(inputArguments); + } + } + + /** + * Prints out the full list of all scheduled workouts. + */ + public void listAllScheduledWorkouts() { + SCHEDULE_TRACKER_LOGGER.log(Level.INFO, "Starting to try and list scheduled workouts."); + if (isScheduledWorkoutListEmpty()) { + System.out.println(ClickfitMessages.EMPTY_SCHEDULE_LIST_MESSAGE); + return; + } + System.out.println(ClickfitMessages.FULL_SCHEDULE_LIST_MESSAGE); + int currentIndex = 1; + int scheduleCount = 0; + for (ScheduledWorkout workout : scheduledWorkouts) { + System.out.println(currentIndex + ". " + workout.getWorkoutDescription() + workout.isRecurringStatus()); + System.out.println("Date: " + workout.getWorkoutDate()); + System.out.println("Time: " + workout.getWorkoutTime()); + System.out.println(workout.getActivitiesAsStringToPrint(true)); + currentIndex++; + scheduleCount++; + } + System.out.println(ClickfitMessages.getTotalScheduledWorkoutMessage(scheduleCount)); + SCHEDULE_TRACKER_LOGGER.log(Level.INFO, "Successfully listed workouts."); + } + + /** + * Prints out a filtered schedule list based off a certain date. + * + * @param inputArguments Date to use as a filter in the format dd/mm/yyyy. + */ + public void listScheduledWorkoutsOnDate(String inputArguments) { + ArrayList filteredScheduleList = (ArrayList) scheduledWorkouts.stream() + .filter((t) -> t.getWorkoutDate().equals(inputArguments)).collect(Collectors.toList()); + if (filteredScheduleList.isEmpty()) { + if (inputArguments.equals(Parser.getSystemDate())) { + System.out.println(ClickfitMessages.EMPTY_SCHEDULE_LIST_TODAY_MESSAGE); + } else { + System.out.println(ClickfitMessages.getEmptyScheduleOnDateMessage(inputArguments)); + } + } else { + if (inputArguments.equals(Parser.getSystemDate())) { + System.out.println(ClickfitMessages.WORKOUT_SCHEDULE_TODAY_MESSAGE); + } else { + System.out.println(ClickfitMessages.getWorkoutScheduleOnDateMessage(inputArguments)); + } + int currentIndex = 1; + int workoutCount = 0; + for (ScheduledWorkout workout : filteredScheduleList) { + System.out.println(currentIndex + ". " + + workout.getWorkoutDescription() + workout.isRecurringStatus()); + System.out.println("Date: " + workout.getWorkoutDate()); + System.out.println("Time: " + workout.getWorkoutTime()); + System.out.println(workout.getActivitiesAsStringToPrint(true)); + currentIndex++; + workoutCount++; + } + System.out.println(ClickfitMessages.getScheduledWorkoutCountMessage(workoutCount)); + } + } + + /** + * Cleans up the schedule list from any overdue workouts. + * If workout is non-recurring and the date is passed it is deleted. Otherwise if it is recurring, the workout + * is rescheduled by the required days in multiples of 7. + */ + public void cleanUpScheduleList() { + if (isScheduledWorkoutListEmpty()) { + return; + } + sortScheduleList(); + LocalDate currentDate = LocalDateTime.now().toLocalDate(); + boolean isAnyWorkoutUpdatedOrDeleted = false; + boolean isAnyWorkoutOverdue = true; + ScheduledWorkout firstWorkoutEntry; + while (isAnyWorkoutOverdue) { + if (isScheduledWorkoutListEmpty()) { + break; + } + firstWorkoutEntry = scheduledWorkouts.get(FIRST_INDEX_IN_LIST); + if (firstWorkoutEntry.getWorkoutDateAsLocalDate().isBefore(currentDate)) { + updateOrDeleteScheduledWorkout(firstWorkoutEntry, currentDate); + isAnyWorkoutUpdatedOrDeleted = true; + } else { + isAnyWorkoutOverdue = false; + } + } + if (isAnyWorkoutUpdatedOrDeleted) { + System.out.println(ClickfitMessages.DELETE_OR_UPDATE_SCHEDULE_MESSAGE); + } + } + + /** + * If workout is non recurring and the date is passed it is deleted. + * Otherwise if it is recurring, the workout's date is updated by the required days in multiples of 7. + * + * @param scheduledWorkout Scheduled workout to be updated or deleted. + * @param currentDate Current date. + */ + public void updateOrDeleteScheduledWorkout(ScheduledWorkout scheduledWorkout, LocalDate currentDate) { + if (scheduledWorkout.isRecurring()) { + try { + rescheduleRecurringWorkout(scheduledWorkout, currentDate); + } catch (ScheduleException e) { + scheduledWorkouts.remove(scheduledWorkout); + System.out.println(e.getMessage()); + } + } else { + scheduledWorkouts.remove(scheduledWorkout); + } + sortScheduleList(); + } + + /** + * Reschedules recurring workout by the required days in multiples of 7. + * + * @param scheduledWorkout Scheduled workout to be rescheduled. + * @param currentDate Current date. + * @throws ScheduleException If workout to be rescheduled already exists in the list. + */ + public void rescheduleRecurringWorkout(ScheduledWorkout scheduledWorkout, LocalDate currentDate) + throws ScheduleException { + long daysUntilCurrentDate = ChronoUnit.DAYS.between(scheduledWorkout.getWorkoutDateAsLocalDate(), currentDate); + long daysToAdd = (long) (Math.ceil((double) daysUntilCurrentDate / DAYS_IN_A_WEEK) * DAYS_IN_A_WEEK); + ScheduledWorkout copyOfScheduledWorkout = new ScheduledWorkout(scheduledWorkout); + copyOfScheduledWorkout.incrementWorkoutDate(daysToAdd); + try { + duplicateScheduledWorkoutCheck(copyOfScheduledWorkout); + } catch (ScheduleException e) { + throw new DuplicateRescheduledWorkoutException(); + } + scheduledWorkout.incrementWorkoutDate(daysToAdd); + } + + /** + * Sorts the list of scheduled workouts in ascending order of date time. + */ + public void sortScheduleList() { + scheduledWorkouts.sort(Comparator.comparing(ScheduledWorkout::getWorkoutDateTime)); + } + + /** + * Checks whether input argument is null. + * + * @param inputArguments Arguments input by the user that come after the command word. + * @throws ScheduleException If input argument is null. + */ + public void nullArgumentCheck(String inputArguments) throws ScheduleException { + if (inputArguments == null) { + SCHEDULE_TRACKER_LOGGER.log(Level.WARNING, "User input argument is null."); + throw new ScheduleNullArgumentException(); + } + SCHEDULE_TRACKER_LOGGER.log(Level.INFO, "User input argument(s) is not null."); + } + + /** + * Checks whether the compulsory separators are present in the user input. + * Namely the the date separator /d and time separator /t. + * + * @param inputArguments Arguments input by the user that come after the command word. + * @throws ScheduleException If missing separator detected. + */ + public void scheduledWorkoutSeparatorCheck(String inputArguments) throws ScheduleException { + boolean areSeparatorsCorrect = Parser.containsDateSeparator(inputArguments) + && Parser.containsTimeSeparator(inputArguments); + if (!areSeparatorsCorrect) { + SCHEDULE_TRACKER_LOGGER.log(Level.WARNING, "Separators in user input are missing or invalid."); + throw new MissingScheduleSeparatorException(); + } + SCHEDULE_TRACKER_LOGGER.log(Level.INFO, "Separators in user input are correct."); + } + + /** + * Checks whether the list of scheduled workouts is empty. + * + * @return true if schedule list is empty, false otherwise. + */ + public boolean isScheduledWorkoutListEmpty() { + return scheduledWorkouts.isEmpty(); + } + + /** + * Checks whether the description of the scheduled workout is missing in the user input. + * + * @param inputArguments Arguments input by the user that come after the command word. + * @throws ScheduleException If unable to find description or find separators. + */ + public void missingDescriptionCheck(String inputArguments) throws ScheduleException { + int indexOfFirstDateSeparator = inputArguments.indexOf(Parser.DATE_SEPARATOR.trim()); + String subStringBeforeDateSeparator = ""; + if (indexOfFirstDateSeparator != -1) { + subStringBeforeDateSeparator = inputArguments.substring(0, indexOfFirstDateSeparator).trim(); + } else { + scheduledWorkoutSeparatorCheck(inputArguments); + } + if (subStringBeforeDateSeparator.isEmpty()) { + SCHEDULE_TRACKER_LOGGER.log(Level.WARNING, "Description is missing in user input arguments."); + throw new MissingScheduleDescriptionException(); + } + SCHEDULE_TRACKER_LOGGER.log(Level.INFO, "Description is present in user input arguments."); + } + + /** + * Checks whether a duplicate scheduled workout already exists in the list. + * + * @param scheduledWorkoutToAdd The ScheduledWorkout object to be potentially added. + * @throws ScheduleException If duplicate workout detected. + */ + public void duplicateScheduledWorkoutCheck(ScheduledWorkout scheduledWorkoutToAdd) throws ScheduleException { + String scheduledWorkoutAsString = scheduledWorkoutToAdd.getScheduledWorkoutAsDataString(); + for (ScheduledWorkout s : scheduledWorkouts) { + if (scheduledWorkoutAsString.equals(s.getScheduledWorkoutAsDataString())) { + throw new DuplicateScheduledWorkoutException(); + } + } + } +} diff --git a/src/main/java/seedu/duke/schedule/ScheduledWorkout.java b/src/main/java/seedu/duke/schedule/ScheduledWorkout.java new file mode 100644 index 0000000000..c6f8e80494 --- /dev/null +++ b/src/main/java/seedu/duke/schedule/ScheduledWorkout.java @@ -0,0 +1,198 @@ +package seedu.duke.schedule; + +import seedu.duke.Parser; +import seedu.duke.Ui; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Map; + +//@@author arvejw + +/** + * Represents a scheduled workout. Namely, a ScheduledWorkout stores information about + * the scheduled workout such as its description, date and time, as well as whether the workout is recurring. + */ +public class ScheduledWorkout { + + private String workoutDescription; + private ArrayList activities; + private String workoutDate; + private String workoutTime; + private LocalDateTime workoutDateTime; + private boolean isRecurring; + + /** + * Constructs a ScheduledWorkout object. + * + * @param workoutDescription The description of the workout. + * @param workoutDate The date of the workout. + * @param workoutTime The time of the workout. + * @param activityMap The activities of the workout. + * @param isRecurring The type of workout, namely whether its recurring or not. + */ + public ScheduledWorkout(String workoutDescription, String workoutDate, String workoutTime, + Map> activityMap, boolean isRecurring) { + this.workoutDescription = workoutDescription; + this.workoutDate = workoutDate; + this.workoutTime = workoutTime; + workoutDateTime = LocalDateTime.of( + LocalDate.parse(workoutDate, DateTimeFormatter.ofPattern("dd/MM/yyyy")), + LocalTime.parse(workoutTime, DateTimeFormatter.ofPattern("HH:mm"))); + this.isRecurring = isRecurring; + activities = new ArrayList<>(); + if (!activityMap.isEmpty()) { + for (var entry : activityMap.entrySet()) { + activities.add( + new WorkoutActivity( + entry.getKey().trim(), entry.getValue(), + WorkoutActivity.isDistanceActivity(entry.getKey()) + ) + ); + } + } + } + + /** + * Copies and constructs a ScheduledWorkout object with the same parameters. + * + * @param scheduledWorkout ScheduledWorkout object to be copied. + */ + public ScheduledWorkout(ScheduledWorkout scheduledWorkout) { + this.workoutDescription = scheduledWorkout.workoutDescription; + this.activities = scheduledWorkout.activities; + this.workoutDate = scheduledWorkout.workoutDate; + this.workoutTime = scheduledWorkout.workoutTime; + this.workoutDateTime = scheduledWorkout.workoutDateTime; + this.isRecurring = scheduledWorkout.isRecurring; + } + + public String getWorkoutDescription() { + return workoutDescription; + } + + public String getWorkoutDate() { + return workoutDate; + } + + public void incrementWorkoutDate(long days) { + workoutDateTime = workoutDateTime.plusDays(days); + workoutDate = workoutDateTime.toLocalDate().format(DateTimeFormatter.ofPattern("dd/MM/yyyy")); + } + + public LocalDate getWorkoutDateAsLocalDate() { + return workoutDateTime.toLocalDate(); + } + + public String getWorkoutTime() { + return workoutTime; + } + + public LocalDateTime getWorkoutDateTime() { + return workoutDateTime; + } + + public boolean isRecurring() { + return isRecurring; + } + + public String isRecurringStatus() { + return isRecurring ? " [R]" : ""; + } + + public String isRecurringStatusAsText() { + return isRecurring ? "recurring " : ""; + } + + /** + * Returns the activities in a more readable String format to be printed. + * + * @param isListing Whether the activities are being printed by list commands. + * @return String Activity breakdown as a single String to be printed. + */ + public String getActivitiesAsStringToPrint(boolean isListing) { + StringBuilder output = new StringBuilder(); + output.append(System.lineSeparator()).append("Activities Breakdown: ").append(System.lineSeparator()); + if (activities.isEmpty()) { + output.append("nil").append(System.lineSeparator()).append(Ui.HORIZONTAL_BAR_SHORT); + return output.toString(); + } + int currentIndex = 1; + for (WorkoutActivity a : activities) { + if (a.isDistanceActivity()) { + output.append(currentIndex).append(". ").append(a.getActivityDescription()) + .append(": ").append(a.getActivityDistance()).append("metres"); + } else { + output.append(currentIndex).append(". ").append(a.getActivityDescription()) + .append(": ").append(a.getActivitySets()).append("sets x ").append(a.getActivityReps()) + .append("reps"); + } + if (currentIndex < activities.size()) { + output.append(System.lineSeparator()); + } + currentIndex++; + } + if (isListing) { + output.append(System.lineSeparator()).append(Ui.HORIZONTAL_BAR_SHORT); + } + return output.toString(); + } + + /** + * Returns the information of the ScheduledWorkout in a data file compatible format. + * + * @return String Scheduled workout information as a single String. + */ + public String getScheduledWorkoutAsDataString() { + StringBuilder output = new StringBuilder(); + output.append(workoutDescription).append(Parser.DATE_SEPARATOR).append(workoutDate) + .append(Parser.TIME_SEPARATOR).append(workoutTime).append(getActivitiesAsDataString()); + if (isRecurring) { + output.append(Parser.RECURRING_FLAG); + } + return output.toString(); + } + + /** + * Returns the activity breakdown of the ScheduledWorkout in a data file compatible format. + * + * @return String Activity breakdown information as a single String to be stored. + */ + public String getActivitiesAsDataString() { + StringBuilder activityString = new StringBuilder(); + if (activities.isEmpty()) { + return activityString.toString(); + } + activityString.append(Parser.ACTIVITY_SEPARATOR); + int currentIndex = 0; + for (WorkoutActivity activity : activities) { + if (activity.isDistanceActivity()) { + activityString.append(activity.getActivityDescription()) + .append(Parser.ACTIVITY_SPLITTER) + .append(activity.getActivityDistance()); + } else { + activityString.append(activity.getActivityDescription()) + .append(Parser.ACTIVITY_SPLITTER) + .append(activity.getActivitySets()) + .append(Parser.QUANTIFIER_SPLITTER) + .append(activity.getActivityReps()); + } + currentIndex++; + activityString.append( + (currentIndex < activities.size()) ? Parser.MULTIPLE_ACTIVITY_MARKER : Parser.EMPTY_STRING); + } + return activityString.toString(); + } + + /** + * Returns a boolean depending on whether an activity breakdown was specified. + * + * @return true if has activities, false otherwise. + */ + public boolean hasActivities() { + return !activities.isEmpty(); + } +} diff --git a/src/main/java/seedu/duke/schedule/WorkoutActivity.java b/src/main/java/seedu/duke/schedule/WorkoutActivity.java new file mode 100644 index 0000000000..6990b27f7a --- /dev/null +++ b/src/main/java/seedu/duke/schedule/WorkoutActivity.java @@ -0,0 +1,72 @@ +package seedu.duke.schedule; + +import java.util.ArrayList; + +//@@author arvejw + +/** + * Represents a workout activity. Namely, a WorkoutActivity objects contains information of the + * activity description and the quantifiers tied to it. + */ +public class WorkoutActivity { + + private String activityDescription; + private boolean isDistanceActivity = false; + private int activityDistance; + private int activitySets; + private int activityReps; + public static final String ACTIVITY_SWIMMING = "swimming"; + public static final String ACTIVITY_RUNNING = "running"; + public static final String ACTIVITY_CYCLING = "cycling"; + + /** + * Constructs a WorkoutActivity object. + * + * @param activityDescription Description of the activity. + * @param activityQuantifier Quantifier of the activity. + * @param isDistanceActivity Flag to determine if the activity is distance based. + */ + public WorkoutActivity(String activityDescription, + ArrayList activityQuantifier, boolean isDistanceActivity) { + this.activityDescription = activityDescription; + if (isDistanceActivity) { + activityDistance = activityQuantifier.get(0); + this.isDistanceActivity = true; + } else { + activitySets = activityQuantifier.get(0); + activityReps = activityQuantifier.get(1); + } + } + + /** + * Helps check if the activity is one of the 3 pre-defined distance based activity swimming/running/cycling. + * + * @param activityDescription Description of the activity. + * @return boolean true if the activity is distance based, false otherwise. + */ + public static boolean isDistanceActivity(String activityDescription) { + return activityDescription.trim().equals(ACTIVITY_SWIMMING) + || activityDescription.trim().equals(ACTIVITY_RUNNING) + || activityDescription.trim().equals(ACTIVITY_CYCLING); + } + + public boolean isDistanceActivity() { + return isDistanceActivity; + } + + public String getActivityDescription() { + return activityDescription; + } + + public int getActivityDistance() { + return activityDistance; + } + + public int getActivitySets() { + return activitySets; + } + + public int getActivityReps() { + return activityReps; + } +} diff --git a/src/test/java/seedu/duke/CommandManagerTest.java b/src/test/java/seedu/duke/CommandManagerTest.java new file mode 100644 index 0000000000..74ef7e1a16 --- /dev/null +++ b/src/test/java/seedu/duke/CommandManagerTest.java @@ -0,0 +1,124 @@ +package seedu.duke; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; + +//@@author EdwardZYWang +class CommandManagerTest { + + @Test + void commandManager1() { + String input = "workout leg day /at 23:59 /c 388"; + String[] result = input.trim().split(" ", 2); + String command = result[0]; + assert command.equals(Keywords.INPUT_ADD_WORKOUT); + } + + @Test + void commandManager3() { + String input = "addweight 30 12/12/2021"; + String[] result = input.trim().split(" ", 2); + String command = result[0]; + assert command.equals(Keywords.INPUT_ADD_WEIGHT); + } + + @Test + void commandManager4() { + String input = "bye"; + String[] result = input.trim().split(" ", 2); + String command = result[0]; + assert command.equals(Keywords.INPUT_BYE); + } + + @Test + void commandManager5() { + String input = "help"; + String[] result = input.trim().split(" ", 2); + String command = result[0]; + assert command.equals(Keywords.INPUT_HELP); + } + + @Test + void commandManager6() { + String input = "deletemeal"; + String[] result = input.trim().split(" ", 2); + String command = result[0]; + assert command.equals(Keywords.DELETE_MEAL); + } + + @Test + void commandManager7() { + String input = "listfluids"; + String[] result = input.trim().split(" ", 2); + String command = result[0]; + assert command.equals(Keywords.LIST_DRINKS); + } + + @Test + void foodBankParser1() { + String command = ""; + String inputArguments = "addfluid coke"; + String[] splitResults = inputArguments.trim().split(" ", 2); + command = splitResults[0]; + inputArguments = (splitResults.length == 2) ? splitResults[1] : null; + assert command.equals(Keywords.ADD_FLUID); + assert inputArguments.equals("coke"); + } + + @Test + void foodBankParser2() { + String command = ""; + String inputArguments = "addfluid"; + String[] splitResults = inputArguments.trim().split(" ", 2); + command = splitResults[0]; + inputArguments = (splitResults.length == 2) ? splitResults[1] : null; + assert command.equals(Keywords.ADD_FLUID); + assert inputArguments == null; + } + + @Test + void listParser() { + String command = ""; + String date = ""; + String inputArguments = "meals 06/11/2022"; + String[] splitResults = inputArguments.trim().split(" ", 2); + command = splitResults[0]; + date = splitResults[1]; + assert command.equals(Keywords.MEALS); + assert date.equals("06/11/2022"); + } + + @Test + void listParser1() { + String command = ""; + String date = ""; + String inputArguments = "meals"; + String[] splitResults = inputArguments.trim().split(" ", 2); + command = splitResults[0]; + assert command.equals(Keywords.MEALS); + } + + @Test + void addParser1() { + String command = ""; + String inputArguments = ""; + String input = "meals pasta /c 100 /d 06/11/2021 /t 23:59"; + String[] splitResults = input.trim().split(" ", 2); + command = splitResults[0]; + inputArguments = (splitResults.length == 2) ? splitResults[1] : null; + assert command.equals(Keywords.MEALS); + } + + @Test + void addParser2() { + String command = ""; + String inputArguments = ""; + String input = "meals"; + String[] splitResults = input.trim().split(" ", 2); + command = splitResults[0]; + inputArguments = (splitResults.length == 2) ? splitResults[1] : null; + assert command.equals(Keywords.MEALS); + assert inputArguments == null; + } + +} \ No newline at end of file diff --git a/src/test/java/seedu/duke/FluidTest.java b/src/test/java/seedu/duke/FluidTest.java new file mode 100644 index 0000000000..2f19beb423 --- /dev/null +++ b/src/test/java/seedu/duke/FluidTest.java @@ -0,0 +1,308 @@ +package seedu.duke; + +import org.junit.jupiter.api.Test; +import seedu.duke.exceptions.fluid.DeleteEmptyFluidListException; +import seedu.duke.exceptions.fluid.FluidExceptions; +import seedu.duke.exceptions.fluid.NoDeleteFluidIndexException; +import seedu.duke.exceptions.fluid.NoFluidToDelete; +import seedu.duke.exceptions.fluid.NoVolumeEntered; +import seedu.duke.exceptions.foodbank.EmptyFoodDescription; +import seedu.duke.exceptions.foodbank.FoodBankException; +import seedu.duke.exceptions.foodbank.NoFoodFoundException; +import java.time.format.DateTimeParseException; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; + +//@@author pragyan01 +class FluidTest { + + @Test + void generateFluidParameters() { + Fluid fluid = new Fluid(); + String input = "coke /c 40 /v 100 /d 12/12/2021 /t 10:30"; + assertDoesNotThrow(() -> fluid.generateFluidParameters(input)); + } + + @Test + void generateFluidParameters2() { + Fluid fluid = new Fluid(); + String input = "coke /c 40 /v 100"; + assertDoesNotThrow(() -> fluid.generateFluidParameters(input)); + } + + @Test + void generateFluidParameters3() { + Fluid fluid = new Fluid(); + String input = "coke /c 40 /v 100"; + assertDoesNotThrow(() -> fluid.generateFluidParameters(input)); + } + + @Test + void generateFluidParameters4() { + Fluid fluid = new Fluid(); + String input = "coke /c 40"; + assertDoesNotThrow(() -> fluid.generateFluidParameters(input)); + } + + @Test + void generateFluidParameters5() { + Fluid fluid = new Fluid(); + String input = null; + assertThrows(NullPointerException.class, () -> fluid.generateFluidParameters(input)); + } + + @Test + void addFluid() { + Fluid fluid = new Fluid(); + String input = "coke /c 40 /v 100 /d 12/12/2021 /t 10:30"; + assertDoesNotThrow(() -> fluid.addFluid(input)); + } + + @Test + void addFluid2() { + Fluid fluid = new Fluid(); + String input = "coke /c 40 /v 100 /d 12/12/2021"; + assertDoesNotThrow(() -> fluid.addFluid(input)); + } + + @Test + void addFluid3() { + Fluid fluid = new Fluid(); + String input = "coke /c 40 /v 100 /t 10:30"; + assertDoesNotThrow(() -> fluid.addFluid(input)); + } + + @Test + void addFluid4() { + Fluid fluid = new Fluid(); + String input = "coke /c 40 /v 100 /d 2021-3-9 /t 10:30"; + assertThrows(DateTimeParseException.class, () -> fluid.addFluid(input)); + } + + + @Test + void addFluid5() { + Fluid fluid = new Fluid(); + String input = "coke /c 40 /v 100 /d 12/12/2021 /t 2359pm"; + assertThrows(DateTimeParseException.class, () -> fluid.addFluid(input)); + } + + @Test + void addFluid6() { + Fluid fluid = new Fluid(); + String input = "coke /c hello /v 100 /d 12/12/2021 /t 10:30"; + assertThrows(NumberFormatException.class, () -> fluid.addFluid(input)); + } + + @Test + void addFluid7() { + Fluid fluid = new Fluid(); + String input = "coke /c 40 /v 100ml /d 12/12/2021 /t 10:30"; + assertThrows(NumberFormatException.class, () -> fluid.addFluid(input)); + } + + @Test + void addFluid8() { + Fluid fluid = new Fluid(); + String input = "/c 40 /v 100 /d 12/12/2021 /t 10:30"; + assertThrows(EmptyFoodDescription.class, () -> fluid.addFluid(input)); + } + + @Test + void addFluid9() { + Fluid fluid = new Fluid(); + String input = "coke /c 40 /d 12/12/2021 /t 10:30"; + assertThrows(NoVolumeEntered.class, () -> fluid.addFluid(input)); + } + + @Test + void addFluid10() { + Fluid fluid = new Fluid(); + new FoodBank(); + String input = "coke /v 100 /d 12/12/2021 /t 10:30"; + assertThrows(NoFoodFoundException.class, () -> fluid.addFluid(input)); + } + + @Test + void addFluid11() { + Fluid fluid = new Fluid(); + new FoodBank(); + String input = ""; + assertThrows(NoFoodFoundException.class, () -> fluid.addFluid(input)); + } + + @Test + void addFluid12() { + Fluid fluid = new Fluid(); + new FoodBank(); + String input = " "; + assertThrows(NoFoodFoundException.class, () -> fluid.addFluid(input)); + } + + @Test + void addFluid13() { + Fluid fluid = new Fluid(); + new FoodBank(); + String input = "coke"; + assertThrows(NoFoodFoundException.class, () -> fluid.addFluid(input)); + } + + @Test + void deleteFluid() throws FoodBankException, FluidExceptions { + Fluid fluid = new Fluid(); + fluid.addFluid("coke /c 40 /v 100 /d 12/12/2021 /t 10:30"); + String input = "1"; + assertDoesNotThrow(() -> fluid.deleteFluid(input)); + } + + @Test + void deleteFluid2() throws FoodBankException, FluidExceptions { + Fluid fluid = new Fluid(); + fluid.addFluid("coke /c 40 /v 100 /d 12/12/2021 /t 10:30"); + String input = "one"; + assertThrows(NumberFormatException.class, () -> fluid.deleteFluid(input)); + } + + @Test + void deleteFluid3() throws FluidExceptions, FoodBankException { + Fluid fluid = new Fluid(); + fluid.addFluid("coke /c 40 /v 100 /d 12/12/2021 /t 10:30"); + String input = null; + assertThrows(NoDeleteFluidIndexException.class, () -> fluid.deleteFluid(input)); + } + + @Test + void deleteFluid4() throws FluidExceptions, FoodBankException { + Fluid fluid = new Fluid(); + fluid.addFluid("coke /c 40 /v 100 /d 12/12/2021 /t 10:30"); + String input = ""; + assertThrows(NumberFormatException.class, () -> fluid.deleteFluid(input)); + } + + @Test + void deleteFluid5() { + Fluid fluid = new Fluid(); + String input = "1"; + assertThrows(DeleteEmptyFluidListException.class, () -> fluid.deleteFluid(input)); + } + + @Test + void deleteFluid6() throws FluidExceptions, FoodBankException { + Fluid fluid = new Fluid(); + fluid.addFluid("coke /c 40 /v 100 /d 12/12/2021 /t 10:30"); + String input = "5"; + assertThrows(NoFluidToDelete.class, () -> fluid.deleteFluid(input)); + } + + @Test + void deleteFluid7() throws FluidExceptions, FoodBankException { + Fluid fluid = new Fluid(); + fluid.addFluid("coke /c 40 /v 100 /d 12/12/2021 /t 10:30"); + String input = "-3"; + assertThrows(NoFluidToDelete.class, () -> fluid.deleteFluid(input)); + } + + @Test + void deleteFluid8() throws FluidExceptions, FoodBankException { + Fluid fluid = new Fluid(); + fluid.addFluid("coke /c 40 /v 100 /d 12/12/2021 /t 10:30"); + String input = " "; + assertThrows(NumberFormatException.class, () -> fluid.deleteFluid(input)); + } + + @Test + void listFluid() throws FoodBankException, FluidExceptions { + Fluid fluid = new Fluid(); + fluid.addFluid("coke /c 40 /v 100 /d 12/12/2021 /t 10:30"); + assertDoesNotThrow(() -> fluid.listFluids("12/12/2021")); + } + + @Test + void listFluid2() throws FoodBankException, FluidExceptions { + Fluid fluid = new Fluid(); + fluid.addFluid("coke /c 40 /v 100 /d 12/12/2021 /t 10:30"); + fluid.addFluid("water /c 50 /v 200 /d 13/12/2021 /t 12:30"); + assertDoesNotThrow(() -> fluid.listFluids("all")); + } + + @Test + void listFluid3() { + Fluid fluid = new Fluid(); + assertDoesNotThrow(() -> fluid.listFluids("all")); + } + + @Test + void listFluid4() { + Fluid fluid = new Fluid(); + assertDoesNotThrow(() -> fluid.listFluids("12/12/2021")); + } + + @Test + void listFluid5() throws FoodBankException, FluidExceptions { + Fluid fluid = new Fluid(); + fluid.addFluid("coke /c 40 /v 100 /d 21/12/2021 /t 10:30"); + assertDoesNotThrow(() -> fluid.listFluids("10/12/2021")); + } + + @Test + void listFluid6() throws FoodBankException, FluidExceptions { + Fluid fluid = new Fluid(); + fluid.addFluid("coke /c 40 /v 100 /d 21/12/2021 /t 10:30"); + assertDoesNotThrow(() -> fluid.listFluids("all")); + } + + @Test + void getCalories() throws FoodBankException, FluidExceptions { + Fluid fluid = new Fluid(); + fluid.addFluid("coke /c 40 /v 100"); + assertDoesNotThrow(() -> fluid.getCalories("all")); + } + + @Test + void getCalories2() throws FoodBankException, FluidExceptions { + Fluid fluid = new Fluid(); + fluid.addFluid("coke /c 40 /v 100 /d 12/12/2021 /t 10:30"); + assertDoesNotThrow(() -> fluid.getCalories("12/12/2021")); + } + + @Test + void getCalories3() throws FoodBankException, FluidExceptions { + Fluid fluid = new Fluid(); + fluid.addFluid("coke /c 40 /v 100 /d 12/12/2021 /t 10:30"); + assertDoesNotThrow(() -> fluid.getCalories("23/12/2021")); + } + + @Test + void getCalories4() { + Fluid fluid = new Fluid(); + assertDoesNotThrow(() -> fluid.getCalories("23/12/2021")); + } + + @Test + void getVolume() throws FoodBankException, FluidExceptions { + Fluid fluid = new Fluid(); + fluid.addFluid("coke /c 40 /v 100"); + assertDoesNotThrow(() -> fluid.getVolume("all")); + } + + @Test + void getVolume2() throws FoodBankException, FluidExceptions { + Fluid fluid = new Fluid(); + fluid.addFluid("coke /c 40 /v 100 /d 12/12/2021 /t 10:30"); + assertDoesNotThrow(() -> fluid.getVolume("12/12/2021")); + } + + @Test + void getVolume3() throws FoodBankException, FluidExceptions { + Fluid fluid = new Fluid(); + fluid.addFluid("coke /c 40 /v 100 /d 12/12/2021 /t 10:30"); + assertDoesNotThrow(() -> fluid.getVolume("23/12/2021")); + } + + @Test + void getVolume4() { + Fluid fluid = new Fluid(); + assertDoesNotThrow(() -> fluid.getVolume("23/12/2021")); + } +} diff --git a/src/test/java/seedu/duke/FoodBankTest.java b/src/test/java/seedu/duke/FoodBankTest.java new file mode 100644 index 0000000000..25688be159 --- /dev/null +++ b/src/test/java/seedu/duke/FoodBankTest.java @@ -0,0 +1,541 @@ +package seedu.duke; + +import org.junit.jupiter.api.Test; +import seedu.duke.exceptions.foodbank.DuplicateFood; +import seedu.duke.exceptions.foodbank.EmptyFoodDescription; +import seedu.duke.exceptions.foodbank.EmptyLibraryDescription; +import seedu.duke.exceptions.foodbank.EmptyMealBankException; +import seedu.duke.exceptions.foodbank.FoodBankException; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; +import seedu.duke.exceptions.foodbank.InvalidMealIndexException; +import seedu.duke.exceptions.foodbank.NegativeCaloriesException; +import seedu.duke.exceptions.foodbank.NoFoodFoundException; +import seedu.duke.exceptions.foodbank.NoFoodIndexException; +import seedu.duke.exceptions.foodbank.EmptyFluidBankException; +import seedu.duke.exceptions.foodbank.InvalidFluidIndexException; + +@SuppressWarnings("ALL") +public class FoodBankTest { + + //@@author VishalJeyaram + @Test + void addCustomMeal1() { + FoodBank foodBank = new FoodBank(); + String input = "pasta /c 122"; + assertDoesNotThrow(() -> FoodBank.addCustomMeal(input)); + } + + //@@author VishalJeyaram + @Test + void addCustomMeal2() { + FoodBank foodBank = new FoodBank(); + String input = null; + assertThrows(EmptyLibraryDescription.class, () -> FoodBank.addCustomMeal(input)); + } + + //@@author VishalJeyaram + @Test + void addCustomMeal3() throws FoodBankException { + FoodBank foodBank = new FoodBank(); + String input = "pasta /c 122"; + FoodBank.addCustomMeal(input); + String secondInput = "pasta /c 124"; + assertThrows(DuplicateFood.class, () -> FoodBank.addCustomMeal(secondInput)); + } + + //@@author VishalJeyaram + @Test + void addCustomMeal4() { + FoodBank foodBank = new FoodBank(); + String input = " "; + assertThrows(EmptyLibraryDescription.class, () -> FoodBank.addCustomMeal(input)); + } + + //@@author VishalJeyaram + @Test + void addCustomMeal5() { + FoodBank foodBank = new FoodBank(); + String input = "/c"; + assertThrows(FoodBankException.class, () -> FoodBank.addCustomMeal(input)); + } + + //@@author VishalJeyaram + @Test + void addCustomMeal6() { + FoodBank foodBank = new FoodBank(); + String input = "21"; + assertThrows(EmptyLibraryDescription.class, () -> FoodBank.addCustomMeal(input)); + } + + //@@author VishalJeyaram + @Test + void addCustomMeal7() { + FoodBank foodBank = new FoodBank(); + String input = "pasta /c -122"; + assertThrows(NegativeCaloriesException.class, () -> FoodBank.addCustomMeal(input)); + } + + //@@author VishalJeyaram + @Test + void addCustomMeal8() { + FoodBank foodBank = new FoodBank(); + String input = "pasta"; + assertThrows(EmptyLibraryDescription.class, () -> FoodBank.addCustomMeal(input)); + } + + //@@author VishalJeyaram + @Test + void addCustomMeal9() throws FoodBankException { + FoodBank foodBank = new FoodBank(); + String input = "pasta /c122"; + assertThrows(EmptyLibraryDescription.class, () -> FoodBank.addCustomMeal(input)); + } + + //@@author VishalJeyaram + @Test + void deleteCustomMeal1() throws FoodBankException { + FoodBank foodBank = new FoodBank(); + String input = "pasta /c 122"; + FoodBank.addCustomMeal(input); + assertDoesNotThrow(() -> FoodBank.deleteCustomMeal("1")); + } + + //@@author VishalJeyaram + @Test + void deleteCustomMeal2() throws FoodBankException { + FoodBank foodBank = new FoodBank(); + String input = "pasta /c 122"; + FoodBank.addCustomMeal(input); + String secondInput = null; + assertThrows(NoFoodIndexException.class, () -> FoodBank.deleteCustomMeal(secondInput)); + } + + //@@author VishalJeyaram + @Test + void deleteCustomMeal3() throws FoodBankException { + FoodBank foodBank = new FoodBank(); + String input = "1"; + assertThrows(EmptyMealBankException.class, () -> FoodBank.deleteCustomMeal(input)); + } + + //@@author VishalJeyaram + @Test + void deleteCustomMeal4() throws FoodBankException { + FoodBank foodBank = new FoodBank(); + String input = "pasta /c 122"; + FoodBank.addCustomMeal(input); + String secondInput = "2"; + assertThrows(InvalidMealIndexException.class, () -> FoodBank.deleteCustomMeal(secondInput)); + } + + //@@author VishalJeyaram + @Test + void deleteCustomMeal5() throws FoodBankException { + FoodBank foodBank = new FoodBank(); + String input = "pasta /c 122"; + FoodBank.addCustomMeal(input); + String secondInput = "#"; + assertThrows(NumberFormatException.class, () -> FoodBank.deleteCustomMeal(secondInput)); + } + + //@@author VishalJeyaram + @Test + void deleteCustomMeal6() throws FoodBankException { + FoodBank foodBank = new FoodBank(); + String input = "pasta /c 122"; + FoodBank.addCustomMeal(input); + String secondInput = ""; + assertThrows(NumberFormatException.class, () -> FoodBank.deleteCustomMeal(secondInput)); + } + + //@@author VishalJeyaram + @Test + void deleteCustomMeal7() throws FoodBankException { + FoodBank foodBank = new FoodBank(); + String input = "pasta /c 122"; + FoodBank.addCustomMeal(input); + String secondInput = " "; + assertThrows(NumberFormatException.class, () -> FoodBank.deleteCustomMeal(secondInput)); + } + + //@@author VishalJeyaram + @Test + void deleteCustomMeal8() throws FoodBankException { + FoodBank foodBank = new FoodBank(); + String input = "pasta /c 122"; + FoodBank.addCustomMeal(input); + String secondInput = "delete meal"; + assertThrows(NumberFormatException.class, () -> FoodBank.deleteCustomMeal(secondInput)); + } + + //@@author VishalJeyaram + @Test + void deleteCustomMeal9() throws FoodBankException { + FoodBank foodBank = new FoodBank(); + String input = "pasta /c 122"; + FoodBank.addCustomMeal(input); + String secondInput = ""; + assertThrows(NumberFormatException.class, () -> FoodBank.deleteCustomMeal(secondInput)); + } + + //@@author VishalJeyaram + @Test + void listCustomMeal1() throws FoodBankException { + FoodBank foodbank = new FoodBank(); + FoodBank.addCustomMeal("pasta /c 213 /d 12/09/2021 /t 10:35"); + assertDoesNotThrow(() -> FoodBank.listCustomMeal()); + } + + //@@author pragyan01 + @Test + void generateParameters() { + FoodBank foodbank = new FoodBank(); + String input = "water /c 0"; + assertDoesNotThrow(() -> foodbank.generateParameters(input)); + } + + //@@author pragyan01 + @Test + void generateParameters2() { + FoodBank foodbank = new FoodBank(); + String input = "water"; + assertThrows(NoFoodFoundException.class, () -> foodbank.generateParameters(input)); + } + + //@@author pragyan01 + @Test + void generateParameters3() { + FoodBank foodbank = new FoodBank(); + String input = ""; + assertThrows(NoFoodFoundException.class, () -> foodbank.generateParameters(input)); + } + + //@@author pragyan01 + @Test + void generateParameters4() { + FoodBank foodbank = new FoodBank(); + String input = " "; + assertThrows(NoFoodFoundException.class, () -> foodbank.generateParameters(input)); + } + + //@@author pragyan01 + @Test + void generateParameters5() { + FoodBank foodbank = new FoodBank(); + String input = null; + assertThrows(NullPointerException.class, () -> foodbank.generateParameters(input)); + } + + //@@author pragyan01 + @Test + void generateParameters6() { + FoodBank foodbank = new FoodBank(); + String input = "-5"; + assertThrows(NoFoodFoundException.class, () -> foodbank.generateParameters(input)); + } + + //@@author pragyan01 + @Test + void generateParameters7() { + FoodBank foodbank = new FoodBank(); + String input = "/d"; + assertThrows(EmptyFoodDescription.class, () -> foodbank.generateParameters(input)); + } + + //@@author pragyan01 + @Test + void generateParameters8() { + FoodBank foodbank = new FoodBank(); + String input = "juice /c 60 /v 100 /d 12/12/2021 /t 10:30"; + assertDoesNotThrow(() -> foodbank.generateParameters(input)); + } + + //@@author pragyan01 + @Test + void addCustomFluid() { + FoodBank foodbank = new FoodBank(); + String input = "coke /c 40"; + assertDoesNotThrow(() -> foodbank.addCustomFluid(input)); + } + + //@@author pragyan01 + @Test + void addCustomFluid1() { + FoodBank foodbank = new FoodBank(); + String input = ""; + assertThrows(EmptyLibraryDescription.class, () -> foodbank.addCustomFluid(input)); + } + + //@@author pragyan01 + @Test + void addCustomFluid2() { + FoodBank foodbank = new FoodBank(); + String input = " "; + assertThrows(EmptyLibraryDescription.class, () -> foodbank.addCustomFluid(input)); + } + + //@@author pragyan01 + @Test + void addCustomFluid3() { + FoodBank foodbank = new FoodBank(); + String input = null; + assertThrows(EmptyLibraryDescription.class, () -> foodbank.addCustomFluid(input)); + } + + //@@author pragyan01 + @Test + void addCustomFluid4() { + FoodBank foodbank = new FoodBank(); + String input = "/c"; + assertThrows(FoodBankException.class, () -> foodbank.addCustomFluid(input)); + } + + //@@author pragyan01 + @Test + void addCustomFluid5() throws FoodBankException { + FoodBank foodbank = new FoodBank(); + foodbank.addCustomFluid("cola /c 60"); + String input = "cola /c 60"; + assertThrows(DuplicateFood.class, () -> foodbank.addCustomFluid(input)); + } + + //@@author pragyan01 + @Test + void addCustomFluid6() { + FoodBank foodbank = new FoodBank(); + String input = "21"; + assertThrows(EmptyLibraryDescription.class, () -> foodbank.addCustomFluid(input)); + } + + //@@author pragyan01 + @Test + void addCustomFluid7() { + FoodBank foodbank = new FoodBank(); + String input = "add fluid"; + assertThrows(EmptyLibraryDescription.class, () -> foodbank.addCustomFluid(input)); + } + + //@@author pragyan01 + @Test + void deleteCustomFluid() throws FoodBankException { + FoodBank foodbank = new FoodBank(); + foodbank.addCustomFluid("coke /c 40"); + String input = "1"; + assertDoesNotThrow(() -> foodbank.deleteCustomFluids(input)); + } + + //@@author pragyan01 + @Test + void deleteCustomFluid2() throws FoodBankException { + FoodBank foodbank = new FoodBank(); + foodbank.addCustomFluid("coke /c 40"); + String input = "one"; + assertThrows(NumberFormatException.class, () -> foodbank.deleteCustomFluids(input)); + } + + //@@author pragyan01 + @Test + void deleteCustomFluid3() throws FoodBankException { + FoodBank foodbank = new FoodBank(); + foodbank.addCustomFluid("coke /c 40"); + String input = null; + assertThrows(NoFoodIndexException.class, () -> foodbank.deleteCustomFluids(input)); + } + + //@@author pragyan01 + @Test + void deleteCustomFluid4() throws FoodBankException { + FoodBank foodbank = new FoodBank(); + foodbank.addCustomFluid("coke /c 40"); + String input = ""; + assertThrows(NumberFormatException.class, () -> foodbank.deleteCustomFluids(input)); + } + + //@@author pragyan01 + @Test + void deleteCustomFluid5() { + FoodBank foodbank = new FoodBank(); + String input = "1"; + assertThrows(EmptyFluidBankException.class, () -> foodbank.deleteCustomFluids(input)); + } + + //@@author pragyan01 + @Test + void deleteCustomFluid6() throws FoodBankException { + FoodBank foodbank = new FoodBank(); + foodbank.addCustomFluid("coke /c 40"); + String input = "20"; + assertThrows(InvalidFluidIndexException.class, () -> foodbank.deleteCustomFluids(input)); + } + + //@@author pragyan01 + @Test + void deleteCustomFluid7() throws FoodBankException { + FoodBank foodbank = new FoodBank(); + foodbank.addCustomFluid("coke /c 40"); + String input = "-3"; + assertThrows(InvalidFluidIndexException.class, () -> foodbank.deleteCustomFluids(input)); + } + + //@@author pragyan01 + @Test + void listCustomFluid() throws FoodBankException { + FoodBank foodbank = new FoodBank(); + foodbank.addCustomFluid("coke /c 40 /v 100 /d 12/12/2021 /t 10:30"); + assertDoesNotThrow(() -> foodbank.listCustomFluids()); + } + + //@@author pragyan01 + @Test + void listFluid2() throws FoodBankException { + FoodBank foodbank = new FoodBank(); + foodbank.addCustomFluid("coke /c 40 /v 100 /d 12/12/2021 /t 10:30"); + foodbank.addCustomFluid("water /c 0 /v 300 /d 11/11/2021 /t 11:59"); + assertDoesNotThrow(() -> foodbank.listCustomFluids()); + } + + //@@author pragyan01 + @Test + void listFluid3() { + FoodBank foodbank = new FoodBank(); + assertDoesNotThrow(() -> foodbank.listCustomFluids()); + } + + //@@author pragyan01 + @Test + void findCalories() throws FoodBankException { + FoodBank foodbank = new FoodBank(); + foodbank.addCustomFluid("coke /c 40"); + String input = "coke"; + assertDoesNotThrow(() -> foodbank.findCalories(input)); + } + + //@@author pragyan01 + @Test + void findCalories2() throws FoodBankException { + FoodBank foodbank = new FoodBank(); + foodbank.addCustomFluid("coke /c 40"); + String input = "water"; + assertThrows(NoFoodFoundException.class, () -> foodbank.findCalories(input)); + } + + //@@author pragyan01 + @Test + void findCalories3() throws FoodBankException { + FoodBank foodbank = new FoodBank(); + foodbank.addCustomFluid("coke /c 40"); + String input = "1"; + assertThrows(NoFoodFoundException.class, () -> foodbank.findCalories(input)); + } + + //@@author pragyan01 + @Test + void findCalories4() throws FoodBankException { + FoodBank foodbank = new FoodBank(); + foodbank.addCustomFluid("coke /c 40"); + String input = ""; + assertThrows(NoFoodFoundException.class, () -> foodbank.findCalories(input)); + } + + //@@author pragyan01 + @Test + void findCalories5() throws FoodBankException { + FoodBank foodbank = new FoodBank(); + foodbank.addCustomFluid("coke /c 40"); + String input = " "; + assertThrows(NoFoodFoundException.class, () -> foodbank.findCalories(input)); + } + + //@@author pragyan01 + @Test + void findCalories6() throws FoodBankException { + FoodBank foodbank = new FoodBank(); + foodbank.addCustomFluid("coke /c 40"); + String input = null; + assertThrows(NoFoodFoundException.class, () -> foodbank.findCalories(input)); + } + + //@@author pragyan01 + @Test + void findCalories7() { + FoodBank foodbank = new FoodBank(); + String input = "cola"; + assertThrows(NoFoodFoundException.class, () -> foodbank.findCalories(input)); + } + + //@@author pragyan01 + @Test + void findCalories8() { + FoodBank foodbank = new FoodBank(); + String input = "-5"; + assertThrows(NoFoodFoundException.class, () -> foodbank.findCalories(input)); + } + + //@@author pragyan01 + @Test + void isFoodFound() throws FoodBankException { + FoodBank foodbank = new FoodBank(); + foodbank.addCustomFluid("coke /c 40"); + String input = "cola"; + assertDoesNotThrow(() -> foodbank.isFoodFound(input)); + } + + //@@author pragyan01 + @Test + void isFoodFound2() { + FoodBank foodbank = new FoodBank(); + String input = "coke"; + assertDoesNotThrow(() -> foodbank.isFoodFound(input)); + } + + //@@author pragyan01 + @Test + void isFoodFound3() { + FoodBank foodbank = new FoodBank(); + String input = "-5"; + assertDoesNotThrow(() -> foodbank.isFoodFound(input)); + } + + //@@author pragyan01 + @Test + void isFoodFound4() { + FoodBank foodbank = new FoodBank(); + String input = null; + assertDoesNotThrow(() -> foodbank.isFoodFound(input)); + } + + //@@author pragyan01 + @Test + void isFoodFound5() { + FoodBank foodbank = new FoodBank(); + String input = ""; + assertDoesNotThrow(() -> foodbank.isFoodFound(input)); + } + + //@@author pragyan01 + @Test + void isFoodFound6() { + FoodBank foodbank = new FoodBank(); + String input = " "; + assertDoesNotThrow(() -> foodbank.isFoodFound(input)); + } + + //@@author pragyan01 + @Test + void isFoodFound7() throws FoodBankException { + FoodBank foodbank = new FoodBank(); + foodbank.addCustomMeal("pasta /c 200"); + String input = "pasta"; + assertDoesNotThrow(() -> foodbank.isFoodFound(input)); + } + + //@@author pragyan01 + @Test + void isFoodFound8() throws FoodBankException { + FoodBank foodbank = new FoodBank(); + foodbank.addCustomMeal("pasta /c 200"); + String input = "water"; + assertDoesNotThrow(() -> foodbank.isFoodFound(input)); + } +} diff --git a/src/test/java/seedu/duke/MealTest.java b/src/test/java/seedu/duke/MealTest.java new file mode 100644 index 0000000000..d9f6adba80 --- /dev/null +++ b/src/test/java/seedu/duke/MealTest.java @@ -0,0 +1,210 @@ +package seedu.duke; + +import org.junit.jupiter.api.Test; +import seedu.duke.exceptions.foodbank.EmptyFoodDescription; +import seedu.duke.exceptions.foodbank.FoodBankException; +import java.time.format.DateTimeParseException; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; +import seedu.duke.exceptions.foodbank.NegativeCaloriesException; +import seedu.duke.exceptions.foodbank.NoFoodFoundException; +import seedu.duke.exceptions.meal.EmptyMealListException; +import seedu.duke.exceptions.meal.MealException; +import seedu.duke.exceptions.meal.NoDeleteMealIndexException; +import seedu.duke.exceptions.meal.NoMealDetailsException; +import seedu.duke.exceptions.meal.NoSuchMealIndexException; + +//@@author VishalJeyaram +class MealTest { + @Test + void addMeal1() { + Meal meal = new Meal(); + String input = "pasta /c 356 /d 14/09/2021 /t 7pm"; + assertThrows(DateTimeParseException.class, () -> { + meal.addMeal(input); + }); + } + + @Test + void addMeal2() { + Meal meal = new Meal(); + String input = "pasta /c 356 /d 14-09-2021 /t 17:45"; + assertThrows(DateTimeParseException.class, () -> { + meal.addMeal(input); + }); + } + + @Test + void addMeal3() { + Meal meal = new Meal(); + String input = "pasta /c sauce /d 14/09/2021 /t 17:45"; + assertThrows(NumberFormatException.class, () -> { + meal.addMeal(input); + }); + } + + @Test + void addMeal4() { + Meal meal = new Meal(); + String input = "pasta /c 30 /d 14/09/2021 /t 17:45"; + assertDoesNotThrow(() -> meal.addMeal(input)); + } + + @Test + void addMeal5() { + Meal meal = new Meal(); + String input = "pasta /c 30 /d 14/09/2021"; + assertDoesNotThrow(() -> meal.addMeal(input)); + } + + @Test + void addMeal6() { + Meal meal = new Meal(); + String input = "pasta /c 30 /t 15:07"; + assertDoesNotThrow(() -> meal.addMeal(input)); + } + + @Test + void addMeal7() { + Meal meal = new Meal(); + String input = "pasta /c 30"; + assertDoesNotThrow(() -> meal.addMeal(input)); + } + + @Test + void addMeal8() throws FoodBankException { + Meal meal = new Meal(); + new FoodBank(); + String mealLibraryInput = "pasta /c 13"; + FoodBank.addCustomMeal(mealLibraryInput); + String input = "pasta"; + assertDoesNotThrow(() -> meal.addMeal(input)); + } + + @Test + void addMeal9() throws FoodBankException { + Meal meal = new Meal(); + new FoodBank(); + String mealLibraryInput = "pasta /c 13"; + FoodBank.addCustomMeal(mealLibraryInput); + String input = "Risotto"; + assertThrows(NoFoodFoundException.class, () -> meal.addMeal(input)); + } + + @Test + void addMeal10() { + Meal meal = new Meal(); + String input = "/c 13"; + assertThrows(EmptyFoodDescription.class, () -> meal.addMeal(input)); + } + + @Test + void addMeal11() { + Meal meal = new Meal(); + String input = "pasta /c -13"; + assertThrows(NegativeCaloriesException.class, () -> meal.addMeal(input)); + } + + @Test + void addMeal12() { + Meal meal = new Meal(); + String input = null; + assertThrows(NoMealDetailsException.class, () -> meal.addMeal(input)); + } + + @Test + void addMeal13() { + Meal meal = new Meal(); + String input = "pasta /c % /d 14/09/2021 /t 17:45"; + assertThrows(NumberFormatException.class, () -> { + meal.addMeal(input); + }); + } + + @Test + void addMeal14() { + Meal meal = new Meal(); + String input = "pasta /c -134 /d 14/09/2021 /t 17:45"; + assertThrows(NegativeCaloriesException.class, () -> { + meal.addMeal(input); + }); + } + + @Test + void addMeal15() { + Meal meal = new Meal(); + String input = "pasta /c134 /d 14/09/2021 /t 17:45"; + assertThrows(EmptyFoodDescription.class, () -> { + meal.addMeal(input); + }); + } + + @Test + void deleteMeal1() throws MealException, FoodBankException { + Meal meal = new Meal(); + String input = "pasta /c 300 /d 14/09/2021 /t 17:45"; + meal.addMeal(input); + String deleteInput = "two"; + assertThrows(NumberFormatException.class, () -> { + meal.deleteMeal(deleteInput); + }); + } + + @Test + void deleteMeal2() throws MealException, FoodBankException { + Meal meal = new Meal(); + String input = "pasta /c 300 /d 14/09/2021 /t 17:45"; + meal.addMeal(input); + String deleteInput = "2"; + assertThrows(NoSuchMealIndexException.class, () -> { + meal.deleteMeal(deleteInput); + }); + } + + @Test + void deleteMeal3() { + Meal meal = new Meal(); + String deleteInput = "1"; + assertThrows(EmptyMealListException.class, () -> { + meal.deleteMeal(deleteInput); + }); + } + + @Test + void deleteMeal4() { + Meal meal = new Meal(); + String deleteInput = null; + assertThrows(NoDeleteMealIndexException.class, () -> { + meal.deleteMeal(deleteInput); + }); + } + + @Test + void deleteMeal5() throws MealException, FoodBankException { + Meal meal = new Meal(); + String input = "pasta /c 30 /d 14/09/2021"; + meal.addMeal(input); + String deleteInput = "1"; + assertDoesNotThrow(() -> meal.deleteMeal(deleteInput)); + } + + @Test + void deleteMeal6() throws MealException, FoodBankException { + Meal meal = new Meal(); + String input = "pasta /c 31 /d 14/09/2021"; + meal.addMeal(input); + String deleteInput = "&"; + assertThrows(NumberFormatException.class, () -> { + meal.deleteMeal(deleteInput); + }); + } + + @Test + void listMeals1() throws MealException, FoodBankException { + Meal meal = new Meal(); + String input = "pasta /c 30 /d 14/09/2021 /t 14:05"; + meal.addMeal(input); + String date = "14/09/2021"; + assertDoesNotThrow(() -> meal.listMeals(date)); + } +} \ No newline at end of file diff --git a/src/test/java/seedu/duke/ParserTest.java b/src/test/java/seedu/duke/ParserTest.java new file mode 100644 index 0000000000..9b5f4f549f --- /dev/null +++ b/src/test/java/seedu/duke/ParserTest.java @@ -0,0 +1,102 @@ +package seedu.duke; + +import org.junit.jupiter.api.Test; +import seedu.duke.exceptions.schedule.ScheduleException; +import seedu.duke.exceptions.workout.WorkoutException; + +import java.time.format.DateTimeParseException; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class ParserTest { + + //@@author arvejw + @Test + void getCaloriesBurnedForWorkout_negativeCalories_exceptionThrow() { + String input = "test /c -123"; + assertThrows(WorkoutException.class, () -> Parser.getCaloriesBurnedForWorkout(input)); + } + + //@@author arvejw + @Test + void getCaloriesBurnedForWorkout_nonIntegerCalories_exceptionThrow() { + String input = "test /c !%!@"; + assertThrows(NumberFormatException.class, () -> Parser.getCaloriesBurnedForWorkout(input)); + } + + //@@author arvejw + @Test + void getCaloriesBurnedForWorkout_positiveIntegerCalories_noExceptionThrow() { + String input = "test /c 123"; + assertDoesNotThrow(() -> Parser.getCaloriesBurnedForWorkout(input)); + } + + //@@author arvejw + @Test + void getDateNoDateTracker_invalidDateFormat_exceptionThrow() { + String input = "test /d 1/12/21 /t 17:50"; + assertThrows(DateTimeParseException.class, () -> Parser.getDateNoDateTracker(input)); + } + + //@@author arvejw + @Test + void getDateNoDateTracker_validDateFormat_noExceptionThrow() { + String input = "test /d 12/12/2021 /t 17:50"; + assertDoesNotThrow(() -> Parser.getDateNoDateTracker(input)); + } + + //@@author arvejw + @Test + void getScheduleDescription_noDescription_exceptionThrow() { + String input = "/d 12/12/2021 /t 17:59"; + assertThrows(ScheduleException.class, () -> Parser.getScheduleDescription(input)); + } + + //@@author arvejw + @Test + void getScheduleDescription_validDescription_noExceptionThrow() { + String input = "test /d 12/12/2021 /t 17:59"; + assertDoesNotThrow(() -> Parser.getScheduleDescription(input)); + } + + //@@author arvejw + @Test + void getActivities_noQuantifier_exceptionThrow() { + String input = "add schedule test123 /d 28/12/2021 /t 18:59 /a squats:310"; + assertThrows(ScheduleException.class, () -> Parser.getActivities(input)); + } + + //@@author arvejw + @Test + void getActivities_noSeparator_exceptionThrow() { + String input2 = "add schedule test123 /d 28/12/2021 /t 18:59 /a swimming 800"; + assertThrows(ScheduleException.class, () -> Parser.getActivities(input2)); + } + + //@@author arvejw + @Test + void getActivities_noMarker_exceptionThrow() { + String input = "add schedule test123 /d 28/12/2021 /t 18:59 /a squats:3x10 swimming:600"; + assertThrows(ScheduleException.class, () -> Parser.getActivities(input)); + } + + //@@author arvejw + @Test + void getActivities_validActivityFormat_noExceptionThrow() { + String input1 = "add schedule test123 /d 28/12/2021 /t 18:59 /a swimming:310 /r"; + String input2 = "add schedule test123 /d 28/12/2021 /t 18:59 /a squats:3x10 /r"; + String input3 = "add schedule test123 /d 28/12/2021 /t 18:59 /a squats: 3x10, swimming:1000, running:1000"; + String[] inputs = {input1, input2, input3}; + for (String i : inputs) { + assertDoesNotThrow(() -> Parser.getActivities(i)); + } + } + + //@@author arvejw + @Test + void getActivities_unnecessaryQuantifier_exceptionThrow() { + String input = "add schedule test123 /d 28/12/2021 /t 18:59 /a squats: 3x10, swimming:1000x123, running:1000"; + assertThrows(ScheduleException.class, () -> Parser.getActivities(input)); + } +} \ No newline at end of file diff --git a/src/test/java/seedu/duke/StorageTest.java b/src/test/java/seedu/duke/StorageTest.java new file mode 100644 index 0000000000..821e1a48e9 --- /dev/null +++ b/src/test/java/seedu/duke/StorageTest.java @@ -0,0 +1,128 @@ +package seedu.duke; + +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class StorageTest { + + @Test + void mealSummary() { + ArrayList mealTest = new ArrayList(); + mealTest.add("pasta /c 100 /d 06/11/2021 /t 23:59"); + mealTest.add("risotto /c 200 /d 06/11/2021 /t 23:59"); + mealTest.add("linguini /c 300 /d 06/11/2021 /t 23:59"); + + int totalCalories = 0; + int i = 1; + for (String m : mealTest) { + if (m.contains("[")) { + String[] descriptor = m.substring(1).split(" /c "); + String[] calorie = descriptor[1].split(" /d "); + String description = descriptor[0]; + System.out.println(i + ". " + description); + String calorieIndiv = calorie[0]; + totalCalories += Integer.parseInt(calorieIndiv); + i++; + } else { + String[] descriptor = m.split(" /c "); + String[] calorie = descriptor[1].split(" /d "); + String description = descriptor[0]; + System.out.println(i + ". " + description); + String calorieIndiv = calorie[0]; + totalCalories += Integer.parseInt(calorieIndiv); + i++; + } + } + System.out.println(System.lineSeparator() + "Total number of meals = " + (i - 1)); + System.out.println("Total calories = " + totalCalories); + assertEquals(600, totalCalories); + assertEquals(3, mealTest.size()); + } + + + + @Test + void fluidSummary() { + ArrayList fluidTest = new ArrayList(); + fluidTest.add("cola /c 100 /v 100 /d 06/11/2021 /t 23:59"); + fluidTest.add("water /c 200 /v 200 /d 06/11/2021 /t 23:59"); + fluidTest.add("sprite /c 300 /v 300 /d 06/11/2021 /t 23:59"); + + int totalCalories = 0; + int totalVolume = 0; + int i = 1; + + for (String f : fluidTest) { + if (f.contains("[")) { + String[] descriptor = f.substring(1).split(" /c "); + String[] calorie = descriptor[1].split(" /v "); + String[] volumeSplitter = calorie[1].split(" /d "); + String description = descriptor[0]; + System.out.println(i + ". " + description); + String calorieIndiv = calorie[0]; + String volumeIndiv = volumeSplitter[0]; + totalCalories += Integer.parseInt(calorieIndiv); + totalVolume += Integer.parseInt((volumeIndiv)); + i++; + } else { + String[] descriptor = f.split(" /c "); + String[] calorie = descriptor[1].split(" /v "); + String[] volumeSplitter = calorie[1].split(" /d "); + String description = descriptor[0]; + System.out.println(i + ". " + description); + String calorieIndiv = calorie[0]; + String volumeIndiv = volumeSplitter[0]; + totalCalories += Integer.parseInt(calorieIndiv); + totalVolume += Integer.parseInt((volumeIndiv)); + i++; + } + } + if (totalVolume > 0) { + System.out.println("Total volume consumed = " + totalVolume); + } else { + System.out.println(System.lineSeparator() + "Total variety of drinks = " + (i - 1)); + } + System.out.println("Total calories = " + totalCalories); + assertEquals(600, totalCalories); + assertEquals(3,fluidTest.size()); + assertEquals(600,totalVolume); + } + + @Test + void workoutSummary() { + ArrayList workoutTest = new ArrayList(); + workoutTest.add("pull ups /c 100 /d 06/11/2021 /t 23:59"); + workoutTest.add("run /c 200 /d 06/11/2021 /t 23:59"); + workoutTest.add("fight /c 300 /d 06/11/2021 /t 23:59"); + + int totalCalories = 0; + int i = 1; + for (String w : workoutTest) { + if (w.contains("[")) { + String[] descriptor = w.substring(1).split(" /c "); + String[] calorie = descriptor[1].split(" /d "); + String description = descriptor[0]; + System.out.println(i + ". " + description); + String calorieIndiv = calorie[0]; + totalCalories += Integer.parseInt(calorieIndiv); + i++; + } else { + String[] descriptor = w.split(" /c "); + String[] calorie = descriptor[1].split(" /d "); + String description = descriptor[0]; + System.out.println(i + ". " + description); + String calorieIndiv = calorie[0]; + totalCalories += Integer.parseInt(calorieIndiv); + i++; + } + } + System.out.println(System.lineSeparator() + "Completed Workouts = " + (i - 1)); + System.out.println("Total calories burned = " + totalCalories); + assertEquals(600, totalCalories); + assertEquals(3, workoutTest.size()); + + } +} \ No newline at end of file diff --git a/src/test/java/seedu/duke/UiTest.java b/src/test/java/seedu/duke/UiTest.java new file mode 100644 index 0000000000..9094b0b22a --- /dev/null +++ b/src/test/java/seedu/duke/UiTest.java @@ -0,0 +1,41 @@ +package seedu.duke; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static seedu.duke.ClickfitMessages.MEMORY_STARTUP_N_INPUT; +import static seedu.duke.ClickfitMessages.MEMORY_STARTUP_Y_INPUT; + +//@@author EdwardZYWang +class UiTest { + + @Test + void memoryStartup() { + boolean flag = false; + boolean result = false; + String uiInput = "y"; + if (uiInput.trim().equals("y")) { + System.out.println(MEMORY_STARTUP_Y_INPUT); + result = true; + flag = true; + } + assertEquals(true, flag); + assertEquals(true, result); + } + + @Test + void memoryStartup1() { + boolean flag = false; + boolean result = false; + String uiInput = "y"; + if (uiInput.isEmpty()) { + System.out.println(MEMORY_STARTUP_N_INPUT); + System.out.println(System.lineSeparator() + "What would you like to do?"); + result = false; + flag = true; + assertEquals(true, flag); + assertEquals(false, result); + } + } + +} \ No newline at end of file diff --git a/src/test/java/seedu/duke/UserHelpTest.java b/src/test/java/seedu/duke/UserHelpTest.java new file mode 100644 index 0000000000..b588dbc059 --- /dev/null +++ b/src/test/java/seedu/duke/UserHelpTest.java @@ -0,0 +1,15 @@ +package seedu.duke; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +class UserHelpTest { + + //@@author EdwardZYWang + @Test + void generateUserHelpParameters() { + String input = "commands"; + assertTrue(input.trim().equals("commands")); + } +} \ No newline at end of file diff --git a/src/test/java/seedu/duke/WeightTrackerTest.java b/src/test/java/seedu/duke/WeightTrackerTest.java new file mode 100644 index 0000000000..14b246b648 --- /dev/null +++ b/src/test/java/seedu/duke/WeightTrackerTest.java @@ -0,0 +1,191 @@ +package seedu.duke; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import seedu.duke.exceptions.weight.WeightException; +import seedu.duke.exceptions.weight.DeleteWeightException; +import seedu.duke.exceptions.weight.DeleteWeightIndexException; + +import java.time.format.DateTimeParseException; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +//@@author teoziyiivy +public class WeightTrackerTest { + @Test + void addWeight_validFormat1_noExceptionThrow() { + WeightTracker weights = new WeightTracker(); + String input = "50 /d 03/11/2021"; + assertDoesNotThrow(() -> weights.addWeight(input)); + } + + @Test + void addWeight_validFormat2_noExceptionThrow() { + WeightTracker weights = new WeightTracker(); + String input = "50"; + assertDoesNotThrow(() -> weights.addWeight(input)); + } + + @Test + void addWeight_validFormat3_noExceptionThrow() { + WeightTracker weights = new WeightTracker(); + String input = "50 /d 03/11/2021"; + assertDoesNotThrow(() -> weights.addWeight(input)); + } + + @Test + void addWeight_validFormat4_noExceptionThrow() { + WeightTracker weights = new WeightTracker(); + String input = "50 /d 03/11/2021 "; + assertDoesNotThrow(() -> weights.addWeight(input)); + } + + @Test + void addWeight_invalidFormat1_exceptionThrow() { + WeightTracker weights = new WeightTracker(); + String input = ""; + Assertions.assertThrows(WeightException.class, () -> weights.addWeight(input)); + } + + @Test + void addWeight_invalidFormat2_exceptionThrow() { + WeightTracker weights = new WeightTracker(); + String input = "50 03/11/2021"; + Assertions.assertThrows(WeightException.class, () -> weights.addWeight(input)); + } + + @Test + void addWeight_invalidFormat3_exceptionThrow() { + WeightTracker weights = new WeightTracker(); + String input = "jasgdjsh"; + Assertions.assertThrows(WeightException.class, () -> weights.addWeight(input)); + } + + @Test + void addWeight_invalidFormat4_exceptionThrow() { + WeightTracker weights = new WeightTracker(); + String input = "99999"; + Assertions.assertThrows(WeightException.class, () -> weights.addWeight(input)); + } + + @Test + void addWeight_invalidFormat5_exceptionThrow() { + WeightTracker weights = new WeightTracker(); + String input = "50 /d 11/13/2021"; + Assertions.assertThrows(DateTimeParseException.class, () -> weights.addWeight(input)); + } + + @Test + void addWeight_invalidFormat6_exceptionThrow() { + WeightTracker weights = new WeightTracker(); + String input = "50 /d 11-03-2021"; + Assertions.assertThrows(DateTimeParseException.class, () -> weights.addWeight(input)); + } + + @Test + void addWeight_invalidFormat7_exceptionThrow() { + WeightTracker weights = new WeightTracker(); + String input = "50 /d 11-13-2021"; + Assertions.assertThrows(DateTimeParseException.class, () -> weights.addWeight(input)); + } + + @Test + void addWeight_invalidFormat8_exceptionThrow() { + WeightTracker weights = new WeightTracker(); + String input = "50 /d 11/03/2021"; + Assertions.assertThrows(WeightException.class, () -> weights.addWeight(input)); + } + + @Test + void deleteWeight_validFormat_noExceptionThrow() throws WeightException { + WeightTracker weights = new WeightTracker(); + String entry = "50 /d 03/11/2021"; + String input = "1"; + weights.addWeight(entry); + assertDoesNotThrow(() -> weights.deleteWeight(input)); + } + + @Test + void deleteWeight_invalidFormat_exceptionThrow() { + WeightTracker weights = new WeightTracker(); + String input = "delete weight"; + Assertions.assertThrows(DeleteWeightException.class, () -> weights.deleteWeight(input)); + } + + @Test + void deleteWeight_invalidFormat2_exceptionThrow() throws WeightException { + WeightTracker weights = new WeightTracker(); + String entry = "50 /d 03/11/2021"; + String input = "1 "; + weights.addWeight(entry); + Assertions.assertThrows(NumberFormatException.class, () -> weights.deleteWeight(input)); + } + + @Test + void deleteWeight_invalidFormat3_exceptionThrow() throws WeightException { + WeightTracker weights = new WeightTracker(); + String entry = "50 /d 03/11/2021"; + String input = " 1"; + weights.addWeight(entry); + Assertions.assertThrows(NumberFormatException.class, () -> weights.deleteWeight(input)); + } + + @Test + void deleteWeightIndex_invalidIndex1_exceptionThrow() { + WeightTracker weights = new WeightTracker(); + String input = "1"; + Assertions.assertThrows(DeleteWeightIndexException.class, () -> weights.deleteWeight(input)); + } + + @Test + void deleteWeightIndex_invalidIndex2_exceptionThrow() { + WeightTracker weights = new WeightTracker(); + String input = "-1"; + Assertions.assertThrows(DeleteWeightIndexException.class, () -> weights.deleteWeight(input)); + } + + @Test + void listWeight_validFormat1_noExceptionThrow() throws WeightException { + WeightTracker weights = new WeightTracker(); + String entry = "50 /d 03/11/2021"; + weights.addWeight(entry); + String input = "03/11/2021"; + assertDoesNotThrow(() -> weights.listWeights(input)); + } + + @Test + void listWeight_validFormat2_noExceptionThrow() throws WeightException { + WeightTracker weights = new WeightTracker(); + String entry1 = "50 /d 02/11/2021"; + String entry2 = "45 /d 03/11/2021"; + weights.addWeight(entry1); + weights.addWeight(entry2); + String input = "03/11/2021"; + assertDoesNotThrow(() -> weights.listWeights(input)); + } + + @Test + void listWeight_validFormat3_noExceptionThrow() throws WeightException { + WeightTracker weights = new WeightTracker(); + String entry1 = "50 /d 02/11/2021"; + String entry2 = "45 /d 03/11/2021"; + weights.addWeight(entry1); + weights.addWeight(entry2); + String input = "all"; + assertDoesNotThrow(() -> weights.listWeights(input)); + } + + @Test + void listWeight_validFormat4_noExceptionThrow() { + WeightTracker weights = new WeightTracker(); + String input = ""; + assertDoesNotThrow(() -> weights.listWeights(input)); + } + + @Test + void listWeight_validFormat5_noExceptionThrow() { + WeightTracker weights = new WeightTracker(); + String input = "jashdhj"; + assertDoesNotThrow(() -> weights.listWeights(input)); + } +} diff --git a/src/test/java/seedu/duke/schedule/ScheduleTrackerTest.java b/src/test/java/seedu/duke/schedule/ScheduleTrackerTest.java new file mode 100644 index 0000000000..0437cb994f --- /dev/null +++ b/src/test/java/seedu/duke/schedule/ScheduleTrackerTest.java @@ -0,0 +1,178 @@ +package seedu.duke.schedule; + +import org.junit.jupiter.api.Test; +import seedu.duke.exceptions.schedule.ScheduleException; + +import java.time.format.DateTimeParseException; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +//@@author arvejw +class ScheduleTrackerTest { + + @Test + void nullArgumentCheck_NullInput_exceptionThrow() { + ScheduleTracker st = new ScheduleTracker(); + assertThrows(ScheduleException.class, () -> st.nullArgumentCheck(null)); + } + + @Test + void isScheduledWorkoutNumberWithinRange_inputOutOfRangeNegative_failure() { + ScheduleTracker st = new ScheduleTracker(); + assertFalse(st.isScheduledWorkoutNumberWithinRange(-256)); + } + + @Test + void isScheduledWorkoutNumberWithinRange_inputOutOfRangePositive_failure() { + ScheduleTracker st = new ScheduleTracker(); + assertFalse(st.isScheduledWorkoutNumberWithinRange(999)); + } + + @Test + void isScheduledWorkoutNumberWithinRange_inputWithinRange_success() throws ScheduleException { + ScheduleTracker st = new ScheduleTracker(); + String argumentInput1 = "test /d 07/07/2022 /t 17:59"; + st.addScheduledWorkout(argumentInput1, false, false); + assertTrue(st.isScheduledWorkoutNumberWithinRange(1)); + } + + @Test + void scheduledWorkoutSeparatorCheck_MissingSeparator_exceptionThrow() { + ScheduleTracker st = new ScheduleTracker(); + String argumentInput = "test /d 07/07/2022 17:59"; + assertThrows(ScheduleException.class, () -> st.scheduledWorkoutSeparatorCheck(argumentInput)); + } + + @Test + void deleteScheduledWorkout_outOfRangeNegative_exceptionThrow() throws ScheduleException { + ScheduleTracker st = new ScheduleTracker(); + String argumentInput1 = "test /d 07/07/2022 /t 17:59"; + st.addScheduledWorkout(argumentInput1, false, false); + assertThrows(ScheduleException.class, () -> st.deleteScheduledWorkout("-1")); + } + + @Test + void deleteScheduledWorkout_outOfRangePositive_exceptionThrow() throws ScheduleException { + ScheduleTracker st = new ScheduleTracker(); + String argumentInput1 = "test /d 07/07/2022 /t 17:59"; + st.addScheduledWorkout(argumentInput1, false, false); + assertThrows(ScheduleException.class, () -> st.deleteScheduledWorkout("999")); + } + + @Test + void deleteScheduledWorkout_validIndex_noExceptionThrow() throws ScheduleException { + ScheduleTracker st = new ScheduleTracker(); + String argumentInput1 = "test /d 07/07/2022 /t 17:59"; + st.addScheduledWorkout(argumentInput1, false, false); + assertDoesNotThrow(() -> st.deleteScheduledWorkout("1")); + } + + @Test + void deleteScheduledWorkout_nonInteger_exceptionThrow() throws ScheduleException { + ScheduleTracker st = new ScheduleTracker(); + String argumentInput1 = "test /d 07/07/2022 /t 17:59"; + st.addScheduledWorkout(argumentInput1, false, false); + String argumentInput2 = "@!$!@$!"; + assertThrows(NumberFormatException.class, () -> st.deleteScheduledWorkout(argumentInput2)); + } + + @Test + void deleteScheduledWorkout_nullInput_exceptionThrow() throws ScheduleException { + ScheduleTracker st = new ScheduleTracker(); + String argumentInput1 = "test /d 07/07/2022 /t 17:59"; + st.addScheduledWorkout(argumentInput1, false, false); + assertThrows(ScheduleException.class, () -> st.deleteScheduledWorkout(null)); + } + + @Test + void addScheduledWorkout_missingSeparator_exceptionThrow() { + ScheduleTracker st = new ScheduleTracker(); + String argumentInput = "test /d 07/07/2022 17:59"; + assertThrows(ScheduleException.class, () -> st.addScheduledWorkout(argumentInput, false, false)); + } + + @Test + void addScheduledWorkout_invalidDateFormat_exceptionThrow() { + ScheduleTracker st = new ScheduleTracker(); + String argumentInput = "test /d 07-07-2022 /t 17:59"; + assertThrows(DateTimeParseException.class, () -> st.addScheduledWorkout(argumentInput, false, false)); + } + + @Test + void addScheduledWorkout_invalidTimeFormat_exceptionThrow() { + ScheduleTracker st = new ScheduleTracker(); + String argumentInput = "test /d 07/07/2022 /t 7pm"; + assertThrows(DateTimeParseException.class, () -> st.addScheduledWorkout(argumentInput, false, false)); + } + + @Test + void missingDescriptionCheck_missingDescription_exceptionThrow() { + ScheduleTracker st = new ScheduleTracker(); + String argumentInput = " /d 07/07/2022 /t 07:59"; + assertThrows(ScheduleException.class, () -> st.missingDescriptionCheck(argumentInput)); + } + + @Test + void missingDescriptionCheck_validDescription_noExceptionThrow() { + ScheduleTracker st = new ScheduleTracker(); + String argumentInput = "test /d 07/07/2022 /t 07:59"; + assertDoesNotThrow(() -> st.missingDescriptionCheck(argumentInput)); + } + + @Test + void addScheduledWorkout_noActivityQuantifier_exceptionThrow() { + ScheduleTracker st = new ScheduleTracker(); + String input = "test123 /d 28/12/2021 /t 18:59 /a squats:310"; + assertThrows(ScheduleException.class, () -> st.addScheduledWorkout(input, false, false)); + } + + @Test + void addScheduledWorkout_noActivitySeparator_exceptionThrow() { + ScheduleTracker st = new ScheduleTracker(); + String input = "test123 /d 28/12/2021 /t 18:59 /a swimming 800"; + assertThrows(ScheduleException.class, () -> st.addScheduledWorkout(input, false, false)); + } + + @Test + void addScheduledWorkout_noActivityMarker_exceptionThrow() { + ScheduleTracker st = new ScheduleTracker(); + String input = "test123 /d 28/12/2021 /t 18:59 /a squats:3x10 swimming:600"; + assertThrows(ScheduleException.class, () -> st.addScheduledWorkout(input, false, false)); + } + + @Test + void addScheduledWorkout_validActivityFormat_noExceptionThrow() { + ScheduleTracker st = new ScheduleTracker(); + String input1 = "test123 /d 28/12/2021 /t 18:59 /a swimming:310 /r"; + String input2 = "test123 /d 28/12/2021 /t 18:59 /a squats:3x10 /r"; + String input3 = "test123 /d 28/12/2021 /t 18:59 /a squats: 3x10, swimming:1000, running:1000"; + String[] inputs = {input1, input2, input3}; + for (String i : inputs) { + assertDoesNotThrow(() -> st.addScheduledWorkout(i, false, false)); + } + } + + @Test + void addScheduledWorkout_invalidDistanceActivityFormat_exceptionThrow() { + ScheduleTracker st = new ScheduleTracker(); + String input = "test123 /d 28/12/2021 /t 18:59 /a squats: 3x10, swimming:%!@, running:1000"; + assertThrows(ScheduleException.class, () -> st.addScheduledWorkout(input, false, false)); + } + + @Test + void addScheduledWorkout_invalidNonDistanceActivityFormat_exceptionThrow() { + ScheduleTracker st = new ScheduleTracker(); + String input = "test123 /d 28/12/2021 /t 18:59 /a squats: @!$#!x10, swimming:1000, running:1000"; + assertThrows(ScheduleException.class, () -> st.addScheduledWorkout(input, false, false)); + } + + @Test + void addScheduledWorkout_unnecessaryActivityQuantifier_exceptionThrow() { + ScheduleTracker st = new ScheduleTracker(); + String input = "test123 /d 28/12/2021 /t 18:59 /a squats: 3x10, swimming:1000x123, running:1000"; + assertThrows(ScheduleException.class, () -> st.addScheduledWorkout(input, false, false)); + } +} diff --git a/src/test/java/seedu/duke/schedule/WorkoutTrackerTest.java b/src/test/java/seedu/duke/schedule/WorkoutTrackerTest.java new file mode 100644 index 0000000000..4dc3cfe715 --- /dev/null +++ b/src/test/java/seedu/duke/schedule/WorkoutTrackerTest.java @@ -0,0 +1,110 @@ +package seedu.duke.schedule; + +import org.junit.jupiter.api.Test; +import seedu.duke.WorkoutTracker; +import seedu.duke.exceptions.workout.WorkoutException; + +import java.time.format.DateTimeParseException; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +//@@author arvejw +class WorkoutTrackerTest { + + @Test + void nullArgumentCheck_NullInput_exceptionThrow() { + WorkoutTracker wt = new WorkoutTracker(); + assertThrows(WorkoutException.class, () -> wt.nullArgumentCheck(null)); + } + + @Test + void isWorkoutNumberWithinRange_inputOutOfRangeNegative_failure() { + WorkoutTracker wt = new WorkoutTracker(); + assertFalse(wt.isWorkoutNumberWithinRange(-1)); + } + + @Test + void isWorkoutNumberWithinRange_inputOutOfRangePositive_failure() { + WorkoutTracker wt = new WorkoutTracker(); + assertFalse(wt.isWorkoutNumberWithinRange(999)); + } + + @Test + void isWorkoutNumberWithinRange_inputWithinRange_success() throws WorkoutException { + WorkoutTracker wt = new WorkoutTracker(); + String argumentInput = "test /c 123 /d 07/07/2021 /t 17:59"; + wt.addWorkout(argumentInput, true); + assertTrue(wt.isWorkoutNumberWithinRange(1)); + } + + @Test + void isCompletedWorkoutNumberWithinRange_inputWithinRange_success() throws WorkoutException { + WorkoutTracker wt = new WorkoutTracker(); + String argumentInput = "test /c 123 /d 07/07/2021 /t 17:59"; + wt.addWorkout(argumentInput, true); + assertTrue(wt.isWorkoutNumberWithinRange(1)); + } + + @Test + void deleteWorkout_outOfRange_exceptionThrow() throws WorkoutException { + WorkoutTracker wt = new WorkoutTracker(); + String argumentInput1 = "test /c 123 /d 07/07/2021 /t 17:59"; + wt.addWorkout(argumentInput1, true); + assertThrows(WorkoutException.class, () -> wt.deleteWorkout("-10")); + } + + @Test + void deleteWorkout_nonInteger_exceptionThrow() throws WorkoutException { + WorkoutTracker wt = new WorkoutTracker(); + String argumentInput1 = "test /c 123 /d 07/07/2021 /t 17:59"; + wt.addWorkout(argumentInput1, true); + String argumentInput2 = "@!$!@$!"; + assertThrows(NumberFormatException.class, () -> wt.deleteWorkout(argumentInput2)); + } + + @Test + void deleteWorkout_nullInput_exceptionThrow() throws WorkoutException { + WorkoutTracker wt = new WorkoutTracker(); + String argumentInput1 = "test /c 123 /d 07/07/2021 /t 17:59"; + wt.addWorkout(argumentInput1, true); + assertThrows(WorkoutException.class, () -> wt.deleteWorkout(null)); + } + + @Test + void addWorkout_invalidDateFormat_exceptionThrow() { + WorkoutTracker wt = new WorkoutTracker(); + String argumentInput = "test /c 123 /d 07-07-2021 /t 17:59"; + assertThrows(DateTimeParseException.class, () -> wt.addWorkout(argumentInput, true)); + } + + @Test + void addWorkout_invalidTimeFormat_exceptionThrow() { + WorkoutTracker wt = new WorkoutTracker(); + String argumentInput = "test /c 123 /d 07/07/2021 /t 7pm"; + assertThrows(DateTimeParseException.class, () -> wt.addWorkout(argumentInput, true)); + } + + @Test + void addWorkout_nonIntegerCalorie_exceptionThrow() { + WorkoutTracker wt = new WorkoutTracker(); + String argumentInput = "test /c @$! /d 07/07/2021 /t 7:59"; + assertThrows(NumberFormatException.class, () -> wt.addWorkout(argumentInput, true)); + } + + @Test + void missingDescriptionCheck_missingDescription_exceptionThrow() { + WorkoutTracker wt = new WorkoutTracker(); + String argumentInput = " /c 123 /d 07/07/2021 /t 07:59"; + assertThrows(WorkoutException.class, () -> wt.missingDescriptionCheck(argumentInput)); + } + + @Test + void missingDescriptionCheck_validDescription_noExceptionThrow() { + WorkoutTracker wt = new WorkoutTracker(); + String argumentInput = "test /c 123 /d 07/07/2021 /t 07:59"; + assertDoesNotThrow(() -> wt.missingDescriptionCheck(argumentInput)); + } +} \ No newline at end of file diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index 892cb6cae7..87ade63351 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -1,9 +1,15 @@ -Hello from - ____ _ -| _ \ _ _| | _____ -| | | | | | | |/ / _ \ -| |_| | |_| | < __/ -|____/ \__,_|_|\_\___| +Greetings from + ______ _____ _____ __ ________ _ _ + .' ___ ||_ _| |_ _| [ | _ |_ __ |(_) / |_ +/ .' \_| | | | | .---. | | / ] | |_ \_|__ `| |-' +| | | | _ | | / /'`\] | '' < | _| [ | | | +\ `.___.'\ _| |__/ | _| |_ _ | \__. | |`\ \ _| |_ | | | |, + `.____ .'|________||_____|(_)'.___.'[__| \_]|_____| [___]\__/ -What is your name? -Hello James Gosling + +Welcome! Here's to your fitness journey! Type in any commands to get started! Type "help" to get started! +Lets work hard together in your fitness journey! + +Would you like to check your BMI and recommended caloric intake? +Key in "y" to proceed, or press enter keystroke to skip! +wrong input! diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt index f6ec2e9f95..2f94675b7c 100644 --- a/text-ui-test/input.txt +++ b/text-ui-test/input.txt @@ -1 +1 @@ -James Gosling \ No newline at end of file +N \ No newline at end of file diff --git a/workoutData.txt b/workoutData.txt new file mode 100644 index 0000000000..e69de29bb2