James Sharrock


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:


Classes Explained:

The Exercise class stores a singular exercise. It has properties: name, weight, sets and reps.


Exercise.cs
public 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.cs
public 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:


main-menu.cs
class 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.cs
class 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.cs
class 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.cs
class 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.


storage.cs - Save
class 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 - Load
public 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 - Delete
public 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.cs
public 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.