diff --git a/DrinkInfoPractice/DrinkInfoPractice.sln b/DrinkInfoPractice/DrinkInfoPractice.sln new file mode 100644 index 00000000..2b262649 --- /dev/null +++ b/DrinkInfoPractice/DrinkInfoPractice.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.12.35707.178 d17.12 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DrinkInfoPractice", "DrinkInfoPractice\DrinkInfoPractice.csproj", "{8FF8B80C-BF62-4C83-9773-1C8728D60E19}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {8FF8B80C-BF62-4C83-9773-1C8728D60E19}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8FF8B80C-BF62-4C83-9773-1C8728D60E19}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8FF8B80C-BF62-4C83-9773-1C8728D60E19}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8FF8B80C-BF62-4C83-9773-1C8728D60E19}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/DrinkInfoPractice/DrinkInfoPractice/DrinkInfoPractice.csproj b/DrinkInfoPractice/DrinkInfoPractice/DrinkInfoPractice.csproj new file mode 100644 index 00000000..e2f26fa9 --- /dev/null +++ b/DrinkInfoPractice/DrinkInfoPractice/DrinkInfoPractice.csproj @@ -0,0 +1,14 @@ + + + + Exe + net9.0 + enable + enable + + + + + + + diff --git a/DrinkInfoPractice/DrinkInfoPractice/Interfaces/ICocktailService.cs b/DrinkInfoPractice/DrinkInfoPractice/Interfaces/ICocktailService.cs new file mode 100644 index 00000000..2ce72f79 --- /dev/null +++ b/DrinkInfoPractice/DrinkInfoPractice/Interfaces/ICocktailService.cs @@ -0,0 +1,10 @@ +using DrinkInfoPractice.Models; + +namespace DrinkInfoPractice.Interfaces; + +internal interface ICocktailService +{ + Task> GetCategoriesAsync(); + Task> GetDrinksByCategoryAsync(string category); + Task GetDrinkByIdAsync(string id); +} diff --git a/DrinkInfoPractice/DrinkInfoPractice/Models/ApiResponse.cs b/DrinkInfoPractice/DrinkInfoPractice/Models/ApiResponse.cs new file mode 100644 index 00000000..45af2c39 --- /dev/null +++ b/DrinkInfoPractice/DrinkInfoPractice/Models/ApiResponse.cs @@ -0,0 +1,9 @@ +using System.Text.Json.Serialization; + +namespace DrinkInfoPractice.Models; + +internal class ApiResponse +{ + [JsonPropertyName("drinks")] + public List Drinks { get; set; } +} diff --git a/DrinkInfoPractice/DrinkInfoPractice/Models/DrinkCategory.cs b/DrinkInfoPractice/DrinkInfoPractice/Models/DrinkCategory.cs new file mode 100644 index 00000000..56549875 --- /dev/null +++ b/DrinkInfoPractice/DrinkInfoPractice/Models/DrinkCategory.cs @@ -0,0 +1,9 @@ +using System.Text.Json.Serialization; + +namespace DrinkInfoPractice.Models; + +internal class DrinkCategory +{ + [JsonPropertyName("strCategory")] + public string StrCategory { get; set; } +} diff --git a/DrinkInfoPractice/DrinkInfoPractice/Models/DrinkDetail.cs b/DrinkInfoPractice/DrinkInfoPractice/Models/DrinkDetail.cs new file mode 100644 index 00000000..c7226f10 --- /dev/null +++ b/DrinkInfoPractice/DrinkInfoPractice/Models/DrinkDetail.cs @@ -0,0 +1,34 @@ +using System.Text.Json.Serialization; + +namespace DrinkInfoPractice.Models; + +internal class DrinkDetail +{ + [JsonPropertyName("idDrink")] + public string IdDrink { get; set; } + + [JsonPropertyName("strDrink")] + public string StrDrink { get; set; } + + [JsonPropertyName("strCategory")] + public string StrCategory { get; set; } + + [JsonPropertyName("strGlass")] + public string StrGlass { get; set; } + + [JsonPropertyName("strInstructions")] + public string StrInstructions { get; set; } + + private Dictionary _ingredients = new(); + + public IReadOnlyDictionary Ingredients => _ingredients; + + public void AddIngredient(int index, string ingredient, string measure) + { + if (!string.IsNullOrWhiteSpace(ingredient)) + { + _ingredients[index] = (ingredient, measure); + } + } + +} diff --git a/DrinkInfoPractice/DrinkInfoPractice/Models/DrinkSummary.cs b/DrinkInfoPractice/DrinkInfoPractice/Models/DrinkSummary.cs new file mode 100644 index 00000000..11c7504a --- /dev/null +++ b/DrinkInfoPractice/DrinkInfoPractice/Models/DrinkSummary.cs @@ -0,0 +1,15 @@ +using System.Text.Json.Serialization; + +namespace DrinkInfoPractice.Models; + +internal class DrinkSummary +{ + [JsonPropertyName("idDrink")] + public string IdDrink { get; set; } + + [JsonPropertyName("strDrink")] + public string StrDrink { get; set; } + + [JsonPropertyName("strDrinkThumb")] + public string StrDrinkThumb { get; set; } +} diff --git a/DrinkInfoPractice/DrinkInfoPractice/Program.cs b/DrinkInfoPractice/DrinkInfoPractice/Program.cs new file mode 100644 index 00000000..79f9f570 --- /dev/null +++ b/DrinkInfoPractice/DrinkInfoPractice/Program.cs @@ -0,0 +1,3 @@ +using DrinkInfoPractice.Services; +UserInput userInput = new UserInput(); +await userInput.Start(); \ No newline at end of file diff --git a/DrinkInfoPractice/DrinkInfoPractice/Services/CocktailService.cs b/DrinkInfoPractice/DrinkInfoPractice/Services/CocktailService.cs new file mode 100644 index 00000000..340e6329 --- /dev/null +++ b/DrinkInfoPractice/DrinkInfoPractice/Services/CocktailService.cs @@ -0,0 +1,128 @@ +using DrinkInfoPractice.Interfaces; +using DrinkInfoPractice.Models; +using System.Text.Json; + + +namespace DrinkInfoPractice.Services; + +internal class CocktailService : ICocktailService +{ + // filed for stroing HttpClient + private readonly HttpClient _httpClient; + + // API's base url + private const string BaseUrl = "http://www.thecocktaildb.com/api/json/v1/1/"; + + public CocktailService() + { + _httpClient = new HttpClient(); + + // setting the base address + _httpClient.BaseAddress = new Uri(BaseUrl); + } + + public async Task> GetCategoriesAsync() + { + try + { + // making the API call + var response = await _httpClient.GetAsync("list.php?c=list"); + + + // check to see if call was successful + response.EnsureSuccessStatusCode(); + + // read the response content + var content = await response.Content.ReadAsStringAsync(); + + + // convert the JSON to our C# objects + // takes the json and creates DrinkCategory object + var result = JsonSerializer.Deserialize>(content); + + + //return list of categories + // if result or Drinks is null, return empty list + return result?.Drinks ?? new List(); + } + catch (Exception ex) + { + Console.WriteLine($"Error getting categories: {ex.Message}"); + throw; + } + } + + public async Task> GetDrinksByCategoryAsync(string category) + { + try + { + // API call + var response = await _httpClient.GetAsync($"filter.php?c={category}"); + + response.EnsureSuccessStatusCode(); + + var content = await response.Content.ReadAsStringAsync(); + + var result = JsonSerializer.Deserialize>(content); + + return result?.Drinks ?? new List(); + } + catch (Exception ex) + { + Console.WriteLine($"Error getting drink summary: {ex.Message}"); + throw; + } + } + + public async Task GetDrinkByIdAsync(string id) + { + try + { + // API call + var response = await _httpClient.GetAsync($"lookup.php?i={id}"); + + response.EnsureSuccessStatusCode(); + + var content = await response.Content.ReadAsStringAsync(); + + var result = JsonSerializer.Deserialize>(content); + + var drink = result?.Drinks?.FirstOrDefault(); + + if (drink != null) + { + // Then process ingredients from the JSON directly + using (JsonDocument document = JsonDocument.Parse(content)) + { + var drinkElement = document.RootElement.GetProperty("drinks")[0]; + + // Process ingredients + for (int i = 1; i <= 15; i++) + { + if (drinkElement.TryGetProperty($"strIngredient{i}", out JsonElement ingredientElement) && + ingredientElement.ValueKind != JsonValueKind.Null) + { + var ingredient = ingredientElement.GetString(); + var measure = "As needed"; + + if (drinkElement.TryGetProperty($"strMeasure{i}", out JsonElement measureElement) && + measureElement.ValueKind != JsonValueKind.Null) + { + measure = measureElement.GetString(); + } + + drink.AddIngredient(i, ingredient, measure); + } + } + } + } + + return drink ?? new DrinkDetail(); + } + catch (Exception ex) + { + Console.WriteLine($"Error getting drink details: {ex.Message}"); + throw; + } + } +} diff --git a/DrinkInfoPractice/DrinkInfoPractice/Services/TableVisualization.cs b/DrinkInfoPractice/DrinkInfoPractice/Services/TableVisualization.cs new file mode 100644 index 00000000..c2adb00c --- /dev/null +++ b/DrinkInfoPractice/DrinkInfoPractice/Services/TableVisualization.cs @@ -0,0 +1,47 @@ +using DrinkInfoPractice.Models; +using Spectre.Console; + +namespace DrinkInfoPractice.Services; + +internal class TableVisualization +{ + internal static void ShowTable(DrinkDetail drinkDetail) + { + List ingredientList = new List(); + foreach (var ingredient in drinkDetail.Ingredients) + { + ingredientList.Add($"- {ingredient.Value.Ingredients}: {ingredient.Value.Measure}"); + } + + string? ingredients = string.Join("\n", ingredientList); + + Console.WriteLine("\n"); + + Table table = new Table(); + + table.AddColumn("Drink ID"); + table.AddColumn("Drink"); + table.AddColumn("Category"); + table.AddColumn("Glass"); + table.AddColumn("Instructions"); + table.AddColumn("Ingredients"); + + table.AddRow( + drinkDetail.IdDrink, + drinkDetail.StrDrink, + drinkDetail.StrCategory, + drinkDetail.StrGlass, + drinkDetail.StrInstructions, + ingredients + ); + + table.Border(TableBorder.Square); + table.BorderColor(Color.Blue); + + AnsiConsole.Write(table); + + Console.WriteLine("Press Enter"); + Console.ReadLine(); + + } +} diff --git a/DrinkInfoPractice/DrinkInfoPractice/Services/UserInput.cs b/DrinkInfoPractice/DrinkInfoPractice/Services/UserInput.cs new file mode 100644 index 00000000..dec34b3c --- /dev/null +++ b/DrinkInfoPractice/DrinkInfoPractice/Services/UserInput.cs @@ -0,0 +1,72 @@ +using Spectre.Console; + +namespace DrinkInfoPractice.Services; + +internal class UserInput +{ + CocktailService cocktailService = new CocktailService(); + + internal async Task Start() + { + bool continueRun = true; + while (continueRun) + { + Console.Clear(); + + string category = await CategoryMenu(); + + string id = await DrinksMenu(category); + + TableVisualization.ShowTable(await cocktailService.GetDrinkByIdAsync(id)); + + continueRun = ContinueRun(); + } + } + + internal bool ContinueRun() + { + var choice = AnsiConsole.Prompt( + new SelectionPrompt() + .Title("Would you like to find another drink?") + .AddChoices(new[] + { + "Yes", + "No" + })); + return choice == "Yes"; + } + + private async Task DrinksMenu(string category) + { + var drinks = await cocktailService.GetDrinksByCategoryAsync(category); + + Dictionary drinkMapping = drinks.ToDictionary(d => d.StrDrink, d => d.IdDrink); + + var drinkNames = drinkMapping.Keys.ToList(); + + var choice = AnsiConsole.Prompt( + new SelectionPrompt() + .Title("Select a drink") + .AddChoices(drinkNames)); + + string id = drinkMapping[choice].ToString(); + return id; + } + + internal async Task CategoryMenu() + { + var categories = await cocktailService.GetCategoriesAsync(); + + var categoryNames = categories.Select(c => c.StrCategory).ToList(); + categoryNames.Add("Exit"); + + var category = AnsiConsole.Prompt( + new SelectionPrompt() + .Title("Selection the category of drink.") + .AddChoices(categoryNames)); + + if (category == "Exit") Environment.Exit(0); + + return category; + } +}