diff --git a/.gitignore b/.gitignore
index 49976e11..449d9c28 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,8 @@
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
+# Databse files
+*.db
# User-specific files
*.rsuser
diff --git a/CodingTracker.Kunikazu723.sln b/CodingTracker.Kunikazu723.sln
new file mode 100644
index 00000000..447bd548
--- /dev/null
+++ b/CodingTracker.Kunikazu723.sln
@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.14.37531.7
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodingTracker.Kunikazu723", "CodingTracker.Kunikazu723\CodingTracker.Kunikazu723.csproj", "{97EA1958-EE4A-4B55-AAB7-9E57F0A17958}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodingTracker.UnitTests", "CodingTracker.UnitTests\CodingTracker.UnitTests.csproj", "{8DE33EA7-572A-4D3D-A47F-097B04F53AA5}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {97EA1958-EE4A-4B55-AAB7-9E57F0A17958}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {97EA1958-EE4A-4B55-AAB7-9E57F0A17958}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {97EA1958-EE4A-4B55-AAB7-9E57F0A17958}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {97EA1958-EE4A-4B55-AAB7-9E57F0A17958}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8DE33EA7-572A-4D3D-A47F-097B04F53AA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8DE33EA7-572A-4D3D-A47F-097B04F53AA5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8DE33EA7-572A-4D3D-A47F-097B04F53AA5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8DE33EA7-572A-4D3D-A47F-097B04F53AA5}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {178835FB-F730-48E9-ACB5-35AB7EE86672}
+ EndGlobalSection
+EndGlobal
diff --git a/CodingTracker.Kunikazu723/CodingTracker.Kunikazu723.csproj b/CodingTracker.Kunikazu723/CodingTracker.Kunikazu723.csproj
new file mode 100644
index 00000000..be84a9e4
--- /dev/null
+++ b/CodingTracker.Kunikazu723/CodingTracker.Kunikazu723.csproj
@@ -0,0 +1,28 @@
+
+
+
+ Exe
+ net9.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+
+
diff --git a/CodingTracker.Kunikazu723/Dao/CodingSessionDao.cs b/CodingTracker.Kunikazu723/Dao/CodingSessionDao.cs
new file mode 100644
index 00000000..8240c4c6
--- /dev/null
+++ b/CodingTracker.Kunikazu723/Dao/CodingSessionDao.cs
@@ -0,0 +1,77 @@
+using CodingTracker.Kunikazu723.Dao.Interfaces;
+using CodingTracker.Kunikazu723.Models;
+using Dapper;
+using Microsoft.Data.Sqlite;
+
+namespace CodingTracker.Kunikazu723.Dao
+{
+ public class CodingSessionDao : IDao
+ {
+ private readonly string _connectionString;
+
+ public CodingSessionDao(string connectionString)
+ {
+ _connectionString = connectionString;
+ }
+
+ public void DeleteItemById(CodingSession codingSession)
+ {
+ using var connection = new SqliteConnection(_connectionString);
+ var sql = """
+ DELETE FROM CodingSessions WHERE Id = @Id
+ """;
+ connection.Execute(sql, codingSession);
+ }
+
+ public List GetAllItems()
+ {
+ using var connection = new SqliteConnection(_connectionString);
+ string sql = """
+ SELECT * FROM CodingSessions
+ """;
+ return connection.Query(sql).ToList();
+ }
+
+ public CodingSession? GetItemById(int id)
+ {
+ using var connection = new SqliteConnection(_connectionString);
+ var sql = """
+ SELECT * FROM CodingSessions WHERE Id = @itemId
+ """;
+ return connection.QuerySingleOrDefault(sql, new { itemId = id });
+ }
+
+ public void InsertItem(CodingSession item)
+ {
+ using var connection = new SqliteConnection(_connectionString);
+ var sql = """
+ INSERT INTO CodingSessions (StartTime, EndTime) VALUES (@StartTime, @EndTime)
+ """;
+ connection.Execute(sql, item);
+ }
+
+ public void InsertMany(List collection)
+ {
+ using var connection = new SqliteConnection(_connectionString);
+ connection.Open();
+
+ using var transaction = connection.BeginTransaction();
+ connection.Execute("""
+ INSERT INTO CodingSessions (StartTime, EndTime)
+ VALUES (@StartTime, @EndTime)
+ """, collection, transaction);
+ transaction.Commit();
+ }
+
+ public void UpdateItemById(CodingSession newItem)
+ {
+ using var connection = new SqliteConnection(_connectionString);
+ var sql = """
+ UPDATE CodingSessions
+ SET StartTime = @StartTime, EndTime = @EndTime
+ WHERE Id = @Id
+ """;
+ connection.Execute(sql, newItem);
+ }
+ }
+}
diff --git a/CodingTracker.Kunikazu723/Dao/Interfaces/IDao.cs b/CodingTracker.Kunikazu723/Dao/Interfaces/IDao.cs
new file mode 100644
index 00000000..5bd3fd8e
--- /dev/null
+++ b/CodingTracker.Kunikazu723/Dao/Interfaces/IDao.cs
@@ -0,0 +1,12 @@
+namespace CodingTracker.Kunikazu723.Dao.Interfaces
+{
+ public interface IDao where T : class
+ {
+ List GetAllItems();
+ T? GetItemById(int id);
+ void InsertItem(T item);
+ void UpdateItemById(T newItem);
+ void DeleteItemById(T item);
+ void InsertMany(List items);
+ }
+}
diff --git a/CodingTracker.Kunikazu723/DatabaseInitializer.cs b/CodingTracker.Kunikazu723/DatabaseInitializer.cs
new file mode 100644
index 00000000..9edab0b1
--- /dev/null
+++ b/CodingTracker.Kunikazu723/DatabaseInitializer.cs
@@ -0,0 +1,26 @@
+using Dapper;
+using Microsoft.Data.Sqlite;
+
+namespace CodingTracker.Kunikazu723
+{
+ public class DatabaseInitializer
+ {
+ private readonly string _connectionString;
+ public DatabaseInitializer(string connectionString)
+ {
+ _connectionString = connectionString;
+ }
+
+ public void Initialize()
+ {
+ using var connection = new SqliteConnection(_connectionString);
+ connection.Execute("""
+ CREATE TABLE IF NOT EXISTS CodingSessions (
+ Id INTEGER PRIMARY KEY AUTOINCREMENT,
+ StartTime TEXT UNIQUE NOT NULL,
+ EndTime TEXT UNIQUE NOT NULL
+ )
+ """);
+ }
+ }
+}
diff --git a/CodingTracker.Kunikazu723/Enums/DateEnums.cs b/CodingTracker.Kunikazu723/Enums/DateEnums.cs
new file mode 100644
index 00000000..f4a80cda
--- /dev/null
+++ b/CodingTracker.Kunikazu723/Enums/DateEnums.cs
@@ -0,0 +1,13 @@
+namespace CodingTracker.Kunikazu723.Enums
+{
+ public class DateEnums
+ {
+ public enum TimeType
+ {
+ Day,
+ Hour,
+ Minute,
+ Second
+ }
+ }
+}
diff --git a/CodingTracker.Kunikazu723/Enums/Filters.cs b/CodingTracker.Kunikazu723/Enums/Filters.cs
new file mode 100644
index 00000000..3006815e
--- /dev/null
+++ b/CodingTracker.Kunikazu723/Enums/Filters.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace CodingTracker.Kunikazu723.Enums
+{
+ public static class Filters
+ {
+ public enum FilterType
+ {
+ Days,
+ Weeks,
+ Months,
+ Years,
+ }
+
+ public enum FilterOrder
+ {
+ Ascending,
+ Descending,
+ }
+ }
+}
diff --git a/CodingTracker.Kunikazu723/Enums/MenuEnums.cs b/CodingTracker.Kunikazu723/Enums/MenuEnums.cs
new file mode 100644
index 00000000..663555ab
--- /dev/null
+++ b/CodingTracker.Kunikazu723/Enums/MenuEnums.cs
@@ -0,0 +1,19 @@
+using System.Text.RegularExpressions;
+
+namespace CodingTracker.Kunikazu723.Enums
+{
+ internal static class MenuEnums
+ {
+ public enum MainMenuOptions
+ {
+ ViewSessions,
+ LogSessions,
+ UpdateSession,
+ DeleteSession,
+ Exit
+ }
+
+ public static string OptionToString(T option) where T: struct, Enum => Regex.Replace(option.ToString(), "([A-Z])", " $1").Trim();
+
+ }
+}
diff --git a/CodingTracker.Kunikazu723/FigletFonts/isometric3.flf b/CodingTracker.Kunikazu723/FigletFonts/isometric3.flf
new file mode 100644
index 00000000..1ba5e5ca
--- /dev/null
+++ b/CodingTracker.Kunikazu723/FigletFonts/isometric3.flf
@@ -0,0 +1,1146 @@
+flf2a$ 11 11 18 -1 23
+isometric3.flf
+
+Figlet conversion by Kent Nassen (kentn@cyberspace.org), 8-10-94, based
+on the post by Lennert Stock:
+
+From: stock@fwi.uva.nl (Lennert Stock)
+Date: 15 Jul 1994 00:04:25 GMT
+
+Here are some fonts. Non-figlet I'm afraid, if you wanna convert them, be
+my guest. I posted the isometric fonts before.
+
+------------------------------------------------------------------------------
+
+ .x%%%%%%x. .x%%%%%%x.
+ ,%%%%%%%%%%. .%%%%%%%%%%.
+ ,%%%' )' \) :( `( `%%%.
+ ,%x%)________) --------- L e n n e r t S t o c k ( _ __ (%x%.
+ (%%%~^88P~88P| |~=> .=-~ %%%)
+ (%%::. .:,\ .' `. /,:. .::%%)
+ `;%:`\. `-' | | `-' ./':%:'
+ ``x`. -===.' stock@fwi.uva.nl -------- `.===- .'x''
+ / `:`.__.; :.__.':' \
+ .d8b. ..`. .'.. .d8b.
+$ $@
+$ $@
+$ $@
+$ $@
+$ $@
+$ $@
+$ $@
+$ $@
+$ $@
+$ $@
+$ $@@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@@
+ ___ @
+ / /\ @
+ / /::\ @
+ / /:/\:\ @
+ / /:/~/::\ @
+ /__/:/ /:/\:\@
+ \ \:\/:/__\/@
+ \ \::/ @
+ \ \:\ @
+ \ \:\ @
+ \__\/ @@
+ @
+ _____ @
+ / /::\ @
+ / /:/\:\ @
+ / /:/~/::\ @
+ /__/:/ /:/\:|@
+ \ \:\/:/~/:/@
+ \ \::/ /:/ @
+ \ \:\/:/ @
+ \ \::/ @
+ \__\/ @@
+ ___ @
+ / /\ @
+ / /:/ @
+ / /:/ @
+ / /:/ ___ @
+ /__/:/ / /\@
+ \ \:\ / /:/@
+ \ \:\ /:/ @
+ \ \:\/:/ @
+ \ \::/ @
+ \__\/ @@
+ _____ @
+ / /::\ @
+ / /:/\:\ @
+ / /:/ \:\ @
+ /__/:/ \__\:|@
+ \ \:\ / /:/@
+ \ \:\ /:/ @
+ \ \:\/:/ @
+ \ \::/ @
+ \__\/ @
+ @@
+ ___ @
+ / /\ @
+ / /:/_ @
+ / /:/ /\ @
+ / /:/ /:/_ @
+ /__/:/ /:/ /\@
+ \ \:\/:/ /:/@
+ \ \::/ /:/ @
+ \ \:\/:/ @
+ \ \::/ @
+ \__\/ @@
+ ___ @
+ / /\ @
+ / /:/_ @
+ / /:/ /\@
+ / /:/ /:/@
+ /__/:/ /:/ @
+ \ \:\/:/ @
+ \ \::/ @
+ \ \:\ @
+ \ \:\ @
+ \__\/ @@
+ ___ @
+ / /\ @
+ / /:/_ @
+ / /:/ /\ @
+ / /:/_/::\ @
+ /__/:/__\/\:\@
+ \ \:\ /~~/:/@
+ \ \:\ /:/ @
+ \ \:\/:/ @
+ \ \::/ @
+ \__\/ @@
+ ___ @
+ /__/\ @
+ \ \:\ @
+ \__\:\ @
+ ___ / /::\ @
+ /__/\ /:/\:\@
+ \ \:\/:/__\/@
+ \ \::/ @
+ \ \:\ @
+ \ \:\ @
+ \__\/ @@
+ @
+ ___ @
+ / /\ @
+ / /:/ @
+ /__/::\ @
+ \__\/\:\__ @
+ \ \:\/\@
+ \__\::/@
+ /__/:/ @
+ \__\/ @
+ @@
+ ___ @
+ / /\ @
+ / /:/ @
+ /__/::\ @
+ \__\/\:\ @
+ \ \:\ @
+ \__\:\@
+ / /:/@
+ /__/:/ @
+ \__\/ @
+ @@
+ ___ @
+ /__/| @
+ | |:| @
+ | |:| @
+ __| |:| @
+ /__/\_|:|____@
+ \ \:\/:::::/@
+ \ \::/~~~~ @
+ \ \:\ @
+ \ \:\ @
+ \__\/ @@
+ @
+ @
+ @
+ ___ ___ @
+ /__/\ / /\@
+ \ \:\ / /:/@
+ \ \:\ /:/ @
+ \ \:\/:/ @
+ \ \::/ @
+ \__\/ @
+ @@
+ ___ @
+ /__/\ @
+ | |::\ @
+ | |:|:\ @
+ __|__|:|\:\ @
+ /__/::::| \:\@
+ \ \:\~~\__\/@
+ \ \:\ @
+ \ \:\ @
+ \ \:\ @
+ \__\/ @@
+ ___ @
+ /__/\ @
+ \ \:\ @
+ \ \:\ @
+ _____\__\:\ @
+ /__/::::::::\@
+ \ \:\~~\~~\/@
+ \ \:\ ~~~ @
+ \ \:\ @
+ \ \:\ @
+ \__\/ @@
+ ___ @
+ / /\ @
+ / /::\ @
+ / /:/\:\ @
+ / /:/ \:\ @
+ /__/:/ \__\:\@
+ \ \:\ / /:/@
+ \ \:\ /:/ @
+ \ \:\/:/ @
+ \ \::/ @
+ \__\/ @@
+ ___ @
+ / /\ @
+ / /::\ @
+ / /:/\:\@
+ / /:/~/:/@
+ /__/:/ /:/ @
+ \ \:\/:/ @
+ \ \::/ @
+ \ \:\ @
+ \ \:\ @
+ \__\/ @@
+ @
+ ___ @
+ / /\ @
+ / /::\ @
+ / /:/\:\ @
+ / /:/~/::\ @
+ /__/:/ /:/\:\@
+ \ \:\/:/__\/@
+ \ \::/ @
+ \__\/ @
+ @@
+ ___ @
+ / /\ @
+ / /::\ @
+ / /:/\:\ @
+ / /:/~/:/ @
+ /__/:/ /:/___@
+ \ \:\/:::::/@
+ \ \::/~~~~ @
+ \ \:\ @
+ \ \:\ @
+ \__\/ @@
+ ___ @
+ / /\ @
+ / /:/_ @
+ / /:/ /\ @
+ / /:/ /::\ @
+ /__/:/ /:/\:\@
+ \ \:\/:/~/:/@
+ \ \::/ /:/ @
+ \__\/ /:/ @
+ /__/:/ @
+ \__\/ @@
+ @
+ ___ @
+ / /\ @
+ / /:/ @
+ / /:/ @
+ / /::\ @
+ /__/:/\:\ @
+ \__\/ \:\ @
+ \ \:\@
+ \__\/@
+ @@
+ ___ @
+ /__/\ @
+ \ \:\ @
+ \ \:\ @
+ ___ \ \:\ @
+ /__/\ \__\:\@
+ \ \:\ / /:/@
+ \ \:\ /:/ @
+ \ \:\/:/ @
+ \ \::/ @
+ \__\/ @@
+ @
+ ___ @
+ /__/\ @
+ \ \:\ @
+ \ \:\ @
+ ___ \__\:\@
+ /__/\ | |:|@
+ \ \:\| |:|@
+ \ \:\__|:|@
+ \__\::::/ @
+ ~~~~ @@
+ ___ @
+ /__/\ @
+ _\_ \:\ @
+ /__/\ \:\ @
+ _\_ \:\ \:\ @
+ /__/\ \:\ \:\@
+ \ \:\ \:\/:/@
+ \ \:\ \::/ @
+ \ \:\/:/ @
+ \ \::/ @
+ \__\/ @@
+ ___ @
+ /__/| @
+ | |:| @
+ | |:| @
+ __|__|:| @
+ /__/::::\____@
+ ~\~~\::::/@
+ |~~|:|~~ @
+ | |:| @
+ | |:| @
+ |__|/ @@
+ @
+ ___ @
+ /__/| @
+ | |:| @
+ | |:| @
+ __|__|:| @
+ /__/::::\ @
+ ~\~~\:\ @
+ \ \:\@
+ \__\/@
+ @@
+ ___ @
+ / /\ @
+ / /::| @
+ / /:/:| @
+ / /:/|:|__ @
+ /__/:/ |:| /\@
+ \__\/ |:|/:/@
+ | |:/:/ @
+ | |::/ @
+ | |:/ @
+ |__|/ @@
+ ___ @
+ / /\ @
+ / /::\ @
+ / /::::\ @
+ / /::::::\ @
+ /__/:::LS:::\@
+ \ \::1994::/@
+ \ \::::::/ @
+ \ \::::/ @
+ \ \::/ @
+ \__\/ @@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@@
+ ___ @
+ / /\ @
+ / /::\ @
+ / /:/\:\ @
+ / /:/~/::\ @
+ /__/:/ /:/\:\@
+ \ \:\/:/__\/@
+ \ \::/ @
+ \ \:\ @
+ \ \:\ @
+ \__\/ @@
+ @
+ _____ @
+ / /::\ @
+ / /:/\:\ @
+ / /:/~/::\ @
+ /__/:/ /:/\:|@
+ \ \:\/:/~/:/@
+ \ \::/ /:/ @
+ \ \:\/:/ @
+ \ \::/ @
+ \__\/ @@
+ ___ @
+ / /\ @
+ / /:/ @
+ / /:/ @
+ / /:/ ___ @
+ /__/:/ / /\@
+ \ \:\ / /:/@
+ \ \:\ /:/ @
+ \ \:\/:/ @
+ \ \::/ @
+ \__\/ @@
+ _____ @
+ / /::\ @
+ / /:/\:\ @
+ / /:/ \:\ @
+ /__/:/ \__\:|@
+ \ \:\ / /:/@
+ \ \:\ /:/ @
+ \ \:\/:/ @
+ \ \::/ @
+ \__\/ @
+ @@
+ ___ @
+ / /\ @
+ / /:/_ @
+ / /:/ /\ @
+ / /:/ /:/_ @
+ /__/:/ /:/ /\@
+ \ \:\/:/ /:/@
+ \ \::/ /:/ @
+ \ \:\/:/ @
+ \ \::/ @
+ \__\/ @@
+ ___ @
+ / /\ @
+ / /:/_ @
+ / /:/ /\@
+ / /:/ /:/@
+ /__/:/ /:/ @
+ \ \:\/:/ @
+ \ \::/ @
+ \ \:\ @
+ \ \:\ @
+ \__\/ @@
+ ___ @
+ / /\ @
+ / /:/_ @
+ / /:/ /\ @
+ / /:/_/::\ @
+ /__/:/__\/\:\@
+ \ \:\ /~~/:/@
+ \ \:\ /:/ @
+ \ \:\/:/ @
+ \ \::/ @
+ \__\/ @@
+ ___ @
+ /__/\ @
+ \ \:\ @
+ \__\:\ @
+ ___ / /::\ @
+ /__/\ /:/\:\@
+ \ \:\/:/__\/@
+ \ \::/ @
+ \ \:\ @
+ \ \:\ @
+ \__\/ @@
+ @
+ ___ @
+ / /\ @
+ / /:/ @
+ /__/::\ @
+ \__\/\:\__ @
+ \ \:\/\@
+ \__\::/@
+ /__/:/ @
+ \__\/ @
+ @@
+ ___ @
+ / /\ @
+ / /:/ @
+ /__/::\ @
+ \__\/\:\ @
+ \ \:\ @
+ \__\:\@
+ / /:/@
+ /__/:/ @
+ \__\/ @
+ @@
+ ___ @
+ /__/| @
+ | |:| @
+ | |:| @
+ __| |:| @
+ /__/\_|:|____@
+ \ \:\/:::::/@
+ \ \::/~~~~ @
+ \ \:\ @
+ \ \:\ @
+ \__\/ @@
+ @
+ @
+ @
+ ___ ___ @
+ /__/\ / /\@
+ \ \:\ / /:/@
+ \ \:\ /:/ @
+ \ \:\/:/ @
+ \ \::/ @
+ \__\/ @
+ @@
+ ___ @
+ /__/\ @
+ | |::\ @
+ | |:|:\ @
+ __|__|:|\:\ @
+ /__/::::| \:\@
+ \ \:\~~\__\/@
+ \ \:\ @
+ \ \:\ @
+ \ \:\ @
+ \__\/ @@
+ ___ @
+ /__/\ @
+ \ \:\ @
+ \ \:\ @
+ _____\__\:\ @
+ /__/::::::::\@
+ \ \:\~~\~~\/@
+ \ \:\ ~~~ @
+ \ \:\ @
+ \ \:\ @
+ \__\/ @@
+ ___ @
+ / /\ @
+ / /::\ @
+ / /:/\:\ @
+ / /:/ \:\ @
+ /__/:/ \__\:\@
+ \ \:\ / /:/@
+ \ \:\ /:/ @
+ \ \:\/:/ @
+ \ \::/ @
+ \__\/ @@
+ ___ @
+ / /\ @
+ / /::\ @
+ / /:/\:\@
+ / /:/~/:/@
+ /__/:/ /:/ @
+ \ \:\/:/ @
+ \ \::/ @
+ \ \:\ @
+ \ \:\ @
+ \__\/ @@
+ @
+ ___ @
+ / /\ @
+ / /::\ @
+ / /:/\:\ @
+ / /:/~/::\ @
+ /__/:/ /:/\:\@
+ \ \:\/:/__\/@
+ \ \::/ @
+ \__\/ @
+ @@
+ ___ @
+ / /\ @
+ / /::\ @
+ / /:/\:\ @
+ / /:/~/:/ @
+ /__/:/ /:/___@
+ \ \:\/:::::/@
+ \ \::/~~~~ @
+ \ \:\ @
+ \ \:\ @
+ \__\/ @@
+ ___ @
+ / /\ @
+ / /:/_ @
+ / /:/ /\ @
+ / /:/ /::\ @
+ /__/:/ /:/\:\@
+ \ \:\/:/~/:/@
+ \ \::/ /:/ @
+ \__\/ /:/ @
+ /__/:/ @
+ \__\/ @@
+ @
+ ___ @
+ / /\ @
+ / /:/ @
+ / /:/ @
+ / /::\ @
+ /__/:/\:\ @
+ \__\/ \:\ @
+ \ \:\@
+ \__\/@
+ @@
+ ___ @
+ /__/\ @
+ \ \:\ @
+ \ \:\ @
+ ___ \ \:\ @
+ /__/\ \__\:\@
+ \ \:\ / /:/@
+ \ \:\ /:/ @
+ \ \:\/:/ @
+ \ \::/ @
+ \__\/ @@
+ @
+ ___ @
+ /__/\ @
+ \ \:\ @
+ \ \:\ @
+ ___ \__\:\@
+ /__/\ | |:|@
+ \ \:\| |:|@
+ \ \:\__|:|@
+ \__\::::/ @
+ ~~~~ @@
+ ___ @
+ /__/\ @
+ _\_ \:\ @
+ /__/\ \:\ @
+ _\_ \:\ \:\ @
+ /__/\ \:\ \:\@
+ \ \:\ \:\/:/@
+ \ \:\ \::/ @
+ \ \:\/:/ @
+ \ \::/ @
+ \__\/ @@
+ ___ @
+ /__/| @
+ | |:| @
+ | |:| @
+ __|__|:| @
+ /__/::::\____@
+ ~\~~\::::/@
+ |~~|:|~~ @
+ | |:| @
+ | |:| @
+ |__|/ @@
+ @
+ ___ @
+ /__/| @
+ | |:| @
+ | |:| @
+ __|__|:| @
+ /__/::::\ @
+ ~\~~\:\ @
+ \ \:\@
+ \__\/@
+ @@
+ ___ @
+ / /\ @
+ / /::| @
+ / /:/:| @
+ / /:/|:|__ @
+ /__/:/ |:| /\@
+ \__\/ |:|/:/@
+ | |:/:/ @
+ | |::/ @
+ | |:/ @
+ |__|/ @@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@
+@@
diff --git a/CodingTracker.Kunikazu723/FigletFonts/larry3d.flf b/CodingTracker.Kunikazu723/FigletFonts/larry3d.flf
new file mode 100644
index 00000000..2988d187
--- /dev/null
+++ b/CodingTracker.Kunikazu723/FigletFonts/larry3d.flf
@@ -0,0 +1,924 @@
+flf2a$ 9 6 30 1 5
+larry3d.flf by Larry Gelberg (larryg@avs.com)
+(stolen liberally from Juan Car's puffy.flf)
+tweaked by Glenn Chappell
+Version 1.2 2/24/94
+
+$$$ @
+ $$$ @
+ $$$ @
+ $$$ @
+ $$$ @
+ $$$ @
+ $$$ @
+ $$$ @
+ $$$@@
+ __ @
+/\ \ @
+\ \ \ @
+ \ \ \ @
+ \ \_\ @
+ \/\_\@
+ \/_/@
+ @
+ @@
+ __ __ @
+/\ \\ \ @
+\ \_\\_\ @
+ \/_//_/$ @
+ $ $ @
+ $ $@
+ @
+ @
+ @@
+ __ __ @
+ _\ \\ \__ @
+/\__ _ _\ @
+\/_L\ \\ \L_ @
+ /\_ _ _\@
+ \/_/\_\\_\/@
+ \/_//_/ @
+ @
+ @@
+ __ @
+/\ \_ @
+\/'__`\ @
+/\ \_\_\ @
+\ \____ \ @
+ \/\ \_\ \@
+ \ `\_ _/@
+ `\_/\_\@
+ \/_/@@
+ __ __ @
+/\_\ / / @
+\/_/ / / @
+ / / @
+ / / __ @
+ /_/ /\_\@
+ /_/ \/_/@
+ @
+ @@
+ ____ @
+/| _ \ @
+|/\ | @
+ \// __`\/\ @
+ /| \L> <_@
+ | \_____/\/@
+ \/____/\/ @
+ @
+ @@
+ __ @
+/\ \ @
+\ \/$ @
+ \/ $ @
+ $ $ @
+ $ $@
+ @
+ @
+ @@
+ _ @
+ /' \ @
+/\ ,/' @
+\ \ \ @
+ \ \ `\ @
+ \ `\__\@
+ `\/_/ @
+ @
+ @@
+ __ @
+/\ `\ @
+\`\ \ @
+ `\`\ \ @
+ `\/' \@
+ /\__/@
+ \/_/ @
+ @
+ @@
+ __ @
+ _\ \ _ @
+/\_` ' \ @
+\/_> <_ @
+ /\_, ,_\@
+ \/_/\_\/@
+ \/_/ @
+ @
+ @@
+ __ @
+ /\ \ @
+ \_\ \___ @
+/\___ __\@
+\/__/\ \_/@
+ \ \_\ @
+ \/_/ @
+ @
+ @@
+ @
+ @
+ @
+ @
+ __ @
+/\ \@
+\ \/@
+ \/ @
+ @@
+ @
+ @
+ @
+ _______ @
+/\______\@
+\/______/@
+ @
+ @
+ @@
+ @
+ @
+ @
+ @
+ __ @
+/\_\@
+\/_/@
+ @
+ @@
+ __@
+ / /@
+ / / @
+ / / @
+ / / @
+ /_/ @
+/_/ @
+ @
+ @@
+ __ @
+ /'__`\ @
+/\ \/\ \ @
+\ \ \ \ \ @
+ \ \ \_\ \@
+ \ \____/@
+ \/___/ @
+ @
+ @@
+ _ @
+ /' \ @
+/\_, \ @
+\/_/\ \ @
+ \ \ \ @
+ \ \_\@
+ \/_/@
+ @
+ @@
+ ___ @
+ /'___`\ @
+/\_\ /\ \ @
+\/_/// /__ @
+ // /_\ \@
+ /\______/@
+ \/_____/ @
+ @
+ @@
+ __ @
+ /'__`\ @
+/\_\L\ \ @
+\/_/_\_<_ @
+ /\ \L\ \@
+ \ \____/@
+ \/___/ @
+ @
+ @@
+ __ __ @
+/\ \\ \ @
+\ \ \\ \ @
+ \ \ \\ \_ @
+ \ \__ ,__\@
+ \/_/\_\_/@
+ \/_/ @
+ @
+ @@
+ ______ @
+/\ ___\ @
+\ \ \__/ @
+ \ \___``\ @
+ \/\ \L\ \@
+ \ \____/@
+ \/___/ @
+ @
+ @@
+ ____ @
+ /'___\ @
+/\ \__/ @
+\ \ _``\ @
+ \ \ \L\ \@
+ \ \____/@
+ \/___/ @
+ @
+ @@
+ ________ @
+/\_____ \@
+\/___//'/'@
+ /' /' @
+ /' /' @
+ /\_/ @
+ \// @
+ @
+ @@
+ __ @
+ /'_ `\ @
+/\ \L\ \ @
+\/_> _ <_ @
+ /\ \L\ \@
+ \ \____/@
+ \/___/ @
+ @
+ @@
+ __ @
+ /'_ `\ @
+/\ \L\ \ @
+\ \___, \ @
+ \/__,/\ \ @
+ \ \_\@
+ \/_/@
+ @
+ @@
+ @
+ @
+ __ @
+/\_\ @
+\/_/_ @
+ /\_\@
+ \/_/@
+ @
+ @@
+ @
+ @
+ __ @
+/\_\ @
+\/_/_ @
+ /\ \@
+ \ \/@
+ \/ @
+ @@
+ ___ @
+ / / @
+ / / @
+/< < @
+\ `\ `\ @
+ `\ `\_|@
+ `\// @
+ @
+ @@
+ @
+ _______ @
+/\______\ @
+\/______/_ @
+ /\______\@
+ \/______/@
+ @
+ @
+ @@
+ __ @
+/\ `\ @
+\ `\ `\ @
+ `\ > >@
+ / / @
+ /\_/ @
+ \// @
+ @
+ @@
+ _ @
+ /'_`\ @
+/\_\/\`\@
+\/_//'/'@
+ /\_\ @
+ \/\_\@
+ \/_/@
+ @
+ @@
+ @
+ __ @
+ /'_`\_ @
+ /'/'_` \ @
+/\ \ \L\ \ @
+\ \ `\__,_\@
+ \ `\_____\@
+ `\/_____/@
+ @@
+ ______ @
+/\ _ \ @
+\ \ \L\ \ @
+ \ \ __ \ @
+ \ \ \/\ \ @
+ \ \_\ \_\@
+ \/_/\/_/@
+ @
+ @@
+ ____ @
+/\ _`\ @
+\ \ \L\ \ @
+ \ \ _ <' @
+ \ \ \L\ \@
+ \ \____/@
+ \/___/ @
+ @
+ @@
+ ____ @
+/\ _`\ @
+\ \ \/\_\ @
+ \ \ \/_/_ @
+ \ \ \L\ \@
+ \ \____/@
+ \/___/ @
+ @
+ @@
+ ____ @
+/\ _`\ @
+\ \ \/\ \ @
+ \ \ \ \ \ @
+ \ \ \_\ \@
+ \ \____/@
+ \/___/ @
+ @
+ @@
+ ____ @
+/\ _`\ @
+\ \ \L\_\ @
+ \ \ _\L @
+ \ \ \L\ \@
+ \ \____/@
+ \/___/ @
+ @
+ @@
+ ____ @
+/\ _`\ @
+\ \ \L\_\@
+ \ \ _\/@
+ \ \ \/ @
+ \ \_\ @
+ \/_/ @
+ @
+ @@
+ ____ @
+/\ _`\ @
+\ \ \L\_\ @
+ \ \ \L_L @
+ \ \ \/, \@
+ \ \____/@
+ \/___/ @
+ @
+ @@
+ __ __ @
+/\ \/\ \ @
+\ \ \_\ \ @
+ \ \ _ \ @
+ \ \ \ \ \ @
+ \ \_\ \_\@
+ \/_/\/_/@
+ @
+ @@
+ ______ @
+/\__ _\ @
+\/_/\ \/ @
+ \ \ \ @
+ \_\ \__ @
+ /\_____\@
+ \/_____/@
+ @
+ @@
+ _____ @
+/\___ \ @
+\/__/\ \ @
+ _\ \ \ @
+ /\ \_\ \@
+ \ \____/@
+ \/___/ @
+ @
+ @@
+ __ __ @
+/\ \/\ \ @
+\ \ \/'/' @
+ \ \ , < @
+ \ \ \\`\ @
+ \ \_\ \_\@
+ \/_/\/_/@
+ @
+ @@
+ __ @
+/\ \ @
+\ \ \ @
+ \ \ \ __ @
+ \ \ \L\ \@
+ \ \____/@
+ \/___/ @
+ @
+ @@
+ @
+ /'\_/`\ @
+/\ \ @
+\ \ \__\ \ @
+ \ \ \_/\ \ @
+ \ \_\\ \_\@
+ \/_/ \/_/@
+ @
+ @@
+ __ __ @
+/\ \/\ \ @
+\ \ `\\ \ @
+ \ \ , ` \ @
+ \ \ \`\ \ @
+ \ \_\ \_\@
+ \/_/\/_/@
+ @
+ @@
+ _____ @
+/\ __`\ @
+\ \ \/\ \ @
+ \ \ \ \ \ @
+ \ \ \_\ \ @
+ \ \_____\@
+ \/_____/@
+ @
+ @@
+ ____ @
+/\ _`\ @
+\ \ \L\ \@
+ \ \ ,__/@
+ \ \ \/ @
+ \ \_\ @
+ \/_/ @
+ @
+ @@
+ _____ @
+/\ __`\ @
+\ \ \/\ \ @
+ \ \ \ \ \ @
+ \ \ \\'\\ @
+ \ \___\_\@
+ \/__//_/@
+ @
+ @@
+ ____ @
+/\ _`\ @
+\ \ \L\ \ @
+ \ \ , / @
+ \ \ \\ \ @
+ \ \_\ \_\@
+ \/_/\/ /@
+ @
+ @@
+ ____ @
+/\ _`\ @
+\ \,\L\_\ @
+ \/_\__ \ @
+ /\ \L\ \ @
+ \ `\____\@
+ \/_____/@
+ @
+ @@
+ ______ @
+/\__ _\ @
+\/_/\ \/ @
+ \ \ \ @
+ \ \ \ @
+ \ \_\@
+ \/_/@
+ @
+ @@
+ __ __ @
+/\ \/\ \ @
+\ \ \ \ \ @
+ \ \ \ \ \ @
+ \ \ \_\ \ @
+ \ \_____\@
+ \/_____/@
+ @
+ @@
+ __ __ @
+/\ \/\ \ @
+\ \ \ \ \ @
+ \ \ \ \ \ @
+ \ \ \_/ \@
+ \ `\___/@
+ `\/__/ @
+ @
+ @@
+ __ __ @
+/\ \ __/\ \ @
+\ \ \/\ \ \ \ @
+ \ \ \ \ \ \ \ @
+ \ \ \_/ \_\ \@
+ \ `\___x___/@
+ '\/__//__/ @
+ @
+ @@
+ __ __ @
+/\ \ /\ \ @
+\ `\`\/'/' @
+ `\/ > < @
+ \/'/\`\ @
+ /\_\\ \_\@
+ \/_/ \/_/@
+ @
+ @@
+ __ __ @
+/\ \ /\ \@
+\ `\`\\/'/@
+ `\ `\ /' @
+ `\ \ \ @
+ \ \_\@
+ \/_/@
+ @
+ @@
+ ________ @
+/\_____ \ @
+\/____//'/' @
+ //'/' @
+ //'/'___ @
+ /\_______\@
+ \/_______/@
+ @
+ @@
+ ____ @
+/\ _\ @
+\ \ \/ @
+ \ \ \ @
+ \ \ \_ @
+ \ \___\@
+ \/___/@
+ @
+ @@
+ __ @
+/\ `\ @
+\`\ `\ @
+`\`\ `\ @
+ `\`\ `\ @
+ `\`\__\@
+ `\/__/@
+ @
+ @@
+ ____ @
+/\__ \ @
+\/_/\ \ @
+ \ \ \ @
+ \_\ \ @
+ /\___\@
+ \/___/@
+ @
+ @@
+ __ @
+ / `\ @
+/\_/\_\ @
+\//\// $ @
+ $ $ @
+ $ $@
+ @
+ @
+ @@
+ @
+ @
+ @
+ @
+ @
+$ $ @
+ $_______ @
+ /\______\@
+ \/______/@@
+ __ @
+/\ \ @
+\ \\$ @
+ \// $ @
+ $ $ @
+ $ $@
+ @
+ @
+ @@
+ @
+ @
+ __ @
+ /'__`\ @
+/\ \L\.\_ @
+\ \__/.\_\@
+ \/__/\/_/@
+ @
+ @@
+ __ @
+/\ \ @
+\ \ \____ @
+ \ \ '__`\ @
+ \ \ \L\ \@
+ \ \_,__/@
+ \/___/ @
+ @
+ @@
+ @
+ @
+ ___ @
+ /'___\ @
+/\ \__/ @
+\ \____\@
+ \/____/@
+ @
+ @@
+ __ @
+ /\ \ @
+ \_\ \ @
+ /'_` \ @
+/\ \L\ \ @
+\ \___,_\@
+ \/__,_ /@
+ @
+ @@
+ @
+ @
+ __ @
+ /'__`\ @
+/\ __/ @
+\ \____\@
+ \/____/@
+ @
+ @@
+ ___ @
+ /'___\ @
+/\ \__/ @
+\ \ ,__\@
+ \ \ \_/@
+ \ \_\ @
+ \/_/ @
+ @
+ @@
+ @
+ @
+ __ @
+ /'_ `\ @
+/\ \L\ \ @
+\ \____ \ @
+ \/___L\ \@
+ /\____/@
+ \_/__/ @@
+ __ @
+/\ \ @
+\ \ \___ @
+ \ \ _ `\ @
+ \ \ \ \ \ @
+ \ \_\ \_\@
+ \/_/\/_/@
+ @
+ @@
+ @
+ __ @
+/\_\ @
+\/\ \ @
+ \ \ \ @
+ \ \_\@
+ \/_/@
+ @
+ @@
+ @
+ __ @
+/\_\ @
+\/\ \ @
+ \ \ \ @
+ _\ \ \ @
+/\ \_\ \@
+\ \____/@
+ \/___/ @@
+ __ @
+/\ \ @
+\ \ \/'\ @
+ \ \ , < @
+ \ \ \\`\ @
+ \ \_\ \_\@
+ \/_/\/_/@
+ @
+ @@
+ ___ @
+/\_ \ @
+\//\ \ @
+ \ \ \ @
+ \_\ \_ @
+ /\____\@
+ \/____/@
+ @
+ @@
+ @
+ @
+ ___ ___ @
+/' __` __`\ @
+/\ \/\ \/\ \ @
+\ \_\ \_\ \_\@
+ \/_/\/_/\/_/@
+ @
+ @@
+ @
+ @
+ ___ @
+/' _ `\ @
+/\ \/\ \ @
+\ \_\ \_\@
+ \/_/\/_/@
+ @
+ @@
+ @
+ @
+ ___ @
+ / __`\ @
+/\ \L\ \@
+\ \____/@
+ \/___/ @
+ @
+ @@
+ @
+ @
+ _____ @
+/\ '__`\ @
+\ \ \L\ \@
+ \ \ ,__/@
+ \ \ \/ @
+ \ \_\ @
+ \/_/ @@
+ @
+ @
+ __ @
+ /'__`\ @
+/\ \L\ \ @
+\ \___, \ @
+ \/___/\ \ @
+ \ \_\@
+ \/_/@@
+ @
+ @
+ _ __ @
+/\`'__\@
+\ \ \/ @
+ \ \_\ @
+ \/_/ @
+ @
+ @@
+ @
+ @
+ ____ @
+ /',__\ @
+/\__, `\@
+\/\____/@
+ \/___/ @
+ @
+ @@
+ __ @
+/\ \__ @
+\ \ ,_\ @
+ \ \ \/ @
+ \ \ \_ @
+ \ \__\@
+ \/__/@
+ @
+ @@
+ @
+ @
+ __ __ @
+/\ \/\ \ @
+\ \ \_\ \@
+ \ \____/@
+ \/___/ @
+ @
+ @@
+ @
+ @
+ __ __ @
+/\ \/\ \ @
+\ \ \_/ |@
+ \ \___/ @
+ \/__/ @
+ @
+ @@
+ @
+ @
+ __ __ __ @
+/\ \/\ \/\ \ @
+\ \ \_/ \_/ \@
+ \ \___x___/'@
+ \/__//__/ @
+ @
+ @@
+ @
+ @
+ __ _ @
+/\ \/'\ @
+\/> @
+ /\_/\_\@
+ \//\/_/@
+ @
+ @@
+ @
+ @
+ __ __ @
+/\ \/\ \ @
+\ \ \_\ \ @
+ \/`____ \ @
+ `/___/> \@
+ /\___/@
+ \/__/ @@
+ @
+ @
+ ____ @
+/\_ ,`\ @
+\/_/ /_ @
+ /\____\@
+ \/____/@
+ @
+ @@
+ _ @
+ /' \@
+ \ ,/'@
+ <' \ @
+< \ `\ @
+ \`\__\@
+ \/__/@
+ @
+ @@
+ __ @
+/\ \ @
+\ \ \ @
+ \ \ \ @
+ \ \ \ @
+ \ \ \ @
+ \ \ \ @
+ \ \_\@
+ \/_/@@
+ __ @
+/\ `\ @
+\`\ \ @
+ \ \ `>@
+ //' \ @
+/\__/' @
+\/_/ @
+ @
+ @@
+ _ _ @
+ /' \/' \ @
+/\_/\__//$ @
+\//\/__/ $ @
+ $ $@
+ @
+ @
+ @
+ @@
+ __ __ @
+/\_\/\_\ @
+\/\ _ \ @
+ \ \ \L\ \ @
+ \ \ __ \ @
+ \ \_\/\_\@
+ \/_/\/_/@
+ @
+ @@
+ __ __ @
+/\_\/\_\ @
+\/\ __ \ @
+ \ \ \/\ \ @
+ \ \ \_\ \ @
+ \ \_____\@
+ \/_____/@
+ @
+ @@
+ __ __ @
+/\_\/\_\ @
+\/\ \/\ \ @
+ \ \ \ \ \ @
+ \ \ \_\ \ @
+ \ \_____\@
+ \/_____/@
+ @
+ @@
+ __ __ @
+/\_\/\_\ @
+\/_/\/_/_ @
+ /'_` \ @
+ /\ \L\ \ @
+ \ `\__,_\@
+ `\/_,__/@
+ @
+ @@
+ __ __ @
+/\_\/\_\ @
+\/_/\/_/ @
+ /'_`\ @
+ /\ \L\ \@
+ \ `\___/@
+ `\/__/ @
+ @
+ @@
+ __ __ @
+/\_\ \_\ @
+\/_/\/_/_ @
+ /\ \/\ \ @
+ \ \ \_\ \@
+ \ `\___/@
+ `\/__/ @
+ @
+ @@
+ ______ @
+/\ __ \ @
+\ \ \/\ \ @
+ \ \ \<_<_ @
+ \ \ \ \ \@
+ \ \ \\_/@
+ \ \_\/ @
+ \/_/ @
+ @@
diff --git a/CodingTracker.Kunikazu723/FigletFonts/smkeyboard.flf b/CodingTracker.Kunikazu723/FigletFonts/smkeyboard.flf
new file mode 100644
index 00000000..cefecc4a
--- /dev/null
+++ b/CodingTracker.Kunikazu723/FigletFonts/smkeyboard.flf
@@ -0,0 +1,466 @@
+flf2a$ 4 4 16 1 57
+
+smkeyboard.flf
+
+Adapted for figlet by Kent Nassen, kentn@cyberspace.org, 11/22/94, based
+on the idea:
+
+From: bas@damek.kth.se (Bas Meijer)
+Newsgroups: rec.arts.ascii
+Subject: Talk-line: keyboard.flf
+Date: 22 Nov 1994 07:26:10 -0600
+Organization: Royal Institute of Technology
+Lines: 41
+Sender: boba@gagme.wwa.com
+Approved: boba@wwa.com
+Message-ID: <3asrhi$con@gagme.wwa.com>
+References: <3asltp$aog@gagme.wwa.com>
+Reply-To: bas@damek.kth.se
+NNTP-Posting-Host: gagme.wwa.com
+
+
+In article aog@gagme.wwa.com, ssfiit@cs.umb.edu (SuperStreetFighter2Turbo) writes:
+>
+> I have composed a new figlet font! =)
+> it is called keyboard.flf. It resemble the actual keyboard, well, kinda! =)
+> anyway! here is a sample! =)
+> _______ _______ _______ _______ _______ _______
+> |\ /|\ /|\ /|\ /|\ /|\ /|
+> | +---+ | +---+ | +---+ | +---+ | +---+ | +---+ |
+> | | | | | | | | | | | | | | | | | | |
+> | |s | | |a | | |m | | |p | | |l | | |e | |
+> | +---+ | +---+ | +---+ | +---+ | +---+ | +---+ |
+> |/_____\|/_____\|/_____\|/_____\|/_____\|/_____\|
+
+ ____ _________ ____ ____ ____ _________ ____ ____ ____ _________
+||A ||| |||B |||I |||T ||| |||T |||O |||O ||| ||
+||__|||_______|||__|||__|||__|||_______|||__|||__|||__|||_______||
+|/__\|/_______\|/__\|/__\|/__\|/_______\|/__\|/__\|/__\|/_______\|____
+||B |||I |||G ||| |||I |||F ||| |||Y |||O |||U ||| ||
+||__|||__|||__|||_______|||__|||__|||_______|||__|||__|||__|||_______||
+|/__\|/__\|/__\|/_______\|/__\|/__\|/_______\|/__\|/__\|/__\|/_______\|
+||A |||S |||K ||| |||M |||E |||! ||| |||: |||) |||Return||
+||__|||__|||__|||_______|||__|||__|||__|||_______|||__|||__||| ||
+|/__\|/__\|/__\|/_______\|/__\|/__\|/__\|/_______\|/__\|/__\|/l ||
+ l|_____||
+Bas. |/_____\|
+
+
+email: bas@damek.kth.se ____ ____ KTH, Dept. of Mach. Design
+ _______ ___ ______ / \/ \ ____________________________
+( \/ \/ \ / \ ) ) ) ) \
+ | | ) \ \___) / \ \__/\ /\ \ \__/ | )
+ | ( | \_ \ ( ( ) ) )_ ( )_) \ )_| (
+ | | ) _ )\ ) \ \__/ / / \/ \ / / \ \ \
+(_______/_/ \__/_____/ \ \/ /_______)___)___/______)__/\___)
+ \____/\____/
+tel.: +46-(0)8-790 6308 S-100 44 Stockholm, Sweden
+
+ _________ @
+|| ||@
+||_______||@
+|/_______\|@@
+ ____ @
+||! ||@
+||__||@
+|/__\|@@
+ ____ @
+||" ||@
+||__||@
+|/__\|@@
+ ____ @
+||# ||@
+||__||@
+|/__\|@@
+ ____ @
+||$ ||@
+||__||@
+|/__\|@@
+ ____ @
+||% ||@
+||__||@
+|/__\|@@
+ ____ @
+||& ||@
+||__||@
+|/__\|@@
+ ____ @
+||' ||@
+||__||@
+|/__\|@@
+ ____ @
+||( ||@
+||__||@
+|/__\|@@
+ ____ @
+||) ||@
+||__||@
+|/__\|@@
+ ____ @
+||* ||@
+||__||@
+|/__\|@@
+ ____ @
+||+ ||@
+||__||@
+|/__\|@@
+ ____ @
+||, ||@
+||__||@
+|/__\|@@
+ ____ @
+||- ||@
+||__||@
+|/__\|@@
+ ____ @
+||. ||@
+||__||@
+|/__\|@@
+ ____ @
+||/ ||@
+||__||@
+|/__\|@@
+ ____ @
+||0 ||@
+||__||@
+|/__\|@@
+ ____ @
+||1 ||@
+||__||@
+|/__\|@@
+ ____ @
+||2 ||@
+||__||@
+|/__\|@@
+ ____ @
+||3 ||@
+||__||@
+|/__\|@@
+ ____ @
+||4 ||@
+||__||@
+|/__\|@@
+ ____ @
+||5 ||@
+||__||@
+|/__\|@@
+ ____ @
+||6 ||@
+||__||@
+|/__\|@@
+ ____ @
+||7 ||@
+||__||@
+|/__\|@@
+ ____ @
+||8 ||@
+||__||@
+|/__\|@@
+ ____ @
+||9 ||@
+||__||@
+|/__\|@@
+ ____ @
+||: ||@
+||__||@
+|/__\|@@
+ ____ @
+||; ||@
+||__||@
+|/__\|@@
+ ____ @
+||< ||@
+||__||@
+|/__\|@@
+ ____ @
+||= ||@
+||__||@
+|/__\|@@
+ ____ @
+||> ||@
+||__||@
+|/__\|@@
+ ____ @
+||? ||@
+||__||@
+|/__\|@@
+ ____ @
+||@ ||@
+||__||@
+|/__\|@@
+ ____ @
+||A ||@
+||__||@
+|/__\|@@
+ ____ @
+||B ||@
+||__||@
+|/__\|@@
+ ____ @
+||C ||@
+||__||@
+|/__\|@@
+ ____ @
+||D ||@
+||__||@
+|/__\|@@
+ ____ @
+||E ||@
+||__||@
+|/__\|@@
+ ____ @
+||F ||@
+||__||@
+|/__\|@@
+ ____ @
+||G ||@
+||__||@
+|/__\|@@
+ ____ @
+||H ||@
+||__||@
+|/__\|@@
+ ____ @
+||I ||@
+||__||@
+|/__\|@@
+ ____ @
+||J ||@
+||__||@
+|/__\|@@
+ ____ @
+||K ||@
+||__||@
+|/__\|@@
+ ____ @
+||L ||@
+||__||@
+|/__\|@@
+ ____ @
+||M ||@
+||__||@
+|/__\|@@
+ ____ @
+||N ||@
+||__||@
+|/__\|@@
+ ____ @
+||O ||@
+||__||@
+|/__\|@@
+ ____ @
+||P ||@
+||__||@
+|/__\|@@
+ ____ @
+||Q ||@
+||__||@
+|/__\|@@
+ ____ @
+||R ||@
+||__||@
+|/__\|@@
+ ____ @
+||S ||@
+||__||@
+|/__\|@@
+ ____ @
+||T ||@
+||__||@
+|/__\|@@
+ ____ @
+||U ||@
+||__||@
+|/__\|@@
+ ____ @
+||V ||@
+||__||@
+|/__\|@@
+ ____ @
+||W ||@
+||__||@
+|/__\|@@
+ ____ @
+||X ||@
+||__||@
+|/__\|@@
+ ____ @
+||Y ||@
+||__||@
+|/__\|@@
+ ____ @
+||Z ||@
+||__||@
+|/__\|@@
+ ____ @
+||[ ||@
+||__||@
+|/__\|@@
+ ____ @
+||\ ||@
+||__||@
+|/__\|@@
+ ____ @
+||] ||@
+||__||@
+|/__\|@@
+ ____ @
+||^ ||@
+||__||@
+|/__\|@@
+ ____ @
+||_ ||@
+||__||@
+|/__\|@@
+ ____ @
+||` ||@
+||__||@
+|/__\|@@
+ ____ @
+||a ||@
+||__||@
+|/__\|@@
+ ____ @
+||b ||@
+||__||@
+|/__\|@@
+ ____ @
+||c ||@
+||__||@
+|/__\|@@
+ ____ @
+||d ||@
+||__||@
+|/__\|@@
+ ____ @
+||e ||@
+||__||@
+|/__\|@@
+ ____ @
+||f ||@
+||__||@
+|/__\|@@
+ ____ @
+||g ||@
+||__||@
+|/__\|@@
+ ____ @
+||h ||@
+||__||@
+|/__\|@@
+ ____ @
+||i ||@
+||__||@
+|/__\|@@
+ ____ @
+||j ||@
+||__||@
+|/__\|@@
+ ____ @
+||k ||@
+||__||@
+|/__\|@@
+ ____ @
+||l ||@
+||__||@
+|/__\|@@
+ ____ @
+||m ||@
+||__||@
+|/__\|@@
+ ____ @
+||n ||@
+||__||@
+|/__\|@@
+ ____ @
+||o ||@
+||__||@
+|/__\|@@
+ ____ @
+||p ||@
+||__||@
+|/__\|@@
+ ____ @
+||q ||@
+||__||@
+|/__\|@@
+ ____ @
+||r ||@
+||__||@
+|/__\|@@
+ ____ @
+||s ||@
+||__||@
+|/__\|@@
+ ____ @
+||t ||@
+||__||@
+|/__\|@@
+ ____ @
+||u ||@
+||__||@
+|/__\|@@
+ ____ @
+||v ||@
+||__||@
+|/__\|@@
+ ____ @
+||w ||@
+||__||@
+|/__\|@@
+ ____ @
+||x ||@
+||__||@
+|/__\|@@
+ ____ @
+||y ||@
+||__||@
+|/__\|@@
+ ____ @
+||z ||@
+||__||@
+|/__\|@@
+ ____ @
+||{ ||@
+||__||@
+|/__\|@@
+ ____ @
+||| ||@
+||__||@
+|/__\|@@
+ ____ @
+||} ||@
+||__||@
+|/__\|@@
+ ____ @
+||~ ||@
+||__||@
+|/__\|@@
+@
+@
+@
+@@
+@
+@
+@
+@@
+@
+@
+@
+@@
+@
+@
+@
+@@
+@
+@
+@
+@@
+@
+@
+@
+@@
+@
+@
+@
+@@
diff --git a/CodingTracker.Kunikazu723/Models/CodingSession.cs b/CodingTracker.Kunikazu723/Models/CodingSession.cs
new file mode 100644
index 00000000..65c15ce8
--- /dev/null
+++ b/CodingTracker.Kunikazu723/Models/CodingSession.cs
@@ -0,0 +1,11 @@
+namespace CodingTracker.Kunikazu723.Models
+{
+ public class CodingSession
+ {
+ //Id, StartTime, EndTime, Duration
+ public int Id { get; set; }
+ public DateTime StartTime { get; set; }
+ public DateTime EndTime { get; set; }
+ public TimeSpan Duration { get; set; }
+ }
+}
diff --git a/CodingTracker.Kunikazu723/Program.cs b/CodingTracker.Kunikazu723/Program.cs
new file mode 100644
index 00000000..886dabab
--- /dev/null
+++ b/CodingTracker.Kunikazu723/Program.cs
@@ -0,0 +1,49 @@
+
+using CodingTracker.Kunikazu723.Dao;
+using CodingTracker.Kunikazu723.Dao.Interfaces;
+using CodingTracker.Kunikazu723.Models;
+using CodingTracker.Kunikazu723.Seeders;
+using CodingTracker.Kunikazu723.Services;
+using CodingTracker.Kunikazu723.UserLand;
+using Microsoft.Extensions.Configuration;
+namespace CodingTracker.Kunikazu723
+{
+ internal class Program
+ {
+ static void Main(string[] args)
+ {
+ var configuration = new ConfigurationBuilder()
+ .SetBasePath(AppContext.BaseDirectory)
+ .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
+ .Build();
+
+ string databasePath = configuration["DatabasePath"]
+ ?? throw new InvalidOperationException("DatabasePath is missing from appsettings.json");
+
+ string connectionString = configuration.GetConnectionString("DefaultConnection")
+ ?? throw new InvalidOperationException("Connection String not found in appsettings.json");
+
+ string dateTimeFormat = configuration["DateTimeFormat"]
+ ?? throw new InvalidOperationException("DateTime Format not found in appsettings.json");
+ bool seedDatabase = configuration.GetValue("SeedDatabase")
+ ?? throw new InvalidOperationException("SeedDatabase not found in appsettings.json");
+
+
+ var dbInitializer = new DatabaseInitializer(connectionString);
+ dbInitializer.Initialize();
+
+ IDao codingSessionDao = new CodingSessionDao(connectionString);
+ var codingSessionSeeder = new CodingSessionSeeder(codingSessionDao, dateTimeFormat);
+
+ if (seedDatabase)
+ codingSessionSeeder.SeedIfEmpty();
+
+ var validation = new Validation(dateTimeFormat);
+ var userInput = new UserInput(validation);
+
+ var tableEngine = new TableEngine(dateTimeFormat);
+ var userInterface = new UserInterface(userInput, tableEngine, codingSessionDao, dateTimeFormat);
+ userInterface.MainMenu();
+ }
+ }
+}
diff --git a/CodingTracker.Kunikazu723/Properties/launchSettings.json b/CodingTracker.Kunikazu723/Properties/launchSettings.json
new file mode 100644
index 00000000..5c5dd003
--- /dev/null
+++ b/CodingTracker.Kunikazu723/Properties/launchSettings.json
@@ -0,0 +1,8 @@
+{
+ "profiles": {
+ "CodingTracker.Kunikazu723": {
+ "commandName": "Project",
+ "workingDirectory": "E:\\csharplearning\\CsharpAcademy\\CodeReviews.Console.CodingTracker\\CodingTracker.Kunikazu723"
+ }
+ }
+}
\ No newline at end of file
diff --git a/CodingTracker.Kunikazu723/Seeders/CodingSessionSeeder.cs b/CodingTracker.Kunikazu723/Seeders/CodingSessionSeeder.cs
new file mode 100644
index 00000000..f52f718b
--- /dev/null
+++ b/CodingTracker.Kunikazu723/Seeders/CodingSessionSeeder.cs
@@ -0,0 +1,65 @@
+using CodingTracker.Kunikazu723.Dao.Interfaces;
+using CodingTracker.Kunikazu723.Models;
+using static CodingTracker.Kunikazu723.Enums.DateEnums;
+
+namespace CodingTracker.Kunikazu723.Seeders
+{
+ internal class CodingSessionSeeder
+ {
+ private readonly IDao _dao;
+ private readonly string _dateFormat;
+ private readonly Random _rng = new Random();
+ public CodingSessionSeeder(IDao dao, string dateFormat)
+ {
+ _dao = dao;
+ _dateFormat = dateFormat;
+ }
+
+ public void SeedIfEmpty()
+ {
+ if (_dao.GetAllItems().Count == 0)
+ {
+ _dao.InsertMany(GenerateData());
+ }
+ }
+
+ private List GenerateData()
+ {
+ var data = new List();
+ DateTime initialDate = new DateTime(2025, 01, 01);
+ for (int i = 0; i < 100; i++)
+ {
+ DateTime startTime = GenerateDate(initialDate, 12, TimeType.Hour);
+ DateTime endTime = GenerateDate(startTime, 5, TimeType.Hour);
+
+ data.Add(
+ new CodingSession()
+ {
+ StartTime = startTime,
+ EndTime = endTime,
+ });
+
+ initialDate = GenerateDate(endTime, 7, TimeType.Day);
+ }
+
+ return data;
+ }
+
+ private DateTime GenerateDate(DateTime startDate, int range, TimeType rangeType)
+ {
+ if (range <= 0)
+ {
+ throw new ArgumentException("range cannot be lesser than 1");
+ }
+ int randomNumber = _rng.Next(1, range);
+ return rangeType switch
+ {
+ TimeType.Day => startDate.AddDays(randomNumber),
+ TimeType.Hour => startDate.AddHours(randomNumber),
+ TimeType.Minute => startDate.AddMinutes(randomNumber),
+ TimeType.Second => startDate.AddSeconds(randomNumber),
+ _ => throw new NotImplementedException()
+ };
+ }
+ }
+}
diff --git a/CodingTracker.Kunikazu723/Services/TableEngine.cs b/CodingTracker.Kunikazu723/Services/TableEngine.cs
new file mode 100644
index 00000000..51684af0
--- /dev/null
+++ b/CodingTracker.Kunikazu723/Services/TableEngine.cs
@@ -0,0 +1,42 @@
+using CodingTracker.Kunikazu723.Dao.Interfaces;
+using CodingTracker.Kunikazu723.Models;
+using CodingTracker.Kunikazu723.UserLand;
+using Spectre.Console;
+using System.Globalization;
+
+namespace CodingTracker.Kunikazu723.Services
+{
+ public class TableEngine
+ {
+ private readonly string _dateFormat;
+ public TableEngine(string dateFormat)
+ {
+ _dateFormat = dateFormat;
+ }
+
+ public Table GenerateCodingSessionsTable(List codingSessions, string? tableTitle = null)
+ {
+ string durationFormat = @"hh\:mm\:ss";
+ var table = new Table();
+ if (tableTitle is not null)
+ table.Title(tableTitle);
+
+ table.AddColumns("Id", "Start Time", "End Time", $"Duration ({durationFormat})");
+ foreach (CodingSession codingSession in codingSessions)
+ {
+ try
+ {
+ TimeSpan duration = (codingSession.EndTime - codingSession.StartTime);
+ table.AddRow(codingSession.Id.ToString(), codingSession.StartTime.ToString(_dateFormat), codingSession.EndTime.ToString(_dateFormat), duration.ToString(durationFormat));
+
+ }
+ catch (ArgumentException ex)
+ {
+ AnsiConsole.MarkupLine($"[bold red]WARNING: session with ID: {codingSession.Id} \t{ex.Message}[/]");
+ }
+
+ }
+ return table;
+ }
+ }
+}
diff --git a/CodingTracker.Kunikazu723/UserLand/UserInput.cs b/CodingTracker.Kunikazu723/UserLand/UserInput.cs
new file mode 100644
index 00000000..7e13aefc
--- /dev/null
+++ b/CodingTracker.Kunikazu723/UserLand/UserInput.cs
@@ -0,0 +1,102 @@
+using CodingTracker.Kunikazu723.Models;
+using Spectre.Console;
+using System.Diagnostics;
+using static CodingTracker.Kunikazu723.Enums.MenuEnums;
+
+namespace CodingTracker.Kunikazu723.UserLand
+{
+ public class UserInput
+ {
+ private readonly Validation _validation;
+ public UserInput(Validation validation)
+ {
+ _validation = validation;
+ }
+
+ public int AskUserForPositiveInteger(string prompt)
+ {
+ while (true)
+ {
+ int input = AnsiConsole.Ask(prompt);
+ if (input > 0)
+ return input;
+ }
+ }
+
+ public T PromptUserEnumOption(string prompt) where T: struct, Enum
+ {
+ return AnsiConsole.Prompt(
+ new SelectionPrompt()
+ .Title($"[bold yellow]{prompt}[/]")
+ .AddChoices(Enum.GetValues())
+ .UseConverter(OptionToString));
+ }
+ public CodingSession GetUserCodingSession(bool useStopwatch = false)
+ {
+ (DateTime startTime, DateTime endTime) = useStopwatch ? GetStartEndDateViaStopwatch() : GetStartEndDate();
+
+ var codingSession = new CodingSession()
+ {
+ StartTime = startTime,
+ EndTime = endTime
+ };
+ return codingSession;
+ }
+
+ public DateTime GetDateTime()
+ {
+ while (true)
+ {
+ string date = AnsiConsole.Ask($"Enter the date in the format [bold yellow]{_validation.DateFormat}[/]");
+ DateTime parsedDate;
+ if (_validation.IsDateTimeValidFormat(date, out parsedDate))
+ {
+ return parsedDate;
+ }
+ else
+ {
+ AnsiConsole.MarkupLine("[bold red]Invalid Input[/]");
+ }
+ }
+ }
+
+ public (DateTime StartTime, DateTime EndTime) GetStartEndDate()
+ {
+ while (true)
+ {
+ AnsiConsole.MarkupLine("[bold yellow]Start Time[/]");
+ DateTime startTime = GetDateTime();
+
+ Console.WriteLine();
+
+ AnsiConsole.MarkupLine("[bold yellow]End Time[/]");
+ DateTime endTime = GetDateTime();
+
+ if (_validation.AreDatesChronological(startTime, endTime))
+ {
+ return (startTime, endTime);
+ }
+ else
+ {
+ AnsiConsole.MarkupLine("[bold red]Invalid Input: End Time is earlier than Start Time[/]");
+ }
+ }
+ }
+
+ public (DateTime StartTime, DateTime EndTime) GetStartEndDateViaStopwatch()
+ {
+
+ var stopwatch = new Stopwatch();
+
+ stopwatch.Start();
+
+ DateTime startTime = DateTime.Now;
+ AnsiConsole.MarkupLine("[bold yellow]Stopwatch Initiated[/]\n[grey]Press Any Key to stop it[/]");
+ Console.ReadKey();
+
+ stopwatch.Stop();
+
+ return (startTime, (startTime + stopwatch.Elapsed));
+ }
+ }
+}
diff --git a/CodingTracker.Kunikazu723/UserLand/UserInterface.cs b/CodingTracker.Kunikazu723/UserLand/UserInterface.cs
new file mode 100644
index 00000000..3e304b9b
--- /dev/null
+++ b/CodingTracker.Kunikazu723/UserLand/UserInterface.cs
@@ -0,0 +1,257 @@
+using CodingTracker.Kunikazu723.Dao.Interfaces;
+using CodingTracker.Kunikazu723.Models;
+using CodingTracker.Kunikazu723.Services;
+using Spectre.Console;
+using Microsoft.Data.Sqlite;
+using static CodingTracker.Kunikazu723.Enums.MenuEnums;
+using static CodingTracker.Kunikazu723.Enums.Filters;
+
+namespace CodingTracker.Kunikazu723.UserLand
+{
+ public class UserInterface
+ {
+ private readonly UserInput _userInput;
+ private readonly TableEngine _tableEngine;
+ private readonly IDao _dao;
+ private readonly string _dateFormat = string.Empty;
+
+ public UserInterface(UserInput userInput, TableEngine engine, IDao dao, string dateFormat)
+ {
+ _userInput = userInput;
+ _tableEngine = engine;
+ _dao = dao;
+ _dateFormat = dateFormat;
+ }
+ public void MainMenu()
+ {
+ bool isRunning = true;
+ while (isRunning)
+ {
+ Console.Clear();
+ WriteBanner(Color.Blue);
+ MainMenuOptions option = _userInput.PromptUserEnumOption("What do you want to do?");
+ Console.Clear();
+ try
+ {
+ ExecuteOption(option);
+ }
+ catch (SqliteException ex)
+ {
+ if (ex.ErrorCode == 19)
+ {
+ DisplayMessage($"ERROR: Date is already exists in database. Insertion aborted", Color.Red);
+ }
+ else
+ {
+ DisplayMessage($"DATABASE ERROR: \n\t{ex.Message}\nERROR CODE: {ex.ErrorCode}", Color.Red);
+ }
+ }
+ if (option == MainMenuOptions.Exit)
+ isRunning = false;
+
+ AnsiConsole.MarkupLine("[bold grey]Press Any Key To Continue[/]");
+ Console.ReadKey();
+ }
+
+ }
+ private void ExecuteOption(MainMenuOptions option)
+ {
+ switch (option)
+ {
+ case MainMenuOptions.ViewSessions:
+ ViewSessions();
+ break;
+ case MainMenuOptions.LogSessions:
+ LogSession();
+ break;
+ case MainMenuOptions.UpdateSession:
+ UpdateSession();
+ break;
+ case MainMenuOptions.DeleteSession:
+ DeleteSession();
+ break;
+ case MainMenuOptions.Exit:
+ AnsiConsole.MarkupLine("Exiting [bold cyan]Coding Tracker[/]...");
+ break;
+ default:
+ DisplayMessage("Invalid Option", Color.Red);
+ break;
+ }
+ }
+
+
+
+ private void DeleteSession()
+ {
+ WriteBanner(Color.Red, "Delete Session");
+ var codingSession = PromptForExistingSession();
+ if (codingSession is null) return;
+
+ Table sessionTable = _tableEngine.GenerateCodingSessionsTable(new List { codingSession }, "To be Deleted");
+ AnsiConsole.Write(sessionTable);
+
+ bool confirmDeletion = AnsiConsole.Confirm("Are you sure you wish to delete permanently this session?", false);
+ if (confirmDeletion)
+ {
+ _dao.DeleteItemById(codingSession);
+ DisplayMessage("Session Deleted successfully", Color.Green);
+ }
+ else
+ {
+ DisplayMessage("Deletion aborted", Color.Green);
+ }
+ }
+
+ private int PromptForSessionId()
+ {
+ ViewSessions();
+ return _userInput.AskUserForPositiveInteger("Enter the Session's ID");
+ }
+
+ private CodingSession? PromptForExistingSession()
+ {
+ int id = PromptForSessionId();
+ var codingSession = _dao.GetItemById(id);
+ if (codingSession is null)
+ {
+ DisplayMessage($"Coding Session With ID {id} not found\nAborting process", Color.Red);
+ }
+ return codingSession;
+ }
+
+ private CodingSession PromptForNewSession()
+ {
+ bool useStopwatch = AnsiConsole.Confirm("Use stopwatch to track current session in real time?");
+ var codingSession = _userInput.GetUserCodingSession(useStopwatch);
+ return codingSession;
+ }
+
+ private void UpdateSession()
+ {
+ WriteBanner(Color.Cyan, "Update Session");
+ var currentCodingSession = PromptForExistingSession();
+
+ if (currentCodingSession is null) return;
+
+ Table currentItemTable = _tableEngine.GenerateCodingSessionsTable(new List { currentCodingSession }, "Current Coding Session");
+ AnsiConsole.Write(currentItemTable);
+
+ AnsiConsole.MarkupLine("[bold yellow]Enter new Coding Sessino Data[/]");
+ var newCodingSession = _userInput.GetUserCodingSession();
+ newCodingSession.Id = currentCodingSession.Id;
+
+ Table newItemTable = _tableEngine.GenerateCodingSessionsTable(new List { newCodingSession }, "Updated Coding Session");
+ AnsiConsole.Write(newItemTable);
+
+ bool confirmUpdate = AnsiConsole.Confirm("Do you wish to apply the Update?", false);
+
+ if (confirmUpdate)
+ {
+ _dao.UpdateItemById(newCodingSession);
+ DisplayMessage("Coding session updated successfuly", Color.Green);
+ }
+ else
+ {
+ DisplayMessage("Update Aborted. No changes made.", Color.Green);
+ }
+ }
+
+ private void LogSession()
+ {
+ WriteBanner(Color.Yellow, "Log Session");
+
+ var codingSession = PromptForNewSession();
+
+ var toBeInsertedDisplay = _tableEngine.GenerateCodingSessionsTable(new List() { codingSession }, "To Be Inserted");
+ AnsiConsole.Write(toBeInsertedDisplay);
+
+ bool confirmInsertion = AnsiConsole.Confirm("[bold yellow]Do you wish to Log the current session?[/]");
+ if (confirmInsertion)
+ {
+ _dao.InsertItem(codingSession);
+ DisplayMessage("New coding session Successfuly Logged", Color.Green);
+ }
+ else
+ {
+ DisplayMessage("Insertion aborted, no changes made", Color.Green);
+ }
+
+ }
+
+ private void ViewSessions()
+ {
+ var doApplyFilter = AnsiConsole.Confirm("[bold yellow]Apply Filter to view all coding sessions?[/]", false);
+ var codingSessions = _dao.GetAllItems().OrderBy(codingSession => codingSession.StartTime).ToList();
+ if (doApplyFilter)
+ {
+ codingSessions = FilterCodingSessions(codingSessions);
+ }
+
+ if (codingSessions.Count == 0)
+ {
+ AnsiConsole.Write(new Panel("[bold red]No Coding Sessions available[/]")
+ .RoundedBorder()
+ .BorderColor(Color.Yellow));
+ return;
+ }
+
+ var sessionsTable = _tableEngine.GenerateCodingSessionsTable(codingSessions, "Coding Sessions");
+ AnsiConsole.Write(sessionsTable);
+
+ }
+
+ private List FilterCodingSessions(List codingSessions)
+ {
+ var filterType = _userInput.PromptUserEnumOption("[bold yellow]Select Paremeter[/]");
+
+ var amount = _userInput.AskUserForPositiveInteger($"[bold yellow]Enter how many {filterType} before today {DateTime.Today.ToString(_dateFormat)}[/]");
+
+ var today = DateTime.Today;
+
+ var lowestDate = filterType switch
+ {
+ FilterType.Days => today.AddDays(-amount),
+ FilterType.Weeks => today.AddDays(-amount * 7),
+ FilterType.Months => today.AddMonths(-amount),
+ FilterType.Years => today.AddYears(-amount),
+ _ => throw new NotImplementedException()
+ };
+
+ var filteredCodingSessions = codingSessions
+ .Where(codingSession => codingSession.StartTime >= lowestDate);
+
+ if (filteredCodingSessions.Any() == false)
+ return filteredCodingSessions.ToList();
+
+ var filterOrder = _userInput.PromptUserEnumOption("[bold yellow]Select Order[/]");
+
+ filteredCodingSessions = filterOrder switch
+ {
+ FilterOrder.Ascending => filteredCodingSessions.OrderBy(codingSession => codingSession.StartTime),
+ FilterOrder.Descending => filteredCodingSessions.OrderByDescending(codingSession => codingSession.StartTime),
+ _ => throw new NotImplementedException()
+ };
+
+ return filteredCodingSessions.ToList();
+ }
+
+ public void WriteBanner(Color color, string bannerText = "CODING TRACKER")
+ {
+ var fontPath = Path.Combine(AppContext.BaseDirectory, "FigletFonts", "smkeyboard.flf");
+ var figletFont = FigletFont.Load(fontPath);
+
+ var appFiglet = new FigletText(figletFont, bannerText)
+ .Color(color);
+
+ var ruleStyle = Style.Parse(color.ToMarkup());
+ AnsiConsole.Write(new Rule().RuleStyle(ruleStyle));
+ AnsiConsole.Write(appFiglet);
+ AnsiConsole.Write(new Rule().RuleStyle(ruleStyle));
+ }
+
+ void DisplayMessage(string message, Color color)
+ {
+ AnsiConsole.MarkupLine($"[{color.ToMarkup()}]{Markup.Escape(message)}[/]");
+ }
+ }
+}
diff --git a/CodingTracker.Kunikazu723/UserLand/Validation.cs b/CodingTracker.Kunikazu723/UserLand/Validation.cs
new file mode 100644
index 00000000..220ecbf5
--- /dev/null
+++ b/CodingTracker.Kunikazu723/UserLand/Validation.cs
@@ -0,0 +1,15 @@
+using System.Globalization;
+
+namespace CodingTracker.Kunikazu723.UserLand
+{
+ public class Validation
+ {
+ public readonly string DateFormat;
+ public Validation(string dateFormat)
+ {
+ DateFormat = dateFormat;
+ }
+ public bool IsDateTimeValidFormat(string date, out DateTime parsedDate) => DateTime.TryParseExact(date, DateFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out parsedDate);
+ public bool AreDatesChronological(DateTime startTime, DateTime endTime) => endTime > startTime;
+ }
+}
diff --git a/CodingTracker.Kunikazu723/appsettings.json b/CodingTracker.Kunikazu723/appsettings.json
new file mode 100644
index 00000000..01616a8d
--- /dev/null
+++ b/CodingTracker.Kunikazu723/appsettings.json
@@ -0,0 +1,8 @@
+{
+ "DatabasePath": "coding-tracker.db",
+ "ConnectionStrings": {
+ "DefaultConnection": "Data Source=coding-tracker.db"
+ },
+ "DateTimeFormat": "dd-MM-yyyy-HH-mm-ss",
+ "SeedDatabase" : true
+}
\ No newline at end of file
diff --git a/CodingTracker.UnitTests/CodingTracker.UnitTests.csproj b/CodingTracker.UnitTests/CodingTracker.UnitTests.csproj
new file mode 100644
index 00000000..557e16dc
--- /dev/null
+++ b/CodingTracker.UnitTests/CodingTracker.UnitTests.csproj
@@ -0,0 +1,27 @@
+
+
+
+ net9.0
+ latest
+ enable
+ enable
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/CodingTracker.UnitTests/DefaultValidationTests.cs b/CodingTracker.UnitTests/DefaultValidationTests.cs
new file mode 100644
index 00000000..d301ab57
--- /dev/null
+++ b/CodingTracker.UnitTests/DefaultValidationTests.cs
@@ -0,0 +1,166 @@
+using CodingTracker.Kunikazu723.UserLand;
+
+namespace CodingTracker.UnitTests
+{
+ public class DefaultValidationTests
+ {
+ private Validation _validation = null!;
+
+ [SetUp]
+ public void Setup()
+ {
+ _validation = new Validation("dd-MM-yyyy-HH-mm-ss");
+ }
+
+ private DateTime Parse(string input)
+ {
+ Assert.That(_validation.IsDateTimeValidFormat(input, out var date), Is.True, $"Test data '{input}' should be valid");
+ return date;
+ }
+
+ [TestCase("22-10-2026-14-30-45", TestName = "Typical date")]
+ [TestCase("01-01-2026-00-00-00", TestName = "Start of year, midnight")]
+ [TestCase("31-12-2026-23-59-59", TestName = "End of year, last second")]
+ [TestCase("29-02-2024-12-00-00", TestName = "Leap day, leap year")]
+ [TestCase("28-02-2023-12-00-00", TestName = "Last Feb day, non-leap year")]
+ [TestCase("30-04-2026-10-10-10", TestName = "30-day month, day 30")]
+ public void IsDateTimeValidFormat_CorrectFormat_ReturnsTrue(string inputDate)
+ {
+ bool isValid = _validation.IsDateTimeValidFormat(inputDate, out _);
+ Assert.That(isValid, Is.True);
+ }
+
+ // Out-of-range values
+ [TestCase("29-02-2023-12-00-00", TestName = "Feb 29 in non-leap year")]
+ [TestCase("31-04-2026-12-00-00", TestName = "Day 31 in 30-day month")]
+ [TestCase("32-01-2026-12-00-00", TestName = "Day 32")]
+ [TestCase("00-01-2026-12-00-00", TestName = "Day zero")]
+ [TestCase("15-13-2026-12-00-00", TestName = "Month 13")]
+ [TestCase("15-00-2026-12-00-00", TestName = "Month zero")]
+ [TestCase("15-06-2026-24-00-00", TestName = "Hour 24")]
+ [TestCase("15-06-2026-12-60-00", TestName = "Minute 60")]
+ [TestCase("15-06-2026-12-00-60", TestName = "Second 60")]
+ [TestCase("15-06-0000-12-00-00", TestName = "Year zero")]
+ // Wrong shape
+ [TestCase("22/10/2026/14/30/45", TestName = "Wrong separator")]
+ [TestCase("22-10-2026 14:30:45", TestName = "Mixed separators")]
+ [TestCase("22-10-2026-14-30", TestName = "Missing seconds")]
+ [TestCase("22-10-2026-14-30-45-99", TestName = "Extra part")]
+ [TestCase("2026-10-22-14-30-45", TestName = "Year first")]
+ [TestCase("22-10-26-14-30-45", TestName = "Two-digit year")]
+ [TestCase("2-10-2026-14-30-45", TestName = "Single-digit day")]
+ [TestCase("22-10-2026-4-30-45", TestName = "Single-digit hour")]
+ // Garbage and edge input
+ [TestCase("ab-cd-efgh-ij-kl-mn", TestName = "Letters")]
+ [TestCase("22-10-2026-14-30-4x", TestName = "One bad character")]
+ [TestCase(" 22-10-2026-14-30-45", TestName = "Leading space")]
+ [TestCase("22-10-2026-14-30-45 ", TestName = "Trailing space")]
+ [TestCase("", TestName = "Empty string")]
+ [TestCase(" ", TestName = "Whitespace only")]
+ [TestCase(null, TestName = "Null")]
+ public void IsDateTimeValidFormat_InvalidInput_ReturnsFalse(string? input)
+ {
+ bool isValid = _validation.IsDateTimeValidFormat(input!, out _);
+
+ Assert.That(isValid, Is.False);
+ }
+
+ [TestCase("22-10-2026-14-00-00", "22-10-2026-15-30-00", TestName = "Same day, end later")]
+ [TestCase("22-10-2026-23-00-00", "23-10-2026-01-00-00", TestName = "Crosses midnight")]
+ [TestCase("31-12-2026-23-59-59", "01-01-2027-00-00-01", TestName = "Crosses year boundary")]
+ public void AreDatesChronological_ValidInput_ReturnsTrue(string start, string end)
+ {
+ Assert.That(_validation.AreDatesChronological(Parse(start), Parse(end)), Is.True);
+ }
+
+ [TestCase("22-10-2026-15-30-00", "22-10-2026-14-00-00", TestName = "End before start")]
+ [TestCase("23-10-2026-01-00-00", "22-10-2026-23-00-00", TestName = "End on previous day")]
+ [TestCase("22-10-2026-14-00-00", "22-10-2026-14-00-00", TestName = "Equal times")]
+ public void AreDatesChronological_EndNotAfterStart_ReturnsFalse(string start, string end)
+ {
+ Assert.That(_validation.AreDatesChronological(Parse(start), Parse(end)), Is.False);
+ }
+ }
+
+ public class IsoValidationTests
+ {
+ private Validation _validation = null!;
+
+ [SetUp]
+ public void SetUp()
+ {
+ _validation = new Validation("yyyy-MM-dd'T'HH:mm:ss");
+ }
+
+ private DateTime Parse(string input)
+ {
+ Assert.That(_validation.IsDateTimeValidFormat(input, out var date), Is.True, $"Test data '{input}' should be valid");
+ return date;
+ }
+
+ // Valid inputs
+ [TestCase("2026-10-22T14:30:45", TestName = "Typical date")]
+ [TestCase("2026-01-01T00:00:00", TestName = "Start of year")]
+ [TestCase("2026-12-31T23:59:59", TestName = "End of year")]
+ [TestCase("2024-02-29T12:00:00", TestName = "Leap day, leap year")]
+ [TestCase("2023-02-28T12:00:00", TestName = "Last Feb day, non-leap year")]
+ [TestCase("2026-04-30T10:10:10", TestName = "30-day month, day 30")]
+ public void IsDateTimeValidFormat_ValidInput_ReturnsTrue(string input)
+ {
+ bool isValid = _validation.IsDateTimeValidFormat(input, out _);
+
+ Assert.That(isValid, Is.True);
+ }
+
+ // Invalid inputs
+ [TestCase("2023-02-29T12:00:00", TestName = "Feb 29 in non-leap year")]
+ [TestCase("2026-04-31T12:00:00", TestName = "Day 31 in 30-day month")]
+ [TestCase("2026-01-32T12:00:00", TestName = "Day 32")]
+ [TestCase("2026-01-00T12:00:00", TestName = "Day zero")]
+ [TestCase("2026-13-15T12:00:00", TestName = "Month 13")]
+ [TestCase("2026-00-15T12:00:00", TestName = "Month zero")]
+ [TestCase("2026-06-15T24:00:00", TestName = "Hour 24")]
+ [TestCase("2026-06-15T12:60:00", TestName = "Minute 60")]
+ [TestCase("2026-06-15T12:00:60", TestName = "Second 60")]
+ [TestCase("2026-10-22 14:30:45", TestName = "Space instead of T")]
+ [TestCase("2026-10-22t14:30:45", TestName = "Lowercase t")]
+ [TestCase("2026/10/22T14:30:45", TestName = "Wrong date separator")]
+ [TestCase("2026-10-22T14-30-45", TestName = "Wrong time separator")]
+ [TestCase("22-10-2026T14:30:45", TestName = "Day first")]
+ [TestCase("26-10-22T14:30:45", TestName = "Two-digit year")]
+ [TestCase("2026-10-22T14:30", TestName = "Missing seconds")]
+ [TestCase("2026-10-22", TestName = "Date only")]
+ [TestCase("2026-10-22T14:30:45Z", TestName = "Trailing Z")]
+ [TestCase("2026-10-22T14:30:45.123", TestName = "Fractional seconds")]
+ [TestCase("2026-10-22T14:30:45+03:00", TestName = "Timezone offset")]
+ [TestCase("2026-1-2T14:30:45", TestName = "Single-digit month and day")]
+ [TestCase("abcd-ef-ghTij:kl:mn", TestName = "Letters")]
+ [TestCase(" 2026-10-22T14:30:45", TestName = "Leading space")]
+ [TestCase("2026-10-22T14:30:45 ", TestName = "Trailing space")]
+ [TestCase("", TestName = "Empty string")]
+ [TestCase(" ", TestName = "Whitespace only")]
+ [TestCase(null, TestName = "Null")]
+ public void IsDateTimeValidFormat_InvalidInput_ReturnsFalse(string? input)
+ {
+ bool isValid = _validation.IsDateTimeValidFormat(input!, out _);
+
+ Assert.That(isValid, Is.False);
+ }
+
+ [TestCase("2026-10-22T14:00:00", "2026-10-22T15:30:00", TestName = "Same day, end later")]
+ [TestCase("2026-10-22T23:00:00", "2026-10-23T01:00:00", TestName = "Crosses midnight")]
+ [TestCase("2026-12-31T23:59:59", "2027-01-01T00:00:01", TestName = "Crosses year boundary")]
+ public void AreDatesChronological_ValidInput_ReturnsTrue(string start, string end)
+ {
+ Assert.That(_validation.AreDatesChronological(Parse(start), Parse(end)), Is.True);
+ }
+
+ [TestCase("2026-10-22T15:30:00", "2026-10-22T14:00:00", TestName = "End before start")]
+ [TestCase("2026-10-23T01:00:00", "2026-10-22T23:00:00", TestName = "End on previous day")]
+ [TestCase("2026-10-22T14:00:00", "2026-10-22T14:00:00", TestName = "Equal times")]
+ public void AreDatesChronological_EndNotAfterStart_ReturnsFalse(string start, string end)
+ {
+ Assert.That(_validation.AreDatesChronological(Parse(start), Parse(end)), Is.False);
+ }
+ }
+}
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 00000000..6506ce54
--- /dev/null
+++ b/README.md
@@ -0,0 +1,79 @@
+
+
+
+
+## :mag_right: About
+Tracking Coding Sessions is a good way for the user to evaluate how often they code. **Coding Tracker** is a [TUI](https://en.wikipedia.org/wiki/Text-based_user_interface) application that offers simple [CRUD](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete) operations to track the user's Coding Sessions by either manually typing the start and end of a coding session, or by using a built-in Stopwatch to track the session in real time.
+
+**Packages**:
+- [Dapper](https://github.com/dapperlib/dapper)
+- [ADO.NET SQLite provider](https://learn.microsoft.com/en-us/dotnet/standard/data/sqlite/?tabs=net-cli)
+- [Spectre Console](https://spectreconsole.net/)
+- [Microsoft Configuration Extensions for .NET](https://learn.microsoft.com/en-us/dotnet/core/extensions/configuration)
+
+## How to run locally
+The app has not been published, so it needs to run locally by .NET's runtime.
+> [!IMPORTANT]
+> Make sure [**.NET SDK 9**](https://dotnet.microsoft.com/en-us/download/dotnet/9.0) or higher is installed in order to run this project.
+
+Clone the repository:
+```
+git clone https://github.com/Kunikazu723/TCSA.HabitTracker.git
+cd CodeReviews.Console.CodingTracker
+```
+
+Restore dependencies:
+```
+dotnet restore
+```
+
+Run the project:
+```
+dotnet run --poject CodingTracker.Kunikazu723
+```
+
+To change databasePath, connection strings and date time format, refer to the [App Settings](#app-settings-file) section.
+
+## 💾 How it works
+First the Database is generated with 100 random Coding Sessions (Database seeding can be configured using [App Settings](#app-settings-file)). Then the user is presented with 5 options tha can be selected using the keyboard's arrows, 4 of them are CRUD operations the last one is to exit the app.
+Each Coding Session needs a Start Date and End Date, both are stored in the database with a default date-time format. The duration is calculated during data presentation.
+
+## :pencil2: Architectural Choices
+### Database Structure
+The database has a Coding Sessions table that contains Id, Start Time and End Time. Both Start and End times follow one strict storage format (yyyy-MM-dd hh:mm:ss).
+
+### Data Access
+Database access is made through a Data Access Object (DAO) to make client code esaily communicate with the database through simple method calls.
+Usually I would use a service class as an intermediate between the DAO and the User Interface, but for a project this size I thought it was a bit overkill. I uderstand that an intermediate would be valuable to make sure any data entering the Database is clean and follows the specified pattern. But for simplicity sake I chose to make the UI communicate directly to the DAO.
+> [!NOTE]
+> One thing worth noticing is that DateTime objects are stored in the database using Dapper calls directly without any conversion made by my code, it uses [Microsoft's ADO.NET Sqlite provider implicit conversion](https://learn.microsoft.com/pt-br/dotnet/standard/data/sqlite/types) that follows DateTime's ISO string format.
+
+### User Interface
+As usual the UI is projected as a TUI by using [Spectre Console](https://spectreconsole.net/) package, allowing for intuitive, pretty and smooth user experience while also making it far easier to design it.
+
+### App Settings File
+The following is an example containing the required settings that the application uses to run properly.
+```{
+ "DatabasePath": "coding-tracker.db",
+ "ConnectionStrings": {
+ "DefaultConnection": "Data Source=coding-tracker.db"
+ },
+ "DateTimeFormat": "dd-MM-yyyy-HH-mm-ss",
+ "SeedDatabase" : true
+}
+```
+**DatabasePath and ConnectionStrings**: Sets up the relative database path that will be outputed in de working directory. Its available, but its use is not fully fleshed out. Use Path builder and connection builder would be better but they're not currently implemented.
+
+**DateTimeFormat**: This will define the format that will be **presented** to the user. This setting does not affect the way DateTime strings are stored into the database, it only changes presentation.
+
+**SeedDatabase**: If set to true, the Seeding algorithm will run and the database will be populated with 100 random Coding Sessions.
+
+## :mirror: Reflection
+Even though the use of Dapper was required, it turned out to be quite simple, even simpler than using raw ADO.NET. There were few differences in the overall project structure compared to my [previous project](https://github.com/Kunikazu723/TCSA.HabitTracker). The only noticeable differences were the use of Dapper micro ORM, configuration files and DateTime logic.
+
+It was an interesting project. I wish to improve on it when I gather more experience. Maybe adding a GUI later on.
+
+### Challenges
+By far the most difficult challenge was designing a good filter for displaying Coding Sessions. Setting up the date rolling window was tricky and displayed incorrect or unexpected results. Luckily after banging my head against the wall for a bit I managed to make a predictable rolling window.
+
+Another surprising challenge was properly accessing the [App Settings File](#app-settings-file). The task is seemingly simple but I found myself deep into the rabbit hole of configuration files and how to set them up properly. After reading some articles I found the Extension Configuration package from microsoft.
\ No newline at end of file