diff --git a/DrinksInfo.m1chael888/DrinksInfo.m1chael888.slnx b/DrinksInfo.m1chael888/DrinksInfo.m1chael888.slnx new file mode 100644 index 00000000..c0efd021 --- /dev/null +++ b/DrinksInfo.m1chael888/DrinksInfo.m1chael888.slnx @@ -0,0 +1,3 @@ + + + diff --git a/DrinksInfo.m1chael888/DrinksInfo.m1chael888/Controllers/DrinksController.cs b/DrinksInfo.m1chael888/DrinksInfo.m1chael888/Controllers/DrinksController.cs new file mode 100644 index 00000000..bab50d35 --- /dev/null +++ b/DrinksInfo.m1chael888/DrinksInfo.m1chael888/Controllers/DrinksController.cs @@ -0,0 +1,88 @@ +using DrinksInfo.m1chael888.Views; +using DrinksInfo.m1chael888.Services; +using DrinksInfo.m1chael888.Models; +using static DrinksInfo.m1chael888.Enums.MainMenuEnums; +using Spectre.Console; + +namespace DrinksInfo.m1chael888.Controllers; + +public class DrinksController +{ + private readonly IDrinksView _drinksView; + private readonly IDrinksService _drinksService; + public DrinksController(IDrinksView tableView, IDrinksService drinksService) + { + _drinksView = tableView; + _drinksService = drinksService; + } + + public void HandleMainMenu() + { + var choice = _drinksView.ShowMainMenu(); + + switch (choice) + { + case MainMenuOption.ViewCategories: + try + { + HandleCategoryChoice(); + } + catch (Exception ex) + { + _drinksView.ShowAccessError(ex.Message); + } + break; + case MainMenuOption.Exit: + Environment.Exit(0); + break; + } + } + + private void HandleCategoryChoice() + { + var categories = _drinksService.GetCategories(); + if (categories.Count == 0) + { + ReturnStatus("Drinks menu currently unnavailable"); + } + else + { + var categoryChoice = _drinksView.ShowCategoryPrompt(categories); + try + { + HandleDrinkChoice(categoryChoice); + } + catch (Exception ex) + { + _drinksView.ShowAccessError(ex.Message); + } + } + } + + private void HandleDrinkChoice(Category categoryChoice) + { + var drinks = _drinksService.GetDrinks(categoryChoice.strCategory); + if (drinks.Count == 0) + { + ReturnStatus("Drinks menu currently unnavailable"); + } + else + { + var drinkChoice = _drinksView.ShowDrinkPrompt(drinks); + _drinksView.ShowDrinkInfo(drinkChoice); + } + + } + + private void ReturnStatus(string msg) + { + AnsiConsole.MarkupLine($"[cyan]{msg}[/]"); + AnsiConsole.Status() + .Spinner(Spinner.Known.Point) + .SpinnerStyle("white") + .Start($"[grey74]Press any key to return[/]", x => + { + Console.ReadKey(); + }); + } +} \ No newline at end of file diff --git a/DrinksInfo.m1chael888/DrinksInfo.m1chael888/DrinksInfo.m1chael888.csproj b/DrinksInfo.m1chael888/DrinksInfo.m1chael888/DrinksInfo.m1chael888.csproj new file mode 100644 index 00000000..bc546891 --- /dev/null +++ b/DrinksInfo.m1chael888/DrinksInfo.m1chael888/DrinksInfo.m1chael888.csproj @@ -0,0 +1,17 @@ + + + + Exe + net10.0 + enable + enable + + + + + + + + + + diff --git a/DrinksInfo.m1chael888/DrinksInfo.m1chael888/Enums/Extensions.cs b/DrinksInfo.m1chael888/DrinksInfo.m1chael888/Enums/Extensions.cs new file mode 100644 index 00000000..83cfdbfa --- /dev/null +++ b/DrinksInfo.m1chael888/DrinksInfo.m1chael888/Enums/Extensions.cs @@ -0,0 +1,17 @@ +using System.ComponentModel; + +namespace DrinksInfo.m1chael888.Enums; + +public static class Extensions +{ + public static string GetDescription(Enum value) + { + var field = value.GetType().GetField(value.ToString()); + var attributes = (DescriptionAttribute[])field.GetCustomAttributes(typeof(DescriptionAttribute), false); + if (attributes.Length > 0) + { + return attributes[0].Description; + } + return value.ToString(); + } +} diff --git a/DrinksInfo.m1chael888/DrinksInfo.m1chael888/Enums/MainMenuEnums.cs b/DrinksInfo.m1chael888/DrinksInfo.m1chael888/Enums/MainMenuEnums.cs new file mode 100644 index 00000000..d3bd8ac8 --- /dev/null +++ b/DrinksInfo.m1chael888/DrinksInfo.m1chael888/Enums/MainMenuEnums.cs @@ -0,0 +1,14 @@ +using System.ComponentModel; + +namespace DrinksInfo.m1chael888.Enums; + +public static class MainMenuEnums +{ + public enum MainMenuOption + { + [Description("View Categories")] + ViewCategories, + [Description("Exit App")] + Exit + } +} diff --git a/DrinksInfo.m1chael888/DrinksInfo.m1chael888/Infrastructure/Router.cs b/DrinksInfo.m1chael888/DrinksInfo.m1chael888/Infrastructure/Router.cs new file mode 100644 index 00000000..3d3b94a8 --- /dev/null +++ b/DrinksInfo.m1chael888/DrinksInfo.m1chael888/Infrastructure/Router.cs @@ -0,0 +1,18 @@ +using DrinksInfo.m1chael888.Controllers; + +namespace DrinksInfo.m1chael888.Infrastructure; + +public interface IRouter +{ + void Route(DrinksController drinksController); +} +public class Router : IRouter +{ + public void Route(DrinksController drinksController) + { + while (true) + { + drinksController.HandleMainMenu(); + } + } +} diff --git a/DrinksInfo.m1chael888/DrinksInfo.m1chael888/Models/CategoryModel.cs b/DrinksInfo.m1chael888/DrinksInfo.m1chael888/Models/CategoryModel.cs new file mode 100644 index 00000000..19595b7f --- /dev/null +++ b/DrinksInfo.m1chael888/DrinksInfo.m1chael888/Models/CategoryModel.cs @@ -0,0 +1,13 @@ +using Newtonsoft.Json; + +namespace DrinksInfo.m1chael888.Models; + +public class Category +{ + public string strCategory { get; set; } = string.Empty; +} +public class Categories +{ + [JsonProperty("drinks")] + public List CategoriesList { get; set; } +} \ No newline at end of file diff --git a/DrinksInfo.m1chael888/DrinksInfo.m1chael888/Models/DrinkModel.cs b/DrinksInfo.m1chael888/DrinksInfo.m1chael888/Models/DrinkModel.cs new file mode 100644 index 00000000..39b44687 --- /dev/null +++ b/DrinksInfo.m1chael888/DrinksInfo.m1chael888/Models/DrinkModel.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; + +namespace DrinksInfo.m1chael888.Models; + +public class Drink +{ + public string strDrink { get; set; } = string.Empty; + public int idDrink { get; set; } + public string strDrinkThumb { get; set; } = string.Empty; +} + +public class Drinks +{ + [JsonProperty("drinks")] + public List DrinkList { get; set; } +} diff --git a/DrinksInfo.m1chael888/DrinksInfo.m1chael888/Program.cs b/DrinksInfo.m1chael888/DrinksInfo.m1chael888/Program.cs new file mode 100644 index 00000000..6241067a --- /dev/null +++ b/DrinksInfo.m1chael888/DrinksInfo.m1chael888/Program.cs @@ -0,0 +1,29 @@ +using Microsoft.Extensions.DependencyInjection; +using DrinksInfo.m1chael888.Infrastructure; +using DrinksInfo.m1chael888.Services; +using DrinksInfo.m1chael888.Views; +using System.Text; +using DrinksInfo.m1chael888.Controllers; + +namespace DrinksInfo.m1chael888; + +internal class Program +{ + static void Main(string[] args) + { + Console.OutputEncoding = Encoding.UTF8; + + var collection = new ServiceCollection(); + + collection.AddScoped(); + collection.AddScoped(); + collection.AddScoped(); + collection.AddScoped (); + + var provider = collection.BuildServiceProvider(); + + var drinksController = provider.GetRequiredService(); + var router = provider.GetRequiredService(); + router.Route(drinksController); + } +} \ No newline at end of file diff --git a/DrinksInfo.m1chael888/DrinksInfo.m1chael888/Services/DrinksService.cs b/DrinksInfo.m1chael888/DrinksInfo.m1chael888/Services/DrinksService.cs new file mode 100644 index 00000000..cc19772c --- /dev/null +++ b/DrinksInfo.m1chael888/DrinksInfo.m1chael888/Services/DrinksService.cs @@ -0,0 +1,50 @@ +using Newtonsoft.Json; +using RestSharp; +using DrinksInfo.m1chael888.Models; + +namespace DrinksInfo.m1chael888.Services; + +public interface IDrinksService +{ + List GetCategories(); + List GetDrinks(string category); +} +public class DrinksService : IDrinksService +{ + private readonly string _drinkClient = "https://www.thecocktaildb.com/api/json/v1/1/"; + public List GetCategories() + { + var client = new RestClient(_drinkClient); + var request = new RestRequest("list.php?c=list"); + var response = client.ExecuteAsync(request); + + if (response.Result.StatusCode == System.Net.HttpStatusCode.OK) + { + var responseString = response.Result.Content; + var serialize = JsonConvert.DeserializeObject(responseString); + + List categories = serialize.CategoriesList; + + return categories; + } + return new List { }; + } + + public List GetDrinks(string category) + { + var client = new RestClient(_drinkClient); + var request = new RestRequest($"filter.php?c={category}"); + var response = client.ExecuteAsync(request); + + if (response.Result.StatusCode == System.Net.HttpStatusCode.OK) + { + var responseString = response.Result.Content; + var serialize = JsonConvert.DeserializeObject(responseString); + + List drinks = serialize.DrinkList; + + return drinks; + } + return new List { }; + } +} \ No newline at end of file diff --git a/DrinksInfo.m1chael888/DrinksInfo.m1chael888/Views/DrinksView.cs b/DrinksInfo.m1chael888/DrinksInfo.m1chael888/Views/DrinksView.cs new file mode 100644 index 00000000..e4c1a782 --- /dev/null +++ b/DrinksInfo.m1chael888/DrinksInfo.m1chael888/Views/DrinksView.cs @@ -0,0 +1,83 @@ +using Spectre.Console; +using DrinksInfo.m1chael888.Models; +using static DrinksInfo.m1chael888.Enums.Extensions; +using static DrinksInfo.m1chael888.Enums.MainMenuEnums; + +namespace DrinksInfo.m1chael888.Views; + +public interface IDrinksView +{ + Category ShowCategoryPrompt(List categories); + Drink ShowDrinkPrompt(List drinks); + void ShowDrinkInfo(Drink drink); + MainMenuOption ShowMainMenu(); + void ShowAccessError(string msg); +} +public class DrinksView : IDrinksView +{ + public MainMenuOption ShowMainMenu() + { + Console.Clear(); + return AnsiConsole.Prompt( + new SelectionPrompt() + .Title("[cyan]Main Menu::[/]") + .AddChoices(Enum.GetValues()) + .UseConverter(x => $"[grey74]{GetDescription(x)}[/]") + .HighlightStyle("DarkOrange") + .WrapAround()); + } + + public Category ShowCategoryPrompt(List categories) + { + Console.Clear(); + return AnsiConsole.Prompt( + new SelectionPrompt() + .Title("[cyan]Choose a category to view::[/]") + .AddChoices(categories) + .UseConverter(x => $"[grey74]{x.strCategory}[/]") + .HighlightStyle("DarkOrange") + .PageSize(categories.Count) + .WrapAround()); + } + + public Drink ShowDrinkPrompt(List drinks) + { + Console.Clear(); + return AnsiConsole.Prompt( + new SelectionPrompt() + .Title("[cyan]Choose a drink::[/]") + .AddChoices(drinks) + .UseConverter(x => $"[grey74]{x.strDrink}[/]") + .HighlightStyle("DarkOrange") + .PageSize(25) + .WrapAround()); + } + + public void ShowDrinkInfo(Drink drink) + { + Console.Clear(); + AnsiConsole.MarkupLine($"[cyan]Drink info::[/]\n"); + AnsiConsole.MarkupLine($" [darkorange]Name -[/] [grey74]{drink.strDrink}[/]"); + AnsiConsole.MarkupLine($" [darkorange]Id -[/] [grey74]{drink.idDrink}[/]"); + AnsiConsole.MarkupLine($"[darkorange]Image -[/] [grey74]{drink.strDrinkThumb}[/]\n"); + ReturnToMenu(); + } + + public void ShowAccessError(string msg) + { + Console.Clear(); + AnsiConsole.MarkupLine($"[cyan]Error: {msg}[/]"); + ReturnToMenu(); + } + + private void ReturnToMenu() + { + AnsiConsole.Status() + .Spinner(Spinner.Known.Point) + .SpinnerStyle("grey74") + .Start("[grey74]Press any key to return to menu[/]", x => + { + Console.ReadKey(); + }); + } +} \ No newline at end of file diff --git a/DrinksInfo.m1chael888/README.md b/DrinksInfo.m1chael888/README.md new file mode 100644 index 00000000..5c7db7cf --- /dev/null +++ b/DrinksInfo.m1chael888/README.md @@ -0,0 +1,6 @@ +# DrinksInfo.m1chael888 +This is a simple program used to call a drinks menu API in a C# Console. API calls are done using RestSharp, and the results are displayed in the console using Spectre. + +# How it works +* Starting the app will display a main menu, allowing you to view drink categories or exit the app. Choosing to view categories will display a list of all available drink categories. To choose a category, navigate up and down the list using your arrow keys and press enter to select one. +* After choosing a category, a list of all matching drinks will be similarly displayed, and you can choose one in the same way. After choosing a drink, the details associated with it will be displayed, including the drinks name, id and thumbnail image url. You can then press any key to return to the menu.