Gym Tracker - Console
This C# Console App was created using C# .NET. It allows a user to create(save), load and delete their workouts. It also includes additional utilities. JSON files are used to store workouts.
Code Structure:
Helper Libraries:
helper-library.cs: Stores all possible colours and has various methods for printing colour outputs to the console.
Classes:
Exercise.cs: A singular exercise. Stores Name, Weight, Sets and Reps.
Log.cs: A singular log. Stores Name, Date and a list of Exercise objects.
Menus:
main-menu.cs: Displays the main menu, handles inputs and routes to the 3 sub-menus listed below.
log-menu.cs: Displays the log menu, handles inputs and routes to sub-methods: newLog, loadLog and deleteLog.
barbell-menu.cs: Displays the barbell menu, handles inputs and calculates the weights needed.
rm-menu.cs: Displays the onerepmax menu, Handles inputs and calculates the weights needed.
Processing:
main.cs: The main part of the program. This is where the main-menu is initially ran from
-
storage.cs: Handles the saving, loading and deleting of files. Creates a folder in the user's directory and processes the Log objects into JSON files.
Classes Explained:
The Exercise class stores a singular exercise. It has properties: name, weight, sets and reps.
Exercise.cspublic class Exercise{ public string name; public double weight; public int sets; public int reps; //Constructor public Exercise(string name, double weight, int sets, int reps){...} }
The Log class is stored as a JSON file. It has descriptive properties: name and date that are used in the filename. It also contains a list of Exercise objects that make up the content of the workout.
Log.cspublic class Log{ public string title; public DateTime date; public List<Exercise> exercises; //Constructor public Log(string title, DateTime date, List<Exercise> exercises){...} }
Menus:
The menu classes all follow a similar pattern. They consist of:
A method that displays the menu options and inputs needed to access these submenus.
A recursive method that handles user input and validation.
If the menu is for a utility then additional functions for mathematical processing are included.
main-menu.csclass MainMenu { public static void Intro() { HelperLib.colourMessage("blue", "Gym Tracker Console App"); displayMainMenu(); displayMainInput(); } public static void displayMainMenu() { //colourMessages of possible submenus } public static void displayMainInput() { HelperLib.colourMessage("cyan", "Select a Mode: ", false); string ?mode = Console.ReadLine(); switch(mode) { //Various cases for submenu inputs default: HelperLib.colourMessage("red, "Invalid Input"); displayMainInput(); break; } } }
These menus follow the same format so only differences to the MainMenu are shown in the following classes.
log-menu.csclass LogMenu { public static void displayLogMenu() { //colourMessages of possible functions } public static void displayLogInput() { //Inputs for possible functions + Validation. } public static void newLog() { //Output, Input handling + type conversion ... storage.Save(newLog); } public static void loadLog() { //Output, Input handling + type conversion ... Log log = storage.Load(date); log.printLog(); } public static void deleteLog() { //Output, Input handling + type conversion ... Log log = storage.Load(Date); log.printLog(); storage.Delete(date); } }
barbell-menu.csclass BarbellMenu { public static void displayBarbellMenu() { //colourMessages } public static void displayBarbellInput() { //Input + Validation for barbell function List<string> results = CalculatePlates(weight); //Print formatting of results } public static List<string> CalculatePlates(double weight) { var results = new List<string>(); double barbell = 20; double oneSide = (weight - barbell) / 2; double[] plates = [20, 15, 10, 5, 2.5, 1.25]; foreach (double plate in plates) { double amount = Math.Floor(oneSide / plate); if (amount > 0) { results.Add($"{plate}KG: {amount}"); } oneSide -= plate * amount; } return results; } }
rm-menu.csclass RMMenu { public static void displayRMMenu() { //colourMessages } public static void displayRMInput() { //Input + Validation for RM function double oneRepMax = CalculateOneRM(weight, reps); //Print formatting of result } public static double CalculateOneRM(double weight, double reps) { double oneRM = weight * (1 + (reps/30)); oneRM = Math.Round(oneRM, 2); return oneRM; } }
I think it is also important to address the fact that we have input + output handling in our menu classes and yet we also include methods for processing such as newLog(), CalculatePlates() etc. Initially I separated these classes however the reason I chose to do this within the same class was due to the complexity being low and the fact none of the methods were similar enough to share methods meaning separating classes menu -> processing -> storage is the exact same just with more files. If this project was to grow in scope in the future then naturally separating the menus from the processing would be easily implemented.
Storage and Logging:
The Storage.cs class is used for converting our Log objects that we create from the log-menu subfunctions to JSON files.
LogMenu newLog() uses Storage.Save(Log log)
LogMenu loadLog() uses Storage.Load(DateTime date)
LogMenu deleteLog() uses Storage.Delete(DateTime date)
storage.cs - Saveclass Store { public string GetLogPath() { //Returns the directory to save } public void Save(Log log) { string path = GetLogPath(); Directory.CreateDirectory(path); string date = log.date.ToString("dd-MM-yyyy"); string fileName = $"{date}_{log.title}.json"; string filePath = Path.Combine(path, fileName); var options = new JsonSerializerOptions { WriteIndented = true }; string jsonString = JsonSerializer.Serialize(log, options); File.WriteAllText(filePath, jsonString); HelperLib.colourMessage("green", "File saved successfully"); } }
storage.cs - Loadpublic Log Load(DateTime date) { string dateString = date.ToString("dd-MM-yyyy"); string path = GetLogPath(); string[] files = Directory.GetFiles(path, $"{dateString}*.json"); //Load the first file if (files.Length > 0) { string filePath = files[0]; string jsonString = File.ReadAllText(filePath); Log log = JsonSerializer.Deserialize<Log>(jsonString); return log; } else { throw new FileNotFoundException("No file found at this date."); } }
storage.cs - Deletepublic void Delete(DateTime date) { string dateString = date.ToString("dd-MM-yyyy"); string path = GetLogPath(); string[] files = Directory.GetFiles(path, $"{dateString}*.json"); //Delete all Files if (files.Length > 0) { foreach (string filePath in files) { File.Delete(filePath); HelperLib.colourMessage("green", "File deleted successfully"); } } else { HelperLib.colourMessage("red", "No file found at this date"); } }
Helper Library:
helper-library.cspublic static class HelperLib { public static void colourMessage(string colour, string message, bool line = true) { changeColour(colour); if (line) { Console.WriteLine(message); } else { Console.Write(message); } Console.ForegroundColor = ConsoleColor.White; } public static void changeColour(string colour) { switch(colour) { case "black": Console.ForegroundColor = ConsoleColor.Black; break; //Case for every possible console colour default: Console.ForegroundColor = ConsoleColor.White; break; } } }
Code Demo:
Work In Progress: create an API that allows me to embed my C# Console program within my website to allow visitors to use the application.