Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions DrinksInfo.m1chael888/DrinksInfo.m1chael888.slnx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<Solution>
<Project Path="DrinksInfo.m1chael888/DrinksInfo.m1chael888.csproj" />
</Solution>
Original file line number Diff line number Diff line change
@@ -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();
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.2" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<PackageReference Include="RestSharp" Version="113.1.0" />
<PackageReference Include="Spectre.Console" Version="0.54.0" />
</ItemGroup>

</Project>
17 changes: 17 additions & 0 deletions DrinksInfo.m1chael888/DrinksInfo.m1chael888/Enums/Extensions.cs
Original file line number Diff line number Diff line change
@@ -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;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Potential IndexOutOfRangeException Bug

💡 The GetDescription method has a logic error. When no DescriptionAttribute is found, it still tries to access attributes[0].Description, which will throw an IndexOutOfRangeException. Fix by returning the enum name instead:

if (attributes.Length > 0)
{
    return attributes[0].Description;
}
return value.ToString();

}
return value.ToString();
}
}
14 changes: 14 additions & 0 deletions DrinksInfo.m1chael888/DrinksInfo.m1chael888/Enums/MainMenuEnums.cs
Original file line number Diff line number Diff line change
@@ -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
}
}
Original file line number Diff line number Diff line change
@@ -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();
}
}
}
Original file line number Diff line number Diff line change
@@ -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<Category> CategoriesList { get; set; }
}
16 changes: 16 additions & 0 deletions DrinksInfo.m1chael888/DrinksInfo.m1chael888/Models/DrinkModel.cs
Original file line number Diff line number Diff line change
@@ -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<Drink> DrinkList { get; set; }
}
29 changes: 29 additions & 0 deletions DrinksInfo.m1chael888/DrinksInfo.m1chael888/Program.cs
Original file line number Diff line number Diff line change
@@ -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<IRouter, Router>();
collection.AddScoped<DrinksController>();
collection.AddScoped<IDrinksService, DrinksService>();
collection.AddScoped <IDrinksView, DrinksView>();

var provider = collection.BuildServiceProvider();

var drinksController = provider.GetRequiredService<DrinksController>();
var router = provider.GetRequiredService<IRouter>();
router.Route(drinksController);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using Newtonsoft.Json;
using RestSharp;
using DrinksInfo.m1chael888.Models;

namespace DrinksInfo.m1chael888.Services;

public interface IDrinksService
{
List<Category> GetCategories();
List<Drink> GetDrinks(string category);
}
public class DrinksService : IDrinksService
{
private readonly string _drinkClient = "https://www.thecocktaildb.com/api/json/v1/1/";
public List<Category> 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<Categories>(responseString);

List<Category> categories = serialize.CategoriesList;

return categories;
}
return new List<Category> { };
}

public List<Drink> 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<Drinks>(responseString);

List<Drink> drinks = serialize.DrinkList;

return drinks;
}
return new List<Drink> { };
}
}
83 changes: 83 additions & 0 deletions DrinksInfo.m1chael888/DrinksInfo.m1chael888/Views/DrinksView.cs
Original file line number Diff line number Diff line change
@@ -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<Category> categories);
Drink ShowDrinkPrompt(List<Drink> 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<MainMenuOption>()
.Title("[cyan]Main Menu::[/]")
.AddChoices(Enum.GetValues<MainMenuOption>())
.UseConverter(x => $"[grey74]{GetDescription(x)}[/]")
.HighlightStyle("DarkOrange")
.WrapAround());
}

public Category ShowCategoryPrompt(List<Category> categories)
{
Console.Clear();
return AnsiConsole.Prompt(
new SelectionPrompt<Category>()
.Title("[cyan]Choose a category to view::[/]")
.AddChoices(categories)
.UseConverter(x => $"[grey74]{x.strCategory}[/]")
.HighlightStyle("DarkOrange")
.PageSize(categories.Count)
.WrapAround());
}

public Drink ShowDrinkPrompt(List<Drink> drinks)
{
Console.Clear();
return AnsiConsole.Prompt(
new SelectionPrompt<Drink>()
.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();
});
}
}
6 changes: 6 additions & 0 deletions DrinksInfo.m1chael888/README.md
Original file line number Diff line number Diff line change
@@ -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.