From f211504f95027fb445110003bf8532babb2c4f69 Mon Sep 17 00:00:00 2001 From: TwilightSaw <56154417+TwilightSaw@users.noreply.github.com> Date: Sun, 1 Dec 2024 01:19:42 +0100 Subject: [PATCH 1/4] ExerciseTracker 0.01 Start of the project, added based files and functionalities. --- .../Controllers/ExerciseController.cs | 6 +++ .../Data/AppDbContext.cs | 23 ++++++++++ .../ExerciseTracker.TwilightSaw.csproj | 30 +++++++++++++ .../ExerciseTracker.TwilightSaw.sln | 25 +++++++++++ .../Factory/HostFactory.cs | 24 ++++++++++ .../Helpers/UserInput.cs | 41 +++++++++++++++++ .../Helpers/Validation.cs | 44 +++++++++++++++++++ ExerciseTracker.TwilightSaw/Model/Exercise.cs | 10 +++++ ExerciseTracker.TwilightSaw/Program.cs | 1 + .../Repository/EfRepository.cs | 40 +++++++++++++++++ .../Repository/IRepository.cs | 10 +++++ ExerciseTracker.TwilightSaw/View/Menu.cs | 6 +++ ExerciseTracker.TwilightSaw/appsettings.json | 12 +++++ 13 files changed, 272 insertions(+) create mode 100644 ExerciseTracker.TwilightSaw/Controllers/ExerciseController.cs create mode 100644 ExerciseTracker.TwilightSaw/Data/AppDbContext.cs create mode 100644 ExerciseTracker.TwilightSaw/ExerciseTracker.TwilightSaw.csproj create mode 100644 ExerciseTracker.TwilightSaw/ExerciseTracker.TwilightSaw.sln create mode 100644 ExerciseTracker.TwilightSaw/Factory/HostFactory.cs create mode 100644 ExerciseTracker.TwilightSaw/Helpers/UserInput.cs create mode 100644 ExerciseTracker.TwilightSaw/Helpers/Validation.cs create mode 100644 ExerciseTracker.TwilightSaw/Model/Exercise.cs create mode 100644 ExerciseTracker.TwilightSaw/Program.cs create mode 100644 ExerciseTracker.TwilightSaw/Repository/EfRepository.cs create mode 100644 ExerciseTracker.TwilightSaw/Repository/IRepository.cs create mode 100644 ExerciseTracker.TwilightSaw/View/Menu.cs create mode 100644 ExerciseTracker.TwilightSaw/appsettings.json diff --git a/ExerciseTracker.TwilightSaw/Controllers/ExerciseController.cs b/ExerciseTracker.TwilightSaw/Controllers/ExerciseController.cs new file mode 100644 index 00000000..9148420a --- /dev/null +++ b/ExerciseTracker.TwilightSaw/Controllers/ExerciseController.cs @@ -0,0 +1,6 @@ +namespace ExerciseTracker.TwilightSaw.Controllers; + +public class ExerciseController +{ + +} \ No newline at end of file diff --git a/ExerciseTracker.TwilightSaw/Data/AppDbContext.cs b/ExerciseTracker.TwilightSaw/Data/AppDbContext.cs new file mode 100644 index 00000000..a55b27e8 --- /dev/null +++ b/ExerciseTracker.TwilightSaw/Data/AppDbContext.cs @@ -0,0 +1,23 @@ +using ExerciseTracker.TwilightSaw.Model; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace ExerciseTracker.TwilightSaw.Data; + +public class AppDbContext : DbContext +{ + public DbSet Exercises { get; set; } + + private readonly IConfiguration _configuration; + public AppDbContext(DbContextOptions options) + : base(options) + { + + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(); + } +} \ No newline at end of file diff --git a/ExerciseTracker.TwilightSaw/ExerciseTracker.TwilightSaw.csproj b/ExerciseTracker.TwilightSaw/ExerciseTracker.TwilightSaw.csproj new file mode 100644 index 00000000..8fb5f554 --- /dev/null +++ b/ExerciseTracker.TwilightSaw/ExerciseTracker.TwilightSaw.csproj @@ -0,0 +1,30 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + diff --git a/ExerciseTracker.TwilightSaw/ExerciseTracker.TwilightSaw.sln b/ExerciseTracker.TwilightSaw/ExerciseTracker.TwilightSaw.sln new file mode 100644 index 00000000..9f832439 --- /dev/null +++ b/ExerciseTracker.TwilightSaw/ExerciseTracker.TwilightSaw.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.11.35312.102 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExerciseTracker.TwilightSaw", "ExerciseTracker.TwilightSaw.csproj", "{792D3445-2C8A-43F8-A586-03388D66D249}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {792D3445-2C8A-43F8-A586-03388D66D249}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {792D3445-2C8A-43F8-A586-03388D66D249}.Debug|Any CPU.Build.0 = Debug|Any CPU + {792D3445-2C8A-43F8-A586-03388D66D249}.Release|Any CPU.ActiveCfg = Release|Any CPU + {792D3445-2C8A-43F8-A586-03388D66D249}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {C26453EB-0465-4FF9-A21B-737455131161} + EndGlobalSection +EndGlobal diff --git a/ExerciseTracker.TwilightSaw/Factory/HostFactory.cs b/ExerciseTracker.TwilightSaw/Factory/HostFactory.cs new file mode 100644 index 00000000..80660ae0 --- /dev/null +++ b/ExerciseTracker.TwilightSaw/Factory/HostFactory.cs @@ -0,0 +1,24 @@ +using ExerciseTracker.TwilightSaw.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace ExerciseTracker.TwilightSaw.Factory; + +public class HostFactory +{ + public static IHost CreateDbHost(string[] args) + { + var builder = Host.CreateDefaultBuilder(args); + var configuration = builder.ConfigureServices((context, services) => + { + var configuration = context.Configuration; + services.AddDbContext(options => options.UseSqlServer(configuration.GetConnectionString("DefaultConnection")) + .LogTo(Console.WriteLine, LogLevel.None) + .UseLazyLoadingProxies()); + }); + return configuration.Build(); + } +} \ No newline at end of file diff --git a/ExerciseTracker.TwilightSaw/Helpers/UserInput.cs b/ExerciseTracker.TwilightSaw/Helpers/UserInput.cs new file mode 100644 index 00000000..b0e03698 --- /dev/null +++ b/ExerciseTracker.TwilightSaw/Helpers/UserInput.cs @@ -0,0 +1,41 @@ +using System.ComponentModel.DataAnnotations; + +namespace ExerciseTracker.TwilightSaw.Helpers; + +using System.Text.RegularExpressions; +using Spectre.Console; + + +public class UserInput +{ + + public static string CreateRegex(string regexString, string messageStart, string messageError) + { + Regex regex = new Regex(regexString); + var input = AnsiConsole.Prompt( + new TextPrompt($"[green]{messageStart} or 0 to exit:[/]") + .Validate(value => regex.IsMatch(value) + ? ValidationResult.Success() + : ValidationResult.Error($"[red]{messageError}[/]"))); + Console.Clear(); + return input; + } + + public static string Create(string messageStart) + { + var input = AnsiConsole.Prompt( + new TextPrompt($"[green]{messageStart} or 0 to exit: [/]")); + Console.Clear(); + return input; + } + + public static string CreateChoosingList(List variants, string backVariant) + { + variants.Add(backVariant); + return AnsiConsole.Prompt(new SelectionPrompt() + .Title("[blue]Please, choose an option from the list below:[/]") + .PageSize(10) + .MoreChoicesText("[grey](Move up and down to reveal more categories[/]") + .AddChoices(variants)); + } +} \ No newline at end of file diff --git a/ExerciseTracker.TwilightSaw/Helpers/Validation.cs b/ExerciseTracker.TwilightSaw/Helpers/Validation.cs new file mode 100644 index 00000000..eaac2cdd --- /dev/null +++ b/ExerciseTracker.TwilightSaw/Helpers/Validation.cs @@ -0,0 +1,44 @@ +using Spectre.Console; + +namespace ExerciseTracker.TwilightSaw.Helpers; + +public class Validation +{ + public static string Validate(Action action, bool getMessage) + { + try + { + action(); + } + catch (Exception e) + { + return getMessage ? e.Message : ""; + } + return "Executed successfully"; + } + + public static string Validate(T action, bool getMessage, out T back) + { + try + { + back = action; + } + catch (Exception e) + { + back = default; + return e.Message; + } + return getMessage ? "Executed successfully" : ""; + } + + public static void EndMessage(string? message) + { + if (message != null) + { + AnsiConsole.MarkupLine($"[olive]{message}[/]"); + AnsiConsole.Markup($"[grey]Press any key to continue.[/]"); + Console.ReadKey(intercept: true); + } + Console.Clear(); + } +} diff --git a/ExerciseTracker.TwilightSaw/Model/Exercise.cs b/ExerciseTracker.TwilightSaw/Model/Exercise.cs new file mode 100644 index 00000000..3d04a109 --- /dev/null +++ b/ExerciseTracker.TwilightSaw/Model/Exercise.cs @@ -0,0 +1,10 @@ +namespace ExerciseTracker.TwilightSaw.Model; + +public class Exercise(DateTime startTime, DateTime endTime, TimeSpan duration, string? comments) +{ + public int Id { get; set; } + public DateTime StartTime { get; set; } = startTime; + public DateTime EndTime { get; set; } = endTime; + public TimeSpan Duration { get; set; } = duration; + public string? Comments { get; set; } = comments; +} \ No newline at end of file diff --git a/ExerciseTracker.TwilightSaw/Program.cs b/ExerciseTracker.TwilightSaw/Program.cs new file mode 100644 index 00000000..e02abfc9 --- /dev/null +++ b/ExerciseTracker.TwilightSaw/Program.cs @@ -0,0 +1 @@ + diff --git a/ExerciseTracker.TwilightSaw/Repository/EfRepository.cs b/ExerciseTracker.TwilightSaw/Repository/EfRepository.cs new file mode 100644 index 00000000..fcb18b6b --- /dev/null +++ b/ExerciseTracker.TwilightSaw/Repository/EfRepository.cs @@ -0,0 +1,40 @@ +using ExerciseTracker.TwilightSaw.Data; +using Microsoft.EntityFrameworkCore; +using System.Collections.Generic; + +namespace ExerciseTracker.TwilightSaw.Repository; + +public class EfRepository(AppDbContext context) : IRepository where T : class +{ + private readonly DbSet _dbSet = context.Set(); + public T GetById(int id) + { + return _dbSet.Find(id); + } + + public IEnumerable GetAll() + { + return _dbSet.ToList(); + } + + public void Add(T entity) + { + _dbSet.Add(entity); + context.SaveChanges(); + } + + public void Update(T entity) + { + _dbSet.Update(entity); + context.SaveChanges(); + } + + public void Delete(int id) + { + var entity = _dbSet.Find(id); + if (entity == null) return; + _dbSet.Remove(entity); + context.SaveChanges(); + + } +} \ No newline at end of file diff --git a/ExerciseTracker.TwilightSaw/Repository/IRepository.cs b/ExerciseTracker.TwilightSaw/Repository/IRepository.cs new file mode 100644 index 00000000..7448cff5 --- /dev/null +++ b/ExerciseTracker.TwilightSaw/Repository/IRepository.cs @@ -0,0 +1,10 @@ +namespace ExerciseTracker.TwilightSaw.Repository; + +public interface IRepository +{ + T GetById(int id); + IEnumerable GetAll(); + void Add(T entity); + void Update(T entity); + void Delete(int id); +} \ No newline at end of file diff --git a/ExerciseTracker.TwilightSaw/View/Menu.cs b/ExerciseTracker.TwilightSaw/View/Menu.cs new file mode 100644 index 00000000..2541a20d --- /dev/null +++ b/ExerciseTracker.TwilightSaw/View/Menu.cs @@ -0,0 +1,6 @@ +namespace ExerciseTracker.TwilightSaw.View; + +public class Menu +{ + +} \ No newline at end of file diff --git a/ExerciseTracker.TwilightSaw/appsettings.json b/ExerciseTracker.TwilightSaw/appsettings.json new file mode 100644 index 00000000..5aa5a800 --- /dev/null +++ b/ExerciseTracker.TwilightSaw/appsettings.json @@ -0,0 +1,12 @@ +{ + "ConnectionStrings": { + "DefaultConnection": "Server=(localdb)\\localdb;Database=db;Trusted_Connection=True;" + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} From c2125758050a25b6874fec99ae568e9a6c583320 Mon Sep 17 00:00:00 2001 From: TwilightSaw <56154417+TwilightSaw@users.noreply.github.com> Date: Sun, 1 Dec 2024 19:31:19 +0100 Subject: [PATCH 2/4] ExerciseTracker 0.02 Added various functionalities, fixes, validations and others. --- .../.idea/.gitignore | 13 +++ .../.idea.exerciseTracker.doc415/.idea/.name | 1 + .../.idea/indexLayout.xml | 8 ++ .../.idea/vcs.xml | 6 + .../Controller/ExerciseController.cs | 110 ++++++++++++++++++ .../Controllers/ExerciseController.cs | 6 - .../ExerciseTracker.TwilightSaw.csproj | 7 +- .../Helper/UserInput.cs | 80 +++++++++++++ .../{Helpers => Helper}/Validation.cs | 0 .../Helpers/UserInput.cs | 41 ------- .../20241201093223_OnCreate.Designer.cs | 55 +++++++++ .../Migrations/20241201093223_OnCreate.cs | 37 ++++++ .../20241201174224_OnUpdateType.Designer.cs | 59 ++++++++++ .../Migrations/20241201174224_OnUpdateType.cs | 29 +++++ .../Migrations/AppDbContextModelSnapshot.cs | 56 +++++++++ ExerciseTracker.TwilightSaw/Model/Exercise.cs | 13 ++- ExerciseTracker.TwilightSaw/Program.cs | 21 +++- ...{EfRepository.cs => ExerciseRepository.cs} | 7 +- .../Repository/IRepository.cs | 1 + .../Service/ExerciseService.cs | 36 ++++++ ExerciseTracker.TwilightSaw/View/Menu.cs | 57 ++++++++- ExerciseTracker.TwilightSaw/appsettings.json | 2 +- 22 files changed, 589 insertions(+), 56 deletions(-) create mode 100644 ExerciseTracker.Doc415-r/.idea/.idea.exerciseTracker.doc415/.idea/.gitignore create mode 100644 ExerciseTracker.Doc415-r/.idea/.idea.exerciseTracker.doc415/.idea/.name create mode 100644 ExerciseTracker.Doc415-r/.idea/.idea.exerciseTracker.doc415/.idea/indexLayout.xml create mode 100644 ExerciseTracker.Doc415-r/.idea/.idea.exerciseTracker.doc415/.idea/vcs.xml create mode 100644 ExerciseTracker.TwilightSaw/Controller/ExerciseController.cs delete mode 100644 ExerciseTracker.TwilightSaw/Controllers/ExerciseController.cs create mode 100644 ExerciseTracker.TwilightSaw/Helper/UserInput.cs rename ExerciseTracker.TwilightSaw/{Helpers => Helper}/Validation.cs (100%) delete mode 100644 ExerciseTracker.TwilightSaw/Helpers/UserInput.cs create mode 100644 ExerciseTracker.TwilightSaw/Migrations/20241201093223_OnCreate.Designer.cs create mode 100644 ExerciseTracker.TwilightSaw/Migrations/20241201093223_OnCreate.cs create mode 100644 ExerciseTracker.TwilightSaw/Migrations/20241201174224_OnUpdateType.Designer.cs create mode 100644 ExerciseTracker.TwilightSaw/Migrations/20241201174224_OnUpdateType.cs create mode 100644 ExerciseTracker.TwilightSaw/Migrations/AppDbContextModelSnapshot.cs rename ExerciseTracker.TwilightSaw/Repository/{EfRepository.cs => ExerciseRepository.cs} (78%) create mode 100644 ExerciseTracker.TwilightSaw/Service/ExerciseService.cs diff --git a/ExerciseTracker.Doc415-r/.idea/.idea.exerciseTracker.doc415/.idea/.gitignore b/ExerciseTracker.Doc415-r/.idea/.idea.exerciseTracker.doc415/.idea/.gitignore new file mode 100644 index 00000000..9d13e61a --- /dev/null +++ b/ExerciseTracker.Doc415-r/.idea/.idea.exerciseTracker.doc415/.idea/.gitignore @@ -0,0 +1,13 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Rider ignored files +/contentModel.xml +/modules.xml +/projectSettingsUpdater.xml +/.idea.exerciseTracker.doc415.iml +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/ExerciseTracker.Doc415-r/.idea/.idea.exerciseTracker.doc415/.idea/.name b/ExerciseTracker.Doc415-r/.idea/.idea.exerciseTracker.doc415/.idea/.name new file mode 100644 index 00000000..29f3b51d --- /dev/null +++ b/ExerciseTracker.Doc415-r/.idea/.idea.exerciseTracker.doc415/.idea/.name @@ -0,0 +1 @@ +exerciseTracker.doc415 \ No newline at end of file diff --git a/ExerciseTracker.Doc415-r/.idea/.idea.exerciseTracker.doc415/.idea/indexLayout.xml b/ExerciseTracker.Doc415-r/.idea/.idea.exerciseTracker.doc415/.idea/indexLayout.xml new file mode 100644 index 00000000..7b08163c --- /dev/null +++ b/ExerciseTracker.Doc415-r/.idea/.idea.exerciseTracker.doc415/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/ExerciseTracker.Doc415-r/.idea/.idea.exerciseTracker.doc415/.idea/vcs.xml b/ExerciseTracker.Doc415-r/.idea/.idea.exerciseTracker.doc415/.idea/vcs.xml new file mode 100644 index 00000000..6c0b8635 --- /dev/null +++ b/ExerciseTracker.Doc415-r/.idea/.idea.exerciseTracker.doc415/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/ExerciseTracker.TwilightSaw/Controller/ExerciseController.cs b/ExerciseTracker.TwilightSaw/Controller/ExerciseController.cs new file mode 100644 index 00000000..f0ffff6c --- /dev/null +++ b/ExerciseTracker.TwilightSaw/Controller/ExerciseController.cs @@ -0,0 +1,110 @@ +using ExerciseTracker.TwilightSaw.Helpers; +using ExerciseTracker.TwilightSaw.Model; +using ExerciseTracker.TwilightSaw.Service; +using Microsoft.Identity.Client; +using Spectre.Console; + +namespace ExerciseTracker.TwilightSaw.Controllers; + +public class ExerciseController(ExerciseService service) +{ + public void AddExercise(string type) + { + Console.Clear(); + AnsiConsole.Write(new Rule("[cyan]Format - hh:mm[/]")); + var addStartInput = UserInput.CreateRegex("^(?:([0-1][0-9]|2[0-3]):([0-5][0-9])|0)$", "Insert the start of your Exercise", "Wrong format, try again."); + if (addStartInput == "0") return; + AnsiConsole.Write(new Rule("[cyan]Format - hh:mm[/]")); + var addEndInput = UserInput.CreateRegex("^(?:([0-1][0-9]|2[0-3]):([0-5][0-9])|0|(N|n))$", "Insert the end of your Exercise, N for time at this moment", "Wrong format."); + addEndInput = addEndInput is "N" or "n" ? DateTime.Now.AddSeconds(-DateTime.Now.Second).ToShortTimeString() : addEndInput; + if (addEndInput == "0") return; + var addComments = UserInput.Create("Add the comments, leave this field empty"); + if (addComments == "0") return; + + DateTime.TryParse(addStartInput, out var startTime); + DateTime.TryParse(addEndInput, out var endTime); + var exercise = new Exercise(type, startTime, endTime, addComments == "" ? null : addComments); + service.AddExercise(exercise); + Validation.EndMessage("Exercise added successfully."); + } + + public Exercise GetExercise(string type) + { + Console.Clear(); + var input = UserInput.CreateExerciseChoosingList(service.GetExerciseByType(type), "Return"); + return input; + } + + public void DeleteExercise(Exercise exercise) + { + Console.Clear(); + service.DeleteExercise(exercise.Id); + Validation.EndMessage("Exercise deleted successfully."); + } + + public void ChangeExercise(Exercise exercise) + { + Console.Clear(); + var type = exercise.Type; + var stringDate = exercise.StartTime.ToShortDateString(); + var stringStartTime = exercise.StartTime.TimeOfDay.ToString(); + var stringEndTime = exercise.EndTime.TimeOfDay.ToString(); + var comment = exercise.Comments; + + var changeInput = UserInput.CreateUpdateChoosingList([$"Type: {type}", + $"Date: {stringDate}", $"Start Time: {stringStartTime}", + $"End Time: {stringEndTime}", $"Comment: {comment}"], + exercise, "Return"); + switch (changeInput) + { + case "1": + var typeInput = UserInput.CreateChoosingList(["Cardio", "Weights"], "Return"); + if (typeInput == "Return") return; + exercise.Type = typeInput; + break; + case "2": + AnsiConsole.Write(new Rule("[olive]Format: dd.mm.yyyy[/]")); + var newDateInput = UserInput.CreateRegex(@"^(?:([0-2][0-9]|3[01])\.(0[1-9]|1[0-2])\.(\d{4})|(T|t)|0)$", + "Insert your new date, T for today's date", "Wrong format, try again."); + switch (newDateInput) + { + case "0": + return; + case "T" or "t": + newDateInput = DateTime.Now.ToShortDateString(); + break; + } + + DateTime.TryParse(newDateInput, out var newDate); + exercise.StartTime = newDate + exercise.StartTime.TimeOfDay; + exercise.EndTime = newDate + exercise.EndTime.TimeOfDay; + break; + case "3": + AnsiConsole.Write(new Rule("[olive]Format: hh:mm[/]")); + var newStartTimeInput = UserInput.CreateRegex("^(?:([0-1][0-9]|2[0-3]):([0-5][0-9])|0)$", + "Insert the start of your Exercise", "Wrong format, try again."); + if (newStartTimeInput == "0") return; + + DateTime.TryParse(newStartTimeInput, out var newStartTime); + exercise.StartTime = exercise.StartTime.Date + newStartTime.TimeOfDay; + break; + case "4": + AnsiConsole.Write(new Rule("[olive]Format: hh:mm[/]")); + var newEndTimeInput = UserInput.CreateRegex("^(?:([0-1][0-9]|2[0-3]):([0-5][0-9])|0|(N|n))$", + "Insert the end of your Exercise, N for time at this moment", "Wrong format."); + if (newEndTimeInput == "0") return; + newEndTimeInput = newEndTimeInput is "N" or "n" ? DateTime.Now.AddSeconds(-DateTime.Now.Second).ToShortTimeString() : newEndTimeInput; + + DateTime.TryParse(newEndTimeInput, out var newEndTime); + exercise.EndTime = exercise.EndTime.Date + newEndTime.TimeOfDay; + break; + case "5": + var newComment = UserInput.Create("Change your comment, leave this field empty"); + if (newComment == "0") return; + exercise.Comments = newComment; + break; + } + service.UpdateExercise(exercise); + Validation.EndMessage("Changed successfully."); + } +} \ No newline at end of file diff --git a/ExerciseTracker.TwilightSaw/Controllers/ExerciseController.cs b/ExerciseTracker.TwilightSaw/Controllers/ExerciseController.cs deleted file mode 100644 index 9148420a..00000000 --- a/ExerciseTracker.TwilightSaw/Controllers/ExerciseController.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace ExerciseTracker.TwilightSaw.Controllers; - -public class ExerciseController -{ - -} \ No newline at end of file diff --git a/ExerciseTracker.TwilightSaw/ExerciseTracker.TwilightSaw.csproj b/ExerciseTracker.TwilightSaw/ExerciseTracker.TwilightSaw.csproj index 8fb5f554..69a694ba 100644 --- a/ExerciseTracker.TwilightSaw/ExerciseTracker.TwilightSaw.csproj +++ b/ExerciseTracker.TwilightSaw/ExerciseTracker.TwilightSaw.csproj @@ -9,7 +9,6 @@ - @@ -27,4 +26,10 @@ + + + PreserveNewest + + + diff --git a/ExerciseTracker.TwilightSaw/Helper/UserInput.cs b/ExerciseTracker.TwilightSaw/Helper/UserInput.cs new file mode 100644 index 00000000..5a2d6482 --- /dev/null +++ b/ExerciseTracker.TwilightSaw/Helper/UserInput.cs @@ -0,0 +1,80 @@ +using System.ComponentModel.DataAnnotations; +using ExerciseTracker.TwilightSaw.Model; + +namespace ExerciseTracker.TwilightSaw.Helpers; + +using System.Text.RegularExpressions; +using Spectre.Console; + + +public class UserInput +{ + + public static string CreateRegex(string regexString, string messageStart, string messageError) + { + var regex = new Regex(regexString); + var input = AnsiConsole.Prompt( + new TextPrompt($"[green]{messageStart} or 0 to exit:[/]") + .Validate(value => regex.IsMatch(value) + ? ValidationResult.Success() + : ValidationResult.Error($"[red]{messageError}[/]"))); + Console.Clear(); + return input; + } + + public static string Create(string messageStart) + { + var input = AnsiConsole.Prompt( + new TextPrompt($"[green]{messageStart} or 0 to exit: [/]") + .AllowEmpty()); + Console.Clear(); + return input; + } + + public static string CreateChoosingList(List variants, string backVariant) + { + variants.Add(backVariant); + return AnsiConsole.Prompt(new SelectionPrompt() + .Title("[blue]Please, choose an option from the list below:[/]") + .PageSize(10) + .MoreChoicesText("[grey](Move up and down to reveal more categories[/]") + .AddChoices(variants)); + } + + public static Exercise CreateExerciseChoosingList(List variants, string? backVariant) + { + variants.Add(new Exercise("",DateTime.Now, DateTime.Now, backVariant)); + return AnsiConsole.Prompt(new SelectionPrompt() + .Title("[blue]Please, choose an option from the list below:[/]") + .PageSize(10) + .MoreChoicesText("[grey](Move up and down to reveal more categories[/]") + .UseConverter( + exercise => exercise.Comments != backVariant ? $"Date: {exercise.StartTime.ToShortDateString()}\n " + + $" Start Time: {exercise.StartTime.TimeOfDay}, " + + $"End Time: {exercise.EndTime.TimeOfDay}, " + + $"Duration: {exercise.Duration}\n" + + $" Comments: {exercise.Comments}" : + "[red]Return[/]" + ) + .AddChoices(variants)); + } + + public static string CreateUpdateChoosingList(List variants, Exercise exercise, string backVariant) + { + var var = AnsiConsole.Prompt(new SelectionPrompt() + .Title("[blue]Please, choose an option from the list below:[/]") + .PageSize(10) + .MoreChoicesText("[grey](Move up and down to reveal more categories[/]") + .AddChoices(variants)); + + var selectedIndex = variants.IndexOf(var); + return selectedIndex switch + { + 0 => "1", + 1 => "2", + 2 => "3", + 3 => "4", + _ => "5" + }; + } +} \ No newline at end of file diff --git a/ExerciseTracker.TwilightSaw/Helpers/Validation.cs b/ExerciseTracker.TwilightSaw/Helper/Validation.cs similarity index 100% rename from ExerciseTracker.TwilightSaw/Helpers/Validation.cs rename to ExerciseTracker.TwilightSaw/Helper/Validation.cs diff --git a/ExerciseTracker.TwilightSaw/Helpers/UserInput.cs b/ExerciseTracker.TwilightSaw/Helpers/UserInput.cs deleted file mode 100644 index b0e03698..00000000 --- a/ExerciseTracker.TwilightSaw/Helpers/UserInput.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace ExerciseTracker.TwilightSaw.Helpers; - -using System.Text.RegularExpressions; -using Spectre.Console; - - -public class UserInput -{ - - public static string CreateRegex(string regexString, string messageStart, string messageError) - { - Regex regex = new Regex(regexString); - var input = AnsiConsole.Prompt( - new TextPrompt($"[green]{messageStart} or 0 to exit:[/]") - .Validate(value => regex.IsMatch(value) - ? ValidationResult.Success() - : ValidationResult.Error($"[red]{messageError}[/]"))); - Console.Clear(); - return input; - } - - public static string Create(string messageStart) - { - var input = AnsiConsole.Prompt( - new TextPrompt($"[green]{messageStart} or 0 to exit: [/]")); - Console.Clear(); - return input; - } - - public static string CreateChoosingList(List variants, string backVariant) - { - variants.Add(backVariant); - return AnsiConsole.Prompt(new SelectionPrompt() - .Title("[blue]Please, choose an option from the list below:[/]") - .PageSize(10) - .MoreChoicesText("[grey](Move up and down to reveal more categories[/]") - .AddChoices(variants)); - } -} \ No newline at end of file diff --git a/ExerciseTracker.TwilightSaw/Migrations/20241201093223_OnCreate.Designer.cs b/ExerciseTracker.TwilightSaw/Migrations/20241201093223_OnCreate.Designer.cs new file mode 100644 index 00000000..237e3738 --- /dev/null +++ b/ExerciseTracker.TwilightSaw/Migrations/20241201093223_OnCreate.Designer.cs @@ -0,0 +1,55 @@ +// +using System; +using ExerciseTracker.TwilightSaw.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace ExerciseTracker.TwilightSaw.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20241201093223_OnCreate")] + partial class OnCreate + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.0") + .HasAnnotation("Proxies:ChangeTracking", false) + .HasAnnotation("Proxies:CheckEquality", false) + .HasAnnotation("Proxies:LazyLoading", true) + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("ExerciseTracker.TwilightSaw.Model.Exercise", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Comments") + .HasColumnType("nvarchar(max)"); + + b.Property("EndTime") + .HasColumnType("datetime2"); + + b.Property("StartTime") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.ToTable("Exercises"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/ExerciseTracker.TwilightSaw/Migrations/20241201093223_OnCreate.cs b/ExerciseTracker.TwilightSaw/Migrations/20241201093223_OnCreate.cs new file mode 100644 index 00000000..2b281a50 --- /dev/null +++ b/ExerciseTracker.TwilightSaw/Migrations/20241201093223_OnCreate.cs @@ -0,0 +1,37 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace ExerciseTracker.TwilightSaw.Migrations +{ + /// + public partial class OnCreate : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Exercises", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + StartTime = table.Column(type: "datetime2", nullable: false), + EndTime = table.Column(type: "datetime2", nullable: false), + Comments = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Exercises", x => x.Id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Exercises"); + } + } +} diff --git a/ExerciseTracker.TwilightSaw/Migrations/20241201174224_OnUpdateType.Designer.cs b/ExerciseTracker.TwilightSaw/Migrations/20241201174224_OnUpdateType.Designer.cs new file mode 100644 index 00000000..97c763e8 --- /dev/null +++ b/ExerciseTracker.TwilightSaw/Migrations/20241201174224_OnUpdateType.Designer.cs @@ -0,0 +1,59 @@ +// +using System; +using ExerciseTracker.TwilightSaw.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace ExerciseTracker.TwilightSaw.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20241201174224_OnUpdateType")] + partial class OnUpdateType + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.0") + .HasAnnotation("Proxies:ChangeTracking", false) + .HasAnnotation("Proxies:CheckEquality", false) + .HasAnnotation("Proxies:LazyLoading", true) + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("ExerciseTracker.TwilightSaw.Model.Exercise", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Comments") + .HasColumnType("nvarchar(max)"); + + b.Property("EndTime") + .HasColumnType("datetime2"); + + b.Property("StartTime") + .HasColumnType("datetime2"); + + b.Property("Type") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Exercises"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/ExerciseTracker.TwilightSaw/Migrations/20241201174224_OnUpdateType.cs b/ExerciseTracker.TwilightSaw/Migrations/20241201174224_OnUpdateType.cs new file mode 100644 index 00000000..659dbfcf --- /dev/null +++ b/ExerciseTracker.TwilightSaw/Migrations/20241201174224_OnUpdateType.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace ExerciseTracker.TwilightSaw.Migrations +{ + /// + public partial class OnUpdateType : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Type", + table: "Exercises", + type: "nvarchar(max)", + nullable: false, + defaultValue: ""); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Type", + table: "Exercises"); + } + } +} diff --git a/ExerciseTracker.TwilightSaw/Migrations/AppDbContextModelSnapshot.cs b/ExerciseTracker.TwilightSaw/Migrations/AppDbContextModelSnapshot.cs new file mode 100644 index 00000000..5e15a555 --- /dev/null +++ b/ExerciseTracker.TwilightSaw/Migrations/AppDbContextModelSnapshot.cs @@ -0,0 +1,56 @@ +// +using System; +using ExerciseTracker.TwilightSaw.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace ExerciseTracker.TwilightSaw.Migrations +{ + [DbContext(typeof(AppDbContext))] + partial class AppDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.0") + .HasAnnotation("Proxies:ChangeTracking", false) + .HasAnnotation("Proxies:CheckEquality", false) + .HasAnnotation("Proxies:LazyLoading", true) + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("ExerciseTracker.TwilightSaw.Model.Exercise", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Comments") + .HasColumnType("nvarchar(max)"); + + b.Property("EndTime") + .HasColumnType("datetime2"); + + b.Property("StartTime") + .HasColumnType("datetime2"); + + b.Property("Type") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Exercises"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/ExerciseTracker.TwilightSaw/Model/Exercise.cs b/ExerciseTracker.TwilightSaw/Model/Exercise.cs index 3d04a109..c6b55af1 100644 --- a/ExerciseTracker.TwilightSaw/Model/Exercise.cs +++ b/ExerciseTracker.TwilightSaw/Model/Exercise.cs @@ -1,10 +1,19 @@ namespace ExerciseTracker.TwilightSaw.Model; -public class Exercise(DateTime startTime, DateTime endTime, TimeSpan duration, string? comments) +public class Exercise(string type, DateTime startTime, DateTime endTime, string? comments) { public int Id { get; set; } + + public string Type { get; set; } = type; public DateTime StartTime { get; set; } = startTime; public DateTime EndTime { get; set; } = endTime; - public TimeSpan Duration { get; set; } = duration; + public TimeSpan Duration + { + get + { + var duration = EndTime.TimeOfDay - StartTime.TimeOfDay; + return duration.Ticks >= 0 ? duration : duration.Add(TimeSpan.FromHours(24)); + } + } public string? Comments { get; set; } = comments; } \ No newline at end of file diff --git a/ExerciseTracker.TwilightSaw/Program.cs b/ExerciseTracker.TwilightSaw/Program.cs index e02abfc9..395e1243 100644 --- a/ExerciseTracker.TwilightSaw/Program.cs +++ b/ExerciseTracker.TwilightSaw/Program.cs @@ -1 +1,20 @@ - +using ExerciseTracker.TwilightSaw.Controllers; +using ExerciseTracker.TwilightSaw.Data; +using ExerciseTracker.TwilightSaw.Factory; +using ExerciseTracker.TwilightSaw.Model; +using ExerciseTracker.TwilightSaw.Repository; +using ExerciseTracker.TwilightSaw.Service; +using ExerciseTracker.TwilightSaw.View; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Spectre.Console; + +var app = HostFactory.CreateDbHost(args); + +using var scope = app.Services.CreateScope(); +var context = scope.ServiceProvider.GetRequiredService(); +context.Database.Migrate(); + +new Menu(new ExerciseController(new ExerciseService(new ExerciseRepository(context)))).AddMenu(); + + diff --git a/ExerciseTracker.TwilightSaw/Repository/EfRepository.cs b/ExerciseTracker.TwilightSaw/Repository/ExerciseRepository.cs similarity index 78% rename from ExerciseTracker.TwilightSaw/Repository/EfRepository.cs rename to ExerciseTracker.TwilightSaw/Repository/ExerciseRepository.cs index fcb18b6b..4557eadd 100644 --- a/ExerciseTracker.TwilightSaw/Repository/EfRepository.cs +++ b/ExerciseTracker.TwilightSaw/Repository/ExerciseRepository.cs @@ -4,7 +4,7 @@ namespace ExerciseTracker.TwilightSaw.Repository; -public class EfRepository(AppDbContext context) : IRepository where T : class +public class ExerciseRepository(DbContext context) : IRepository where T : class { private readonly DbSet _dbSet = context.Set(); public T GetById(int id) @@ -12,6 +12,11 @@ public T GetById(int id) return _dbSet.Find(id); } + public IEnumerable GetAllByType(Func predicate) + { + return _dbSet.Where(predicate).ToList(); + } + public IEnumerable GetAll() { return _dbSet.ToList(); diff --git a/ExerciseTracker.TwilightSaw/Repository/IRepository.cs b/ExerciseTracker.TwilightSaw/Repository/IRepository.cs index 7448cff5..c3b4875a 100644 --- a/ExerciseTracker.TwilightSaw/Repository/IRepository.cs +++ b/ExerciseTracker.TwilightSaw/Repository/IRepository.cs @@ -3,6 +3,7 @@ public interface IRepository { T GetById(int id); + IEnumerable GetAllByType(Func predicate); IEnumerable GetAll(); void Add(T entity); void Update(T entity); diff --git a/ExerciseTracker.TwilightSaw/Service/ExerciseService.cs b/ExerciseTracker.TwilightSaw/Service/ExerciseService.cs new file mode 100644 index 00000000..0dce8813 --- /dev/null +++ b/ExerciseTracker.TwilightSaw/Service/ExerciseService.cs @@ -0,0 +1,36 @@ +using ExerciseTracker.TwilightSaw.Model; +using ExerciseTracker.TwilightSaw.Repository; + +namespace ExerciseTracker.TwilightSaw.Service; + +public class ExerciseService(IRepository repository) +{ + public void AddExercise(Exercise exercise) + { + repository.Add(exercise); + } + + public List GetExercises() + { + return repository.GetAll().ToList(); + } + + public Exercise GetExercise(int id) + { + return repository.GetById(id); + } + public List GetExerciseByType(string type) + { + return repository.GetAllByType(t => t.Type == type).ToList(); + } + + public void UpdateExercise(Exercise exercise) + { + repository.Update(exercise); + } + + public void DeleteExercise(int id) + { + repository.Delete(id); + } +} \ No newline at end of file diff --git a/ExerciseTracker.TwilightSaw/View/Menu.cs b/ExerciseTracker.TwilightSaw/View/Menu.cs index 2541a20d..0949b425 100644 --- a/ExerciseTracker.TwilightSaw/View/Menu.cs +++ b/ExerciseTracker.TwilightSaw/View/Menu.cs @@ -1,6 +1,57 @@ -namespace ExerciseTracker.TwilightSaw.View; +using ExerciseTracker.TwilightSaw.Controllers; +using ExerciseTracker.TwilightSaw.Helpers; +using Spectre.Console; -public class Menu +namespace ExerciseTracker.TwilightSaw.View; + +public class Menu(ExerciseController controller) { - + public void AddMenu() + { + while(true) + { + Console.Clear(); + AnsiConsole.Write(new Rule("[olive]Welcome to the Exercise Tracker![/]")); + var typeInput = UserInput.CreateChoosingList(["Cardio", "Weights"], "Exit"); + if (typeInput == "Exit") break; + var end = false; + while (!end) + { + Console.Clear(); + AnsiConsole.Write(new Rule($"[olive]{typeInput}[/]")); + switch (UserInput.CreateChoosingList(["Add the exercise", "Your exercises"], "Return")) + { + case "Add the exercise": + controller.AddExercise(typeInput); + break; + case "Your exercises": + while (true) + { + var chosenExercise = controller.GetExercise(typeInput); + Console.Clear(); + AnsiConsole.Write(new Rule($"[olive]{chosenExercise.StartTime.ToShortDateString()} " + + $"{chosenExercise.StartTime.TimeOfDay} " + + $"{chosenExercise.EndTime.TimeOfDay}[/]")); + if (chosenExercise.Comments == "Return") break; + switch (UserInput.CreateChoosingList(["Update exercise information", "Delete the exercise"], + "Return")) + { + case "Update exercise information": + controller.ChangeExercise(chosenExercise); + break; + case "Delete the exercise": + controller.DeleteExercise(chosenExercise); + break; + case "Return": + break; + } + } + break; + case "Return": + end = true; + break; + } + } + } + } } \ No newline at end of file diff --git a/ExerciseTracker.TwilightSaw/appsettings.json b/ExerciseTracker.TwilightSaw/appsettings.json index 5aa5a800..44185bd9 100644 --- a/ExerciseTracker.TwilightSaw/appsettings.json +++ b/ExerciseTracker.TwilightSaw/appsettings.json @@ -4,7 +4,7 @@ }, "Logging": { "LogLevel": { - "Default": "Information", + "Default": "None", "Microsoft.AspNetCore": "Warning" } }, From 2364a9ecd30c385072d9a7728982bcea9bffc49b Mon Sep 17 00:00:00 2001 From: TwilightSaw <56154417+TwilightSaw@users.noreply.github.com> Date: Mon, 2 Dec 2024 19:31:52 +0100 Subject: [PATCH 3/4] ExerciseTracker 0.021 Added another repository version --- .../ExerciseTracker.TwilightSaw.csproj | 1 + ExerciseTracker.TwilightSaw/Program.cs | 6 ++ .../Repository/ExerciseDapperRepository.cs | 67 +++++++++++++++++++ 3 files changed, 74 insertions(+) create mode 100644 ExerciseTracker.TwilightSaw/Repository/ExerciseDapperRepository.cs diff --git a/ExerciseTracker.TwilightSaw/ExerciseTracker.TwilightSaw.csproj b/ExerciseTracker.TwilightSaw/ExerciseTracker.TwilightSaw.csproj index 69a694ba..4dbbb025 100644 --- a/ExerciseTracker.TwilightSaw/ExerciseTracker.TwilightSaw.csproj +++ b/ExerciseTracker.TwilightSaw/ExerciseTracker.TwilightSaw.csproj @@ -13,6 +13,7 @@ + diff --git a/ExerciseTracker.TwilightSaw/Program.cs b/ExerciseTracker.TwilightSaw/Program.cs index 395e1243..a241e341 100644 --- a/ExerciseTracker.TwilightSaw/Program.cs +++ b/ExerciseTracker.TwilightSaw/Program.cs @@ -6,6 +6,7 @@ using ExerciseTracker.TwilightSaw.Service; using ExerciseTracker.TwilightSaw.View; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Spectre.Console; @@ -15,6 +16,11 @@ var context = scope.ServiceProvider.GetRequiredService(); context.Database.Migrate(); +var configuration = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json") + .Build(); + new Menu(new ExerciseController(new ExerciseService(new ExerciseRepository(context)))).AddMenu(); diff --git a/ExerciseTracker.TwilightSaw/Repository/ExerciseDapperRepository.cs b/ExerciseTracker.TwilightSaw/Repository/ExerciseDapperRepository.cs new file mode 100644 index 00000000..a4d93606 --- /dev/null +++ b/ExerciseTracker.TwilightSaw/Repository/ExerciseDapperRepository.cs @@ -0,0 +1,67 @@ +using ExerciseTracker.TwilightSaw.Model; +using System.IO; +using Dapper; +using Microsoft.Data.SqlClient; +using Microsoft.Extensions.Configuration; +using System.Data; + +namespace ExerciseTracker.TwilightSaw.Repository; + +public class ExerciseDapperRepository(IConfiguration configuration) : IRepository where T : class +{ + private readonly IDbConnection _connection; + + public T GetById(int id) + { + var tableName = typeof(T).Name; + using var connection = new SqlConnection(configuration.GetConnectionString("DefaultConnection")); + return connection.QuerySingleOrDefault($"SELECT * FROM {tableName} WHERE Id == @Id", new {Id = id}); + } + + public IEnumerable GetAllByType(Func predicate) + { + return GetAll().Where(predicate); + } + + public IEnumerable GetAll() + { + var tableName = typeof(T).Name; + using var connection = new SqlConnection(configuration.GetConnectionString("DefaultConnection")); + return connection.Query($"SELECT * FROM {tableName}").ToList(); + } + + public void Add(T entity) + { + var tableName = typeof(T).Name; + using var connection = new SqlConnection(configuration.GetConnectionString("DefaultConnection")); + connection.Execute(GenerateInsertQuery(entity), entity); + } + + public void Update(T entity) + { + var tableName = typeof(T).Name; + using var connection = new SqlConnection(configuration.GetConnectionString("DefaultConnection")); + connection.Execute(GenerateUpdateQuery(entity), entity); + } + + public void Delete(int id) + { + var tableName = typeof(T).Name; + using var connection = new SqlConnection(configuration.GetConnectionString("DefaultConnection")); + connection.Execute($"DELETE FROM {tableName} WHERE Id = @Id", new { Id = id }); + } + + private string GenerateInsertQuery(T entity) + { + var properties = typeof(T).GetProperties().Select(p => p.Name); + var columns = string.Join(", ", properties); + var values = string.Join(", ", properties.Select(p => $"@{p}")); + return $"INSERT INTO {typeof(T).Name} ({columns}) VALUES ({values})"; + } + private string GenerateUpdateQuery(T entity) + { + var properties = typeof(T).GetProperties().Select(p => $"{p.Name} = @{p.Name}"); + var setClause = string.Join(", ", properties); + return $"UPDATE {typeof(T).Name} SET {setClause} WHERE Id = @Id"; + } +} \ No newline at end of file From 0bb0a8b14a22e386131f03595672b738f1dd7e4c Mon Sep 17 00:00:00 2001 From: TwilightSaw <56154417+TwilightSaw@users.noreply.github.com> Date: Tue, 3 Dec 2024 19:08:36 +0100 Subject: [PATCH 4/4] ExerciseTracker 0.03 Finished the project, completely implemented second repository to use, added image folder and README --- .../Repository/ExerciseRepositoryDapper.cs | 2 +- .../Controller/ExerciseController.cs | 30 ++++++---- .../Data/AppDbContext.cs | 1 - .../ExerciseTracker.TwilightSaw.csproj | 1 + .../Helper/UserInput.cs | 7 +-- .../Helper/Validation.cs | 2 +- ExerciseTracker.TwilightSaw/Program.cs | 6 +- ExerciseTracker.TwilightSaw/README.md | 55 ++++++++++++++++++ .../Repository/ExerciseDapperRepository.cs | 38 ++++-------- .../Service/ExerciseService.cs | 10 +++- ExerciseTracker.TwilightSaw/View/Menu.cs | 51 ++++++++-------- .../images/appsettings.png | Bin 0 -> 10817 bytes ExerciseTracker.TwilightSaw/images/crud.png | Bin 0 -> 6576 bytes ExerciseTracker.TwilightSaw/images/crud2.png | Bin 0 -> 8010 bytes .../images/migrations.png | Bin 0 -> 7309 bytes .../images/repository.png | Bin 0 -> 17826 bytes ExerciseTracker.TwilightSaw/images/ui.png | Bin 0 -> 6888 bytes ExerciseTracker.TwilightSaw/images/ui2.png | Bin 0 -> 8318 bytes 18 files changed, 128 insertions(+), 75 deletions(-) create mode 100644 ExerciseTracker.TwilightSaw/README.md create mode 100644 ExerciseTracker.TwilightSaw/images/appsettings.png create mode 100644 ExerciseTracker.TwilightSaw/images/crud.png create mode 100644 ExerciseTracker.TwilightSaw/images/crud2.png create mode 100644 ExerciseTracker.TwilightSaw/images/migrations.png create mode 100644 ExerciseTracker.TwilightSaw/images/repository.png create mode 100644 ExerciseTracker.TwilightSaw/images/ui.png create mode 100644 ExerciseTracker.TwilightSaw/images/ui2.png diff --git a/ExerciseTracker.Doc415-r/Repository/ExerciseRepositoryDapper.cs b/ExerciseTracker.Doc415-r/Repository/ExerciseRepositoryDapper.cs index f8ef8403..9b998cd8 100644 --- a/ExerciseTracker.Doc415-r/Repository/ExerciseRepositoryDapper.cs +++ b/ExerciseTracker.Doc415-r/Repository/ExerciseRepositoryDapper.cs @@ -77,7 +77,7 @@ private async Task TInsert(Exercise exercise) Type = exercise.Type }); Console.Error.WriteLine(affectedRows); - } + } public void Update(Exercise exercise) { diff --git a/ExerciseTracker.TwilightSaw/Controller/ExerciseController.cs b/ExerciseTracker.TwilightSaw/Controller/ExerciseController.cs index f0ffff6c..51a5b0b4 100644 --- a/ExerciseTracker.TwilightSaw/Controller/ExerciseController.cs +++ b/ExerciseTracker.TwilightSaw/Controller/ExerciseController.cs @@ -1,10 +1,9 @@ -using ExerciseTracker.TwilightSaw.Helpers; +using ExerciseTracker.TwilightSaw.Helper; using ExerciseTracker.TwilightSaw.Model; using ExerciseTracker.TwilightSaw.Service; -using Microsoft.Identity.Client; using Spectre.Console; -namespace ExerciseTracker.TwilightSaw.Controllers; +namespace ExerciseTracker.TwilightSaw.Controller; public class ExerciseController(ExerciseService service) { @@ -12,11 +11,15 @@ public void AddExercise(string type) { Console.Clear(); AnsiConsole.Write(new Rule("[cyan]Format - hh:mm[/]")); - var addStartInput = UserInput.CreateRegex("^(?:([0-1][0-9]|2[0-3]):([0-5][0-9])|0)$", "Insert the start of your Exercise", "Wrong format, try again."); + var addStartInput = UserInput.CreateRegex("^(?:([0-1][0-9]|2[0-3]):([0-5][0-9])|0)$", + "Insert the start of your Exercise", "Wrong format, try again."); if (addStartInput == "0") return; AnsiConsole.Write(new Rule("[cyan]Format - hh:mm[/]")); - var addEndInput = UserInput.CreateRegex("^(?:([0-1][0-9]|2[0-3]):([0-5][0-9])|0|(N|n))$", "Insert the end of your Exercise, N for time at this moment", "Wrong format."); - addEndInput = addEndInput is "N" or "n" ? DateTime.Now.AddSeconds(-DateTime.Now.Second).ToShortTimeString() : addEndInput; + var addEndInput = UserInput.CreateRegex("^(?:([0-1][0-9]|2[0-3]):([0-5][0-9])|0|(N|n))$", + "Insert the end of your Exercise, N for time at this moment", "Wrong format."); + addEndInput = addEndInput is "N" or "n" + ? DateTime.Now.AddSeconds(-DateTime.Now.Second).ToShortTimeString() + : addEndInput; if (addEndInput == "0") return; var addComments = UserInput.Create("Add the comments, leave this field empty"); if (addComments == "0") return; @@ -51,9 +54,11 @@ public void ChangeExercise(Exercise exercise) var stringEndTime = exercise.EndTime.TimeOfDay.ToString(); var comment = exercise.Comments; - var changeInput = UserInput.CreateUpdateChoosingList([$"Type: {type}", - $"Date: {stringDate}", $"Start Time: {stringStartTime}", - $"End Time: {stringEndTime}", $"Comment: {comment}"], + var changeInput = UserInput.CreateUpdateChoosingList([ + $"Type: {type}", + $"Date: {stringDate}", $"Start Time: {stringStartTime}", + $"End Time: {stringEndTime}", $"Comment: {comment}" + ], exercise, "Return"); switch (changeInput) { @@ -90,10 +95,12 @@ public void ChangeExercise(Exercise exercise) break; case "4": AnsiConsole.Write(new Rule("[olive]Format: hh:mm[/]")); - var newEndTimeInput = UserInput.CreateRegex("^(?:([0-1][0-9]|2[0-3]):([0-5][0-9])|0|(N|n))$", + var newEndTimeInput = UserInput.CreateRegex("^(?:([0-1][0-9]|2[0-3]):([0-5][0-9])|0|(N|n))$", "Insert the end of your Exercise, N for time at this moment", "Wrong format."); if (newEndTimeInput == "0") return; - newEndTimeInput = newEndTimeInput is "N" or "n" ? DateTime.Now.AddSeconds(-DateTime.Now.Second).ToShortTimeString() : newEndTimeInput; + newEndTimeInput = newEndTimeInput is "N" or "n" + ? DateTime.Now.AddSeconds(-DateTime.Now.Second).ToShortTimeString() + : newEndTimeInput; DateTime.TryParse(newEndTimeInput, out var newEndTime); exercise.EndTime = exercise.EndTime.Date + newEndTime.TimeOfDay; @@ -104,6 +111,7 @@ public void ChangeExercise(Exercise exercise) exercise.Comments = newComment; break; } + service.UpdateExercise(exercise); Validation.EndMessage("Changed successfully."); } diff --git a/ExerciseTracker.TwilightSaw/Data/AppDbContext.cs b/ExerciseTracker.TwilightSaw/Data/AppDbContext.cs index a55b27e8..2f82aac0 100644 --- a/ExerciseTracker.TwilightSaw/Data/AppDbContext.cs +++ b/ExerciseTracker.TwilightSaw/Data/AppDbContext.cs @@ -1,7 +1,6 @@ using ExerciseTracker.TwilightSaw.Model; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; namespace ExerciseTracker.TwilightSaw.Data; diff --git a/ExerciseTracker.TwilightSaw/ExerciseTracker.TwilightSaw.csproj b/ExerciseTracker.TwilightSaw/ExerciseTracker.TwilightSaw.csproj index 4dbbb025..2d809805 100644 --- a/ExerciseTracker.TwilightSaw/ExerciseTracker.TwilightSaw.csproj +++ b/ExerciseTracker.TwilightSaw/ExerciseTracker.TwilightSaw.csproj @@ -9,6 +9,7 @@ + diff --git a/ExerciseTracker.TwilightSaw/Helper/UserInput.cs b/ExerciseTracker.TwilightSaw/Helper/UserInput.cs index 5a2d6482..f3fcb25e 100644 --- a/ExerciseTracker.TwilightSaw/Helper/UserInput.cs +++ b/ExerciseTracker.TwilightSaw/Helper/UserInput.cs @@ -1,11 +1,8 @@ -using System.ComponentModel.DataAnnotations; +using System.Text.RegularExpressions; using ExerciseTracker.TwilightSaw.Model; - -namespace ExerciseTracker.TwilightSaw.Helpers; - -using System.Text.RegularExpressions; using Spectre.Console; +namespace ExerciseTracker.TwilightSaw.Helper; public class UserInput { diff --git a/ExerciseTracker.TwilightSaw/Helper/Validation.cs b/ExerciseTracker.TwilightSaw/Helper/Validation.cs index eaac2cdd..8ac66016 100644 --- a/ExerciseTracker.TwilightSaw/Helper/Validation.cs +++ b/ExerciseTracker.TwilightSaw/Helper/Validation.cs @@ -1,6 +1,6 @@ using Spectre.Console; -namespace ExerciseTracker.TwilightSaw.Helpers; +namespace ExerciseTracker.TwilightSaw.Helper; public class Validation { diff --git a/ExerciseTracker.TwilightSaw/Program.cs b/ExerciseTracker.TwilightSaw/Program.cs index a241e341..858e69c1 100644 --- a/ExerciseTracker.TwilightSaw/Program.cs +++ b/ExerciseTracker.TwilightSaw/Program.cs @@ -1,4 +1,4 @@ -using ExerciseTracker.TwilightSaw.Controllers; +using ExerciseTracker.TwilightSaw.Controller; using ExerciseTracker.TwilightSaw.Data; using ExerciseTracker.TwilightSaw.Factory; using ExerciseTracker.TwilightSaw.Model; @@ -8,7 +8,6 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Spectre.Console; var app = HostFactory.CreateDbHost(args); @@ -21,6 +20,7 @@ .AddJsonFile("appsettings.json") .Build(); -new Menu(new ExerciseController(new ExerciseService(new ExerciseRepository(context)))).AddMenu(); +new Menu(new ExerciseController(new ExerciseService(new ExerciseRepository(context), + new ExerciseDapperRepository(configuration)))).AddMenu(); diff --git a/ExerciseTracker.TwilightSaw/README.md b/ExerciseTracker.TwilightSaw/README.md new file mode 100644 index 00000000..5ba57575 --- /dev/null +++ b/ExerciseTracker.TwilightSaw/README.md @@ -0,0 +1,55 @@ +# ExerciseTracker + +## Given Requirements: +- [x] This is an application where you can record exercise data. +- [x] You should be able to Add, Delete, Update and Read from a database, using the console. +- [x] Using Entity Framework or/and raw SQL. +- [x] Using SQL Server or SQLite. +- [x] Use Dependency Injection in the repository. +- [x] Application should have at least the following classes: UserInput, ExerciseController, ExerciseService and ExerciseRepository. +- [x] The Exercise model class should have at least the following properties: {Id INT, DateStart DateTime, DateEnd DateTime, Duration TimeSpan, Comments string}. + +## Features + +* SQL Server database connection with Entity Framework and Dapper ORM. +> [!IMPORTANT] +> After downloading the project, you should check appsetting.json and write your own path to connect the db. +> +> ![image](https://github.com/TwilightSaw/CodeReviews.Console.ExerciseTracker/blob/main/ExerciseTracker.TwilightSaw/images/appsettings.png) + +> [!IMPORTANT] +> Also you should do starting migrations to create db with all necessary tables, simply write ```dotnet ef database update``` in CLI. +> +> ![image](https://github.com/TwilightSaw/CodeReviews.Console.ExerciseTracker/blob/main/ExerciseTracker.TwilightSaw/images/migrations.png) + +* A console based UI where you can navigate by user input. + + ![image](https://github.com/TwilightSaw/CodeReviews.Console.ExerciseTracker/blob/main/ExerciseTracker.TwilightSaw/images/ui.png) + + ![image](https://github.com/TwilightSaw/CodeReviews.Console.ExerciseTracker/blob/main/ExerciseTracker.TwilightSaw/images/crud.png) + +* CRUD abilities for your tracked exercises. + + ![image](https://github.com/TwilightSaw/CodeReviews.Console.ExerciseTracker/blob/main/ExerciseTracker.TwilightSaw/images/ui2.png) + + ![image](https://github.com/TwilightSaw/CodeReviews.Console.ExerciseTracker/blob/main/ExerciseTracker.TwilightSaw/images/crud2.png) + +* Repository pattern structure + + ![image](https://github.com/TwilightSaw/CodeReviews.Console.ExerciseTracker/blob/main/ExerciseTracker.TwilightSaw/images/repository.png) + +## Challenges and Learned Lessons +- Repository pattern is very useful thing, you can swap different implementations of the same repository and it will work fine as well. +- Moreover, you can say the same about interfaces as a whole. +- Delegates, predicates and generic types is a must to know. + +## Areas to Improve +- Better usage of delegates and generic type parameters. + +## Resources Used +- C# Academy guidelines and roadmap. +- ChatGPT for new information as EF usage, Repository Pattern, etc.. +- Spectre.Console documentation. +- EF and Dapper ORM documentation. +- Various StackOverflow articles. + diff --git a/ExerciseTracker.TwilightSaw/Repository/ExerciseDapperRepository.cs b/ExerciseTracker.TwilightSaw/Repository/ExerciseDapperRepository.cs index a4d93606..91730471 100644 --- a/ExerciseTracker.TwilightSaw/Repository/ExerciseDapperRepository.cs +++ b/ExerciseTracker.TwilightSaw/Repository/ExerciseDapperRepository.cs @@ -3,19 +3,16 @@ using Dapper; using Microsoft.Data.SqlClient; using Microsoft.Extensions.Configuration; -using System.Data; namespace ExerciseTracker.TwilightSaw.Repository; public class ExerciseDapperRepository(IConfiguration configuration) : IRepository where T : class { - private readonly IDbConnection _connection; - + private const string TableName = "Exercises"; public T GetById(int id) { - var tableName = typeof(T).Name; using var connection = new SqlConnection(configuration.GetConnectionString("DefaultConnection")); - return connection.QuerySingleOrDefault($"SELECT * FROM {tableName} WHERE Id == @Id", new {Id = id}); + return connection.QuerySingleOrDefault($"SELECT * FROM {TableName} WHERE Id == @Id", new {Id = id}); } public IEnumerable GetAllByType(Func predicate) @@ -25,43 +22,30 @@ public IEnumerable GetAllByType(Func predicate) public IEnumerable GetAll() { - var tableName = typeof(T).Name; using var connection = new SqlConnection(configuration.GetConnectionString("DefaultConnection")); - return connection.Query($"SELECT * FROM {tableName}").ToList(); + return connection.Query($"SELECT * FROM {TableName}").ToList(); } public void Add(T entity) { - var tableName = typeof(T).Name; using var connection = new SqlConnection(configuration.GetConnectionString("DefaultConnection")); - connection.Execute(GenerateInsertQuery(entity), entity); + var properties = new List { "StartTime", "EndTime", "Comments", "Type" }; + var columns = string.Join(", ", properties); + var values = string.Join(", ", properties.Select(p => $"@{p}")); + var insertQuery = $"INSERT INTO {TableName} ({columns}) VALUES ({values})"; + connection.Execute(insertQuery, entity); } public void Update(T entity) { - var tableName = typeof(T).Name; using var connection = new SqlConnection(configuration.GetConnectionString("DefaultConnection")); - connection.Execute(GenerateUpdateQuery(entity), entity); + const string updateQuery = $"UPDATE {TableName} SET StartTime = @StartTime, EndTime = @EndTime, Comments = @Comments, Type = @Type WHERE Id = @Id"; + connection.Execute(updateQuery, entity); } public void Delete(int id) { - var tableName = typeof(T).Name; using var connection = new SqlConnection(configuration.GetConnectionString("DefaultConnection")); - connection.Execute($"DELETE FROM {tableName} WHERE Id = @Id", new { Id = id }); - } - - private string GenerateInsertQuery(T entity) - { - var properties = typeof(T).GetProperties().Select(p => p.Name); - var columns = string.Join(", ", properties); - var values = string.Join(", ", properties.Select(p => $"@{p}")); - return $"INSERT INTO {typeof(T).Name} ({columns}) VALUES ({values})"; - } - private string GenerateUpdateQuery(T entity) - { - var properties = typeof(T).GetProperties().Select(p => $"{p.Name} = @{p.Name}"); - var setClause = string.Join(", ", properties); - return $"UPDATE {typeof(T).Name} SET {setClause} WHERE Id = @Id"; + connection.Execute($"DELETE FROM {TableName} WHERE Id = @Id", new { Id = id }); } } \ No newline at end of file diff --git a/ExerciseTracker.TwilightSaw/Service/ExerciseService.cs b/ExerciseTracker.TwilightSaw/Service/ExerciseService.cs index 0dce8813..f28cefdb 100644 --- a/ExerciseTracker.TwilightSaw/Service/ExerciseService.cs +++ b/ExerciseTracker.TwilightSaw/Service/ExerciseService.cs @@ -3,11 +3,13 @@ namespace ExerciseTracker.TwilightSaw.Service; -public class ExerciseService(IRepository repository) +public class ExerciseService(IRepository repository, IRepository dapperRepository) { public void AddExercise(Exercise exercise) { - repository.Add(exercise); + if(exercise.Type == "Cardio") + repository.Add(exercise); + else dapperRepository.Add(exercise); } public List GetExercises() @@ -26,7 +28,9 @@ public List GetExerciseByType(string type) public void UpdateExercise(Exercise exercise) { - repository.Update(exercise); + if (exercise.Type == "Cardio") + repository.Update(exercise); + else dapperRepository.Update(exercise); } public void DeleteExercise(int id) diff --git a/ExerciseTracker.TwilightSaw/View/Menu.cs b/ExerciseTracker.TwilightSaw/View/Menu.cs index 0949b425..5f17dbd4 100644 --- a/ExerciseTracker.TwilightSaw/View/Menu.cs +++ b/ExerciseTracker.TwilightSaw/View/Menu.cs @@ -1,5 +1,5 @@ -using ExerciseTracker.TwilightSaw.Controllers; -using ExerciseTracker.TwilightSaw.Helpers; +using ExerciseTracker.TwilightSaw.Controller; +using ExerciseTracker.TwilightSaw.Helper; using Spectre.Console; namespace ExerciseTracker.TwilightSaw.View; @@ -25,27 +25,7 @@ public void AddMenu() controller.AddExercise(typeInput); break; case "Your exercises": - while (true) - { - var chosenExercise = controller.GetExercise(typeInput); - Console.Clear(); - AnsiConsole.Write(new Rule($"[olive]{chosenExercise.StartTime.ToShortDateString()} " + - $"{chosenExercise.StartTime.TimeOfDay} " + - $"{chosenExercise.EndTime.TimeOfDay}[/]")); - if (chosenExercise.Comments == "Return") break; - switch (UserInput.CreateChoosingList(["Update exercise information", "Delete the exercise"], - "Return")) - { - case "Update exercise information": - controller.ChangeExercise(chosenExercise); - break; - case "Delete the exercise": - controller.DeleteExercise(chosenExercise); - break; - case "Return": - break; - } - } + ShowExercise(typeInput); break; case "Return": end = true; @@ -54,4 +34,29 @@ public void AddMenu() } } } + + private void ShowExercise(string typeInput) + { + while (true) + { + var chosenExercise = controller.GetExercise(typeInput); + Console.Clear(); + AnsiConsole.Write(new Rule($"[olive]{chosenExercise.StartTime.ToShortDateString()} " + + $"{chosenExercise.StartTime.TimeOfDay} " + + $"{chosenExercise.EndTime.TimeOfDay}[/]")); + if (chosenExercise.Comments == "Return") break; + switch (UserInput.CreateChoosingList(["Update exercise information", "Delete the exercise"], + "Return")) + { + case "Update exercise information": + controller.ChangeExercise(chosenExercise); + break; + case "Delete the exercise": + controller.DeleteExercise(chosenExercise); + break; + case "Return": + break; + } + } + } } \ No newline at end of file diff --git a/ExerciseTracker.TwilightSaw/images/appsettings.png b/ExerciseTracker.TwilightSaw/images/appsettings.png new file mode 100644 index 0000000000000000000000000000000000000000..9e60afbe5cb1d23be2d698e534fca98d83678fa2 GIT binary patch literal 10817 zcmXw9byQW|(>{dKph%~bba$h)bmyfT>F$zlkSH9e!>;yCDD)xkO2Tdla>-w1_0Q!mv<3F*q8gN@%`}21Ib=W%LxF`dtWb@ zBzkm00Qd+SnNeSw)3%;zu_u3nOD+>H*b_oY83#i~Y0`o5GZ3CC$q>Tdso*Zye4Uf% zb=+*e?8I_HQG~i{>DDK@zwciKka8upY;C2v_vu!j$9ljYaM1o6e%Bo%Sgcy1 zn7GGu5svcq^Q-c9wB&7PK0yJ2hjr=T6}EcTnQoJfpf9hVOh&2zkzZ4tHybEvvRs~Q z!@an|Pn2o1o|~~F4bqWlukvw{J?L0l2pA*`^z?*+X=R0H^7Zq*;5kT;fX5%Q7ZVZB zq?$xS(TN4zS@`+ya|f|N;2A2bl(hHiHT(U9DEhvZT6HPxQ_J=xqAv<>JFiS;hPgZH&5*9HmcJgHpI2KloA~~E3+T?P zR5OE3wDC4X3U{p-Am?)8+1`%b)!qj^JdsNS`uvr|4GZ7DNl+xxhylfVtrWp;eQGC{ zR!!C-RSzK=z93lO=y@4RcSq}EUVIhr1+W{lBWnrC%gmr{UA28ZaJqKVA;sC@aG9zn zbu~X3peA`;7h@hZLua3@pzXM;naZ21(j3JyGLUqp@fOc^%*vy~oI0MW5DChJKVe`{ zQU=h5ROd zh@}TcAMy)$T{{A3$z+t6gR`^AeMW+mmY$w>1>fPjjo@c(P6`98m9o-iJCc9b0dv`K+55`HS)KLOMV0j*?Uodvla_{^kI{>I z-wr>RM5s7x=rx_)1(PtQmaQgNO=o<8H&t-j*3eJd8H5SL$^G5)Hfa&S1Q3a0EGHC0PJqM+c?N6!K(thk? z>a!PruTaiijH$-^KGyFcYKR#MzKM@ctuXQaFwsT&DD4chq55#ZH*Y5}CO0b3wZn-I z0GU(FChCGIck*~|dP@1g4Oyfw#v(fT33TTFbs7s`;T~z1sspM{%Q=zu-&U8`CRR5z zmU}+`6US$>{~B}aoXd_@iIdVO?Y)O`qqWlcC<{q<>*tr5K zv5)PLHCAr!ramxYJg0ej-B0(ce!?!9XzO@gu}CNYC8p(E1qk)+=DM^r<)oOT%J7G( zX4vSjREczV^?-qym*G1Wec6L}KmEIU+3h&I)AVZ*w7Kc{RgblEnNT|H zhIkTs2e|O^Z%9e4uAtE^g1ga^sDA%3c2@e-?)+AC>JHER#c97`IuulqAt!bmJ{nu% z*1E?jdVJj~BjwubUsN`K|9<$=)2ENgO(eT)e-}i~C^8^vMU zp|N_C`(5Tzv%TBLo{HDYse5JwD*VGiO5RDitg1`+2PEV)T=4An@Hzo(fd}}*9V;!G$v%ert7~?%3!%bRt=k-lz7uCgs-z{tDX|EaD8p+Pd=j0_) zsY&w=@jHeV3@*@?-1*qIBcj%KStg+R3MR6w4G-E>npJ6EX{A!|wP@qX^ zmY1Kv+CP9s=R7+kAwQbVzrk3oo7slZ-XFz@V+R+s;Gy>W4b6QXs4~?U1V$AHOJX!8 zSuI{Q^Ly_}VhjXg0z7hFlOE;Tt5BY2cm^CGeX^pg#qfN@@wqqn+&89V{$5(CUldu^ zSQ@HSA@u@UH^ZVaOr|s5Y1FEsJ%{cZGDQBkeMD1f9$%&r6n(OXrPO zd~mjMYD;z+2*Z0yj;)N3OT@7cX>Ts6#OuacNQFrcE=oM7`(r*LvL#i)y}2Dsr2fT+ znFa3+!;4U%2d%ZuVg{%$@<~+Mp!>+9b?hAdCY<4U%$%`tH3A!iis zsixpu3LzQm)!*sk$qWM?sv16ClxtkK0@p$nI=ykLi0x>kQg2zY8F$=fjC zsMT%FskiafPu2`w_WDqs^{{eg)Fkz4Rq^d15`rzqA@r_X|60=A3|a z#vjL(!(vLAuPH3-r5j=%8lAb}D>RTd0C1Sl=zTE(G1Cz`(=;=~YHP|TsjvRuQk+LQ ztEs6$LKFX0WUL`HAq&C!aJ~5r7H8Vga5J~j@%h$AXmfiUTL;7vVXH22l%i(LJQQ$# zU++iQTcOuT34Md=-@*hvnLS^O6gFe!lF!LVJ-O8d6uFCX+LY+00&g;Eu`7lJHNLs% zztNs;7G>5BA$iJ}GXM#_Kt?6Y@BOJn)fczlcAisYxJx4}$V#4^2s z>SQ+bLy*MW8CZbhkk_u(*tV;oF%W1uzsfWeZuAVNe+%IFbaEZJ?hGCC5CEE~_Ldz` zgNZf|xwo&VZB6JASFul8$+DW-yms-;#oSO(gMYl=7N`B=nFkyIBJn| z`}#{+^@pU!N;O`YM6seE8}Un0!B%~tLydMze~S3D?b6zBH|DLGH&7cx^4D<8Rv+6U z?_D*MZ|yN%N%m_nu?6{pHa9jZK&@Pc@gFx~&9_CWdxOHWz{~F35_U`BHQ}^>VSYBA z2sQ#j3793If?J2=KkiZvHW?Al68*}%-*2ipfjU}iIlA$SO19N%I&XdgGq#IGBzR!-*=3U;v2ffDATZj`iy~4>DO*CcLpWZ=Y%YMzs&s5A-Irc88~3^V`Fwv z6awLe@EYb1gl5zmu4?y`%w2P!cZOp!TAGxEm=s(;N$fO`V7|nw(Z-kg!}Fb^#I3@B zTLbLc2scA?&apg&5k~~cYPt174sIm1U2bc<{Rk8#sJz*ulDOEw5&$%XpX)FVUBAgV z5d5OhmA{8N64;!#OfUHDZ&GnrbhyJEkfuB+sqQ+dEmtmbc2!7KUTYHW?`dnf9-q;J z%gfGUvvIXOX?47>Rjuz0uCKcg=Uasg1Ue9au&Zdd9rymxCC)@kM1a7QyLG2=>egH= zzk#856x25H`>cGkK5>GC$3eg-mf<=NZH1i1@7N&+pgGW>ltg9{ewO@a4BZi$9Qi(P z9t0hc=20BHK{JKq&<+wGfX`T2AqyNU7gv3awiwJcXi?zePe1fTMg0dDhK~jHAnbY= zM;IqjS&j5xJnoz&Z&Y9+tT)dD%0sDb50@<3&j_MjrA}$gK`kAf=`4a|EWA{PxCNcw zEnYu(Wp2 z0Ra{kCSioEkKg7rNQrOvSzY-B8ZV!!c8)^R$o>q*)C@oK-lD|{Ff-9a6?o+>ON-h& zo#uxlkE&H??>1P2^C6Cm*IU=fqvH^_O+bq-Ik`)34G~ycFdP zCq6uaB;L(&iBa@koe|0);PALf=wk%{0`XxpRvgzTjtDu& ztB4NdR%(;B>b~pL15Msw15;=4*cbb&rRQ|fDUE-^HhB23(8BA)e|+zNu61a=12DdH zHayVx!vZ0HXQMIJVG;OQ?l5-^00tJf8<9#*0yJVaev+npjR++u_ZH0UIYf(d3~tW# z+^TNeNzc}93v%?GkCnVv>hxJ~cp)XLA%~fbF(;J%Xxc>go`LQ7N=wWZGQ#do`bn7n z9A0W$Er^8dnDV7@9IH*Y>XE~K*)HsFfUvv0P#R!uJ5%Spmc?T@!DcGz1?WGy9SCa? z0YKSfR9vU<_9#bpi5H)3_Q>s_Sq>lF{tcTW`@rAf=)gpjqs-Q)Kcop>&Am7mbl*a~ zU22;vJ2Ir0g>P0B#ABb3yeN%=FS~LmVbM{v>$Opaj?U9G#xTB7@prTfn6*T|muK8m z-STy==$K)=+I3_uNl(d`gOVJX#i-P+4n4%OZDon;+H0$?V~^Yp8p7IHrDxyCk|#}+Tn+XpLB3^U6@PR8qNu~|AZd?Rq<&xds$CL zMH%Dx)4ft5X^a3ft>FsCi~NzH2B}bzpF{yn4RjbKePqQ!6`LL(>a^SX| ze7*t4&{0eX%0DOH^_a8XFm_1Qw^>+jpaerq|8;K+G_V=FAja=uC*s_ zsk}WnR+iEE(t#gkEqU^>;|4m8ItMF!5j&m^n{>i+Z;txJIfp`haAA$767UCYIr18E z#^`R3WTh)m5pj-=3q#~;xwwF&x>8o?&82A8C&D=<&!PC=YJ>2MGYDf?reB@^VFv6q zT1)@t#;!d4U5R(*!phM}3eo<{ftO$mtl?d4e${vRym+2+93wkUv&0K({HiFRh4F1hPPqq@z}vxVaEkODy7^|K(sk$Qo|piPvi$$pv(DqMYCImhv4 zBw9Ja5{ABf_)jAaWTe(2lk4V^uh_uCDfCxHR8EmN04m#ooWU=brSRN6^2+ZFgE}Vs8M0`p4iOcrJTO!dPms{>s`t@vy)1)qd_B zGyZe_(|&~{E$Ych%QW4uxS|s8@+7$t1$sX;g}{a$@o`=68D-SFXEIHCl!FZh4(Ogl zY5g9+|8|j!NSQgftDOlOcIr5>kF3KPt~{@m>G%#(>ChVRBQ>Fk_WS3_EA?cTea+v7 zao`OgqoX7W)Ck`WjB8#gQ2;=|{zHGlUObmXlNY}A`zp8g&`c_)vuajVnG9Qy&pn1~ zqvYX4@HAILZYT;Ggqfy2`O-H)^G(6~x#Af5-@R^w2GP6@+l6AlyVaY$n^AR;pneO} z;P>eyoe}qP33L+Y%(mL6>C=PIDql!NtUbQNI^^iBB72f{wC=RpC`^-%mWBg;&G!*^ zJA#hR&Beh;@eY~BC9cj!>g%h=CyMNt`h{>&h-3XUaW=|_#w#Wfik9)3u->+n`zV-E-2O6rg& z8hkG-^$`y_jn zga65!tn5wz7M@~)uZR>|L;7%wiL?K3+cmA}cqL|)pe8s=xG%<@WM zf>1#~{il0xI_9fgBQO0)D7VlcvUbguCAU{d1(V<0->PQ$^vMw?>V>bsZc5lbCA4^< z?mZkm1DzVfX!*d{0WIKwa(IR)fz&wd3Xd1R-fu#htaH z9Bs5q?v{?z&Ltnux3(qB)=EzPrZg?jExlCB3bq0#=fnqLsp2p~=O_!Eu)ts-+3@4V zPjXlFizowy=Rj{giwn3p%cF_pu=g5@pBL?x9^oQfLQ zp~$h7Fi%Ozvozco=b3K$yzZ57?%**u(yWuMv_TT-Y%GeX0M-}-6SnufA{qT0@tmuR z*9bXq%DAMh&9i2xyRd+v+$F{>$Y-3xJS6G8?b_$&Uu?B}?GkS;VivC6@-WXX{>IH;T!!0H2yMgjxq-~g%xF?3-4Gcu`B&OAKsFXhU@7>}91{(ut(hjwz zSk>k&Y#imcDPn8GbzP1!3e|V2<8CD_6in00==*Pv%olBsniqxeu>=}x4eAj4lovz~ zs)7X4a7>hXD$0tM#tZ6Q+~*dS(M>+fQrKGdo4dgSimPllG?F#03wTF|U;5Vvc?7a~ zhQ56j!)5<~{p$u(1Ffrdl00*p{f5+0@1E4=k#5bD3wfglT%?q%?EP%49lf)v655nk zA@GS+;P2T0>eGsO*%JyCIFi)D%kLfI-yzir<)%*mC1gQ#CiF-~hHrT8Zs$)WG9JRy ze_?L6(bNFIzux82^V9h3;yYuD@0@Xj`y}5!=PfF#mUCy&sR8ATY9N?(x94B2?PQ?e z>YpF$ud3b=qIg-n{87EWt7AInBP5Y>MHrk&7$Oge-*yQoGl<3_{qOIj;DP3&po38x zX%%s&$gglz(a{bh=v+mLC1)?Y#*VRc-@5Fndm9-HknRpE?eC*Y6tXZh%3L9rVhQZ# z=R}A`-<^L}kW?=$(1-g~!k2ufO-omG46*FCGLM;OmmRe zbQzB=xqGVNU2s=|3>kipKC~ntBx#d2dg!Tt8b=2b_PZx9)C-e))1XoWZK1R`Z+K@s zyx&!~kv~>z0%I?E0hC5#9B;3M@&FL|V@gc7HXA5F`IjG=-*c+Rf+L|Mp#h!B`{^+j zh>I(sAe9i5mdIQhTFf}Gh5^2I+=Gs#NHTn#n&dC;w(oj8H8U_N8BN;@RBL+DLDqAq zwzyGjzDw>a^t1|E$VbcT^~F(Pt`r1c?D0n8&JBOq8l%Se2mGmD@SKMYj!i0rL)39D z@Qy3#^y@AELPfeTH+J&4;eWgaX?Jsr`tzxjm$_|oysd%!kC1ET%_j7LFI&6SC`g03Cn?QxLQQ z00QrW&g-I0G@lXD)Q9W=hnnWaPPnFwv9d1d~({tF3 z!{O>hS3B2FqWJ_3qHPm2pp0uHu1}a%lNBZRO87d2n}r)k_nS`BhaXHI6yO9lDo$&U zu1x8)W;}GScXMryjiIGIYxX@~j6yygX50KJEh)9qm_mvo8@@yvBZBL;Xs^?bmUsh* z6yOE*NKbO)d65!Ij*^(pN?vrws&H4Avzc3eAIRnp7>YZz+bMbZu-TE zj#}5B(V<_-*GvR$JTY8a2ycU`uGzE};+`LlTHJ;I(P-QU%LJ($RCYdifd)i&461uZ zC4LT+`K?c`L23v9av1I+emI4$l=mxT7AA2~hCkB2GN}o5Xb`)qGl z`_y+L!hyV9wT=@7tk@jpABp7O;{u0qZG=$YOB0Wc{cYnQvN*X%pONl7pPdjqCnj!< zl&N&8n5e}E1wWgTqrTL%_mnN6<7tZ-@8Piv`D4R~KlG5F{`^R&gL&swU}bE*WTKD< zilCno0T(8s5||+SC(DO-F9~?G@D|0IFz%Rf6pNJX&MA5;dnW|+^c!N!fv8bEvP6{J=@C~hb2?WS zR{W89NSLB}l8(91v$eqmL~|avvO10mO(N5S^fL|i&RI`TFEA48vrWVMcqmXPV1G}4 zU4t0Pz$P71{5p$ux@VQ>FGAk4AjO?);21%TjbW=(s`JxeMDWl1WZT%7kmRwQBM!^j zKfb6qICCqlS3Jt)YO&$c@A;+$?@^nBBZu-^HTn3WWp2B+g%LxAU9q9t)m?L0P9N17 zIK^x7j>pudtFz|_1u5b&wC+UAI44k~;O~tbYKf)aKw4QK1zZ14VTU`zpLF?NlzX*t zFy{n`__?7;@!6_7l*yAH*f7SB@P%yn>*i;EpUw! z4A5_(+vJustEmtYwbQf-(BJE&G4m-eV{+F)+g0XisXciYB^z9o!1iuKyl#A+J8!*& zNts5vn#NpN-r>49x~I2Ptnsp&b>y}uvZ~Cy^dOM_y>bCobKx-u!yqBGKi~Dqg^od( zids51gWzZIZ-UaM7Rb8?TKI35nXKy^PPnMM#@PwY@vww^!ZV7<+D)hb z{L>69kLC?uPvu*Is4bl3SY>+AD5<)co16WzIk~VGkT4cATcZ-d{)>eSS9HJs?vG{6 zB#^&5k+$S^{gtK0-ApemCB&&p`=Ft#sq^t_RfcmVCV(_}txC)o0aN{87l#sex?0l( z+HP(6{)z(`d(k;Ff-bv)%k`c&|H)gkCF@$JI%YAMFwMxFh**%VFrfKYtIO{Zp_!zp zbwbUADnT6i+9=9;8!$DW*tt~oR56x}Kf0pS537>MXNdQ-hB%&R{Vq-{Xs)nIHQCRv zf=A`|r}*e_ALWw|7xJ}f2ga((L0QLtSV+pFiFt5K_#haO55EaJnkN?XI4>H=s+Ydk z3t0ZvIH3nqMUQlew3Kn`!y0h*ALkwL^)1CT1v=h*ed*>~8XO;V2CAq`#P(f9etpBY zIO+^a_-Sl~wW_%^?}8~~`jx`7ES4&KAJ^3QCw3y9`_cTdhKfvVv(cx2SsYv$>%dRfF7hP%!!DS%4s=qou2ilTz21eA#l}FBl zT~j(ay+24kd^lS+f}|BKFS}aZIOpt5r!yZJm`U7G_RdeW+_V>|>j|06KzGiBcJ~#% zLYpKdy}(G-YzH>-l}i4qy%ZDVznaG~*LkS}Z56xkHo00Q#ABi^Ky4o_pkE-A!W43r z=f%1K;i1N|Wyi7@#?mgQ9X&SCugW*5+!@(-Q&eog*Dndgxw%ZDA~~eu*#Bm>!+^uX z?Bq8v_H|l$MY7@m~7!pAJHLKk`kQyVm6k%Evj? ztVt#-dmY>R80>AP8_c+EBc}*!Cnr8`e(?B=~S^3vN3HC3aiq%mD# zCuu0uCus#4;H_-eGLC;oTyLwsyRZ3duRDw%zO-5%9&g42yW;DcSu3hC6Fp(81$=xT zlxA&I6)jHPD)qbVUThUo!Wxcrk%hlO3>7=9GzArJx+dBfi$0orX<_n}qUSKvCzM)g zdM6Zu+{64F!>%&{{*b0q_u%P;8Gc?eBekabnxfD9Yy9Q}*eWxMbO^4@Leq2nTqEX1 z2;jIS`?{K?{y*%rLPjp|prO6|^otdmZ|oNrk>`#Xq}+@X6aGF2i=)=Wg4!>eeEemk zjJX(Qy04&yj_`%DVtCR{h4ko6ixJngEm&%`@B$U_grbI-Ly8js+P{bn<4<{i5ET$* zm_8huwRXvpm>p$Z4`9!dyCXO{e9q4mpV63}2iLj9Afrg3UuAE~3~ksS% zyvhF+{Labeiay zI@KHhmv+*<87o@A77L?Eb(Z(X!ZsBn+hY>q4cz0O-!_b(S(%SvHjQB&l_RmKgolFE zUD>(Yrsi8kNHf5YRqm(p9>y0}XyDvJh+&O;Isbdnrg! z`BTQ`5oqUBHfoAB|MvSh(*QvShF)~L-pzV{7M@grZgjr?Jgq5C%<=w@b=murg4u;B z<`I9l;<8#V_p5yg5=?<{u1cM1#<&n_#m8<@%t5Fn11(^Gi;SUgJg=C8n0WLlygEO! zJteCC-m3K|(d4FCaQi~@+`0F69nxp=c#|4w&+7NLe&6$j1chyjE2*?}Pv(}= zyaX3z3K!(aFUj%D3VT&?ekkCye=OQx;nhES&++qq>5;pp=nVX*)Fl4guw3%+??X<* z%lTU3z*SF5V%Gj>nH=fmg8Tk5DY@@l*Z3(d$}aOE5`W{Rg>g~m zxdyW5GV(0}WE+2Z+$TqE1SRBxgM;OCi4X&$0wieOO-qNQELnMc(CGDYADG6fW}{j! z6jY0yi+{j1wvz21zR~44-F~SoLaw7{dRk9v{Q> zE_Rto=Prlk9?ZawoJy`qrYjcXg3R#$@z{)6)ecfr9?W|^Tkj<$d52MuGqSr*8($7~ z5zla6Sb0bL`*c{yq*=DIcLXwLPK4kuW-aSW%#hEK-~J<||J5H*jqzb;;p(C<%sbV`BzZCH^HZ#>sn#k;TXWio?SM3=vw6fCx#yKL z7}r@>tlLRsx4wI5u?TW0Ex9FQw|JcZ@r*^~rC(DWO(nPFc{F}XXso3|@T%$^=)%G{ zHZqbBVl2b&wf)xFdf;@0{?(@kzqFwz$0XUp{xzgtLqe#!ch&W|T)Ya_e;mYzZCN!! zey71JoAY&p|6Ib`Q%X)rN{WU!cxZJ(TSepCs(A%__=dwj zQ3mS%wZ!kFc;)B)eQqe$?$o6+FBX7a+ z>P6r#3Mmc_*NKLLf`XhJSTrF$Eluw0|C0t|OBO#M+Tka#tc)tos@v~z?Q_2ddB zaY6LK-F6~^`N8d_`T5g(8E@xF2Z?a6^-C8dU#6?%Eoot{*C0J_c9v=w&*OBZPrh(M O0cml0v5GGSf&T}Ea6AYA literal 0 HcmV?d00001 diff --git a/ExerciseTracker.TwilightSaw/images/crud.png b/ExerciseTracker.TwilightSaw/images/crud.png new file mode 100644 index 0000000000000000000000000000000000000000..f1b4a584da788b36f2102454fc9cac77ce33a5a9 GIT binary patch literal 6576 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 C09}s& literal 0 HcmV?d00001 diff --git a/ExerciseTracker.TwilightSaw/images/crud2.png b/ExerciseTracker.TwilightSaw/images/crud2.png new file mode 100644 index 0000000000000000000000000000000000000000..d84d9d94e0a7a745ca0f09728c3ee695b2f40dc7 GIT binary patch literal 8010 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 TX!3#g#UNcx1C4TZtC0Ty;zeae literal 0 HcmV?d00001 diff --git a/ExerciseTracker.TwilightSaw/images/migrations.png b/ExerciseTracker.TwilightSaw/images/migrations.png new file mode 100644 index 0000000000000000000000000000000000000000..9fe68aad721739196691f8c06fcf95d10cd79015 GIT binary patch literal 7309 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 NyEm$@%Vf=h{s;KlQ7ixe literal 0 HcmV?d00001 diff --git a/ExerciseTracker.TwilightSaw/images/repository.png b/ExerciseTracker.TwilightSaw/images/repository.png new file mode 100644 index 0000000000000000000000000000000000000000..20d2b479366501487fdb9e4bae1b335abf6816dc GIT binary patch literal 17826 zcmbW91z1#lyY+`|7#is=K|)%(L%O7-rCYkYOC+SbJEc36?nX*FrTg3Xymg*8zW1E# z(mf0cdv?ryuY0ZEe?k@HBv26X5kVjjij<_N67YBl0zpi{!vbF+Kuik(4{K&3A_@*l z62c%5Uc8~69`7&+`YkFla-%Din9z|NB6_SMBC;eYOXF7`2u?jCy=x~FQC4Q{859Qn zpZz&F1gi{?n((;-pejk8q~QTb0~(<@3{odoDItWIGAIKYTrA%?LEP6TSb@;oQ1kqP z^};+DZxBM$K53>R8*PK;PvP5tj+*FIUUzkL@S>9?utoanyT5_*?;#3E`YsF$`=|yR zWR-|+knzy@XpHqn<&&l&B*y8avk|Q%gEX|vYkx>K#z^0_Nqbn>6-ijwH6&zY?QyWP z4}=8^bEXtB4RBcYa~uIVBoZ~x$80O9;Q#`mc0d1x=(i~}0)a?DQldgCE~$sDx|Rg9 zU!ERRYn{xSk)Awa=gb0`ky*q1=cNukk+2}9lGdbK;ERzai4S1M`t&iRP%6>0+n69t zCu;KvqahFK4OBCALQTIg1duaQ;I|J5Dt*f9FGO~>!I3kmQDv_)YZ7j@#<3+VO`&BU zzA^~fwB@>|u%ji4N=MW4NfsC-RC?S?eKB(LHnD$$E>mnIoiBV=*PBR2My4#mJX!bg zS7QBO`|Wha%)E@KO5EDZhg1=UIW!G+D{@Im8sQTxZ#7EWZb20S7_5%%GrVwaW7=Cr^4}lBGdCy|5Q`3#rS@U!=;U8&)T2}8;t0EB4o;02 zWlo_A`u3pYT-S$;!PJM7V;bO~a4Ft>$xS;d*D0*3pTi99=G_uXfs@wLlg;7g5EU9stSoHF<4_ ztj09rI53#O(O70#(~3nLA{kcJ3o?BIHGctG-=AW8t~WjTMxi8pU=ssAM6iXTiRaBJ z1WH?IU+t3N^C`Et|MZ{$1yUKPLG-BpNjUqJ9#vHYhaWm@KL8=(7C$wP({b*H#-4QK zKEBn9GJ*Fr@LHNjRqE>MM2)wtW<9~3j53lmY^K#!47ehdB%pOXRQr6#CY~a@@(oFP z*3E#Rm@KjCa$WD`b$$d>GQ(IaE5E{^-J7|(Bi<4T2hKupK(jv29JG)nax%0q_*H

7DVmPs@_Rt9PhW%BS&6lda*Pvk#Y9yLnxR1Gl{zzU*147D#DfYNa-ytQl%K zY4+7ST}-(yl}hKsg+N=br3Bk!cUzMCiaVC`Gcw+X@B)^TG~bD)qvmFo&-le|IwXdV z>;+E**4vgWs(pgTuyrzi0ksJ}&h<-b)`WPLyJ+YzpB$4AIY6T1=>6TTSC6eOzu@Gv zW0*)U>=BQ7uTWpLJHPNj-`oT@IXi9i++gO-iWHG$mI&!@73KKtg_xD}i0i+a!+oV~ z%exeuE)bA1Yc1@m+IewRv5`|kvL?NU2qJAy==abgz8U@HayF2XH+u$k*3op7JsaG- zYrsh@{xY~l)*^~Nc=#5q+oNqpYu!NHXS=M_jzUk{%2=>pk_i$_nxn-2yu7WiGjr;l zHH+fV346Y6mBH-*yJm~$etBTlxZ*1U7o%)XyJTnI7XHmTTZ~LNaEXbG0N)=TUGYVX zw6tN#7CbwfL=(<@2Eq*SdblCHG1Z`3>to|a>-5EJo?kV6O0lW}`8wXNXI>j3BKa1M zrmE2NZNw0Kk|d*w_j}ch@ynF$9-~Uv^#1W23G`4=+x?lcgnjH^FiSUsM2n6DBw;C| zRpC!->v#zujW~#J=NSqv1KHfa<}IQkh@I+JzMb3SaA#-}zW^CWPIO5-J~$R-mVVEV zEY9R|v1=2`UAcAJ9p?Ljhp!L2R;BMPFX*UlE2~TyzxPra-Ltk-k*+IL5fX@z zGwL_$1})TXNeakcng2Y*+Q+tHL6p734i{Wk0nrEf6DlDgCET7eDhovtx|?FEDA4D_ zzVGy(8AVDMEp)znq4R~c@ciO}o7miME{Yl3(vLLNx6uK3&

  • v?`;P7L?cJ5!`k6MRdwS8^%MMG2bA0wkBk( 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 B(8K@$ literal 0 HcmV?d00001 diff --git a/ExerciseTracker.TwilightSaw/images/ui.png b/ExerciseTracker.TwilightSaw/images/ui.png new file mode 100644 index 0000000000000000000000000000000000000000..07e48d96f9cc80fa236d9ed7c8b153a4e1823ed1 GIT binary patch literal 6888 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 BjiLYm literal 0 HcmV?d00001 diff --git a/ExerciseTracker.TwilightSaw/images/ui2.png b/ExerciseTracker.TwilightSaw/images/ui2.png new file mode 100644 index 0000000000000000000000000000000000000000..22f4303fd9ca9f51299c6e3d99f70b2beba7146f GIT binary patch literal 8318 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 literal 0 HcmV?d00001