Previous part: Dynamic Styles and Appearance Behavior in MAUI [GamesCatalog] - Part 12
Step 1. In MainVM.cs, let’s add 3 variables for binding the quantities on the main page.
private int qtdWant = 0;
private int qtdPlaying = 0;
private int qtdPlayed = 0;
public int QtdWant
{
get => qtdWant;
set => SetProperty(ref qtdWant, value);
}
public int QtdPlaying
{
get => qtdPlaying;
set => SetProperty(ref qtdPlaying, value);
}
public int QtdPlayed
{
get => qtdPlayed;
set => SetProperty(ref qtdPlayed, value);
}
Step 2. In the frontend, we’ll create a VerticalStackLayout that will contain the components related to the game status lists.
This component will be a Grid with a border, and inside it, we’ll have a body and a footer.
In the footer, we’ll place 3 components: an icon for the status, the quantity, and an arrow to indicate the card is clickable.
![VerticalStackLayout]()
Code
<VerticalStackLayout Margin="5" VerticalOptions="Fill">
<Border Margin="0,5,0,5" Style="{StaticResource BorderCardItem}">
<Grid
RowDefinitions="*,*"
RowSpacing="5"
Style="{StaticResource GridCardItem}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label
Grid.Row="1"
Margin="0"
FontSize="40"
HorizontalOptions="Start"
Style="{StaticResource LblImgIcon}"
Text="{x:Static Icons:IconFont.Clock}"
VerticalOptions="End" />
<Label
Grid.Row="1"
FontSize="20"
HorizontalOptions="Center"
Style="{StaticResource LblText}"
Text="{Binding QtdWant, StringFormat='{0} Want to Play'}"
VerticalOptions="Center" />
<Label
Grid.Row="1"
Style="{StaticResource ImgAngleRight}" />
</Grid>
</Border>
</VerticalStackLayout>
Step 3. Let’s add styles related to these components.
![Component]()
Code
<Style x:Key="BorderCardItem" TargetType="Border">
<Setter Property="BackgroundColor" Value="{StaticResource SecondaryBGColor}" />
<Setter Property="Stroke" Value="{StaticResource ActiveColor}" />
<Setter Property="StrokeShape" Value="RoundRectangle 10" />
<Setter Property="Padding" Value="10" />
</Style>
<Style x:Key="GridCardItem" TargetType="Grid">
<Setter Property="VerticalOptions" Value="Start" />
<Setter Property="HorizontalOptions" Value="Fill" />
</Style>
<Style x:Key="LblImgIcon" TargetType="Label">
<Setter Property="FontFamily" Value="FontAwesomeIcons" />
<Setter Property="TextColor" Value="{StaticResource White}" />
</Style>
<Style x:Key="ImgAngleRight" TargetType="Label">
<Setter Property="FontFamily" Value="FontAwesomeIcons" />
<Setter Property="VerticalTextAlignment" Value="Center" />
<Setter Property="FontSize" Value="40" />
<Setter Property="TextColor" Value="{StaticResource White}" />
<Setter Property="HorizontalOptions" Value="End" />
<Setter Property="Text" Value="{x:Static Icons:IconFont.AngleRight}" />
</Style>
<Style x:Key="LblQtd" TargetType="Label">
<Setter Property="TextColor" Value="{StaticResource White}" />
<Setter Property="VerticalOptions" Value="End" />
<Setter Property="VerticalTextAlignment" Value="End" />
<Setter Property="FontSize" Value="14" />
<Setter Property="FontAttributes" Value="Bold" />
<Setter Property="Margin" Value="5,0,0,0" />
</Style>
Step 4. We’ll duplicate the Border that represents the card two more times and change the icon, variable, and text according to each game status.
![Border]()
Code
<Border Margin="0,5,0,5" Style="{StaticResource BorderCardItem}">
<Grid RowDefinitions="*,*" RowSpacing="5" Style="{StaticResource GridCardItem}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label
Grid.Row="1"
Margin="0,0,0,0"
FontSize="40"
HorizontalOptions="Start"
Style="{StaticResource LblImgIcon}"
Text="{x:Static Icons:IconFont.Gamepad}"
VerticalOptions="End" />
<Label
Grid.Row="1"
FontSize="20"
HorizontalOptions="Center"
Style="{StaticResource LblQtd}"
Text="{Binding QtdPlaying, StringFormat='{0} Playing'}"
VerticalOptions="Center" />
<Label Grid.Row="1" Style="{StaticResource ImgAngleRight}" />
</Grid>
</Border>
<Border Margin="0,5,0,5" Style="{StaticResource BorderCardItem}">
<Grid RowDefinitions="*,*" RowSpacing="5" Style="{StaticResource GridCardItem}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label
Grid.Row="1"
Margin="0,0,0,0"
FontSize="40"
HorizontalOptions="Start"
Style="{StaticResource LblImgIcon}"
Text="{x:Static Icons:IconFont.Check}"
VerticalOptions="End" />
<Label
Grid.Row="1"
FontSize="20"
HorizontalOptions="Center"
Style="{StaticResource LblQtd}"
Text="{Binding QtdPlayed, StringFormat='{0} Played'}"
VerticalOptions="Center" />
<Label Grid.Row="1" Style="{StaticResource ImgAngleRight}" />
</Grid>
</Border>
Step 5. Now we have our 3 cards representing the game statuses on the screen.
![Game statuses]()
Step 6. Let’s populate the totals. For that, we’ll create a model to handle the totals inside the Models project.
using Models.DTOs;
namespace Models
{
public record TotalGroupedByStatus
{
public int Total { get; set; }
public GameStatus? Status { get; set; }
public string?[] LastFiveIGDBIdsByUpdatedAt { get; set; } = Array.Empty<string>();
}
}
Step 7. In the Repo project, inside the GameRepo.cs class, we add the function to the class and declare it in the interface.
public List<TotalGroupedByStatus>? GetTotalsGroupedByStatusAsync(int uid)
{
using var context = DbCtx.CreateDbContext();
var result = context.Games
.Where(x => x.UserId.Equals(uid) && x.Inactive == false)
.GroupBy(x => x.Status)
.Select(x => new TotalGroupedByStatus
{
Status = x.Key,
Total = x.Count(),
LastFiveIGDBIdsByUpdatedAt = x.OrderByDescending(g => g.UpdatedAt)
.Take(5)
.Select(g => g.IGDBId.ToString())
.ToArray()
})
.AsEnumerable();
return result.ToList();
}
This way, we group the totals according to their statuses.
Step 7.1. Declare the function in the interface.
Step 8. In GameService.cs, create the function and declare it in the interface.
public List<TotalGroupedByStatus>? GetTotalsGroupedByStatus(int? uid = null)
{
int _uid = uid ?? 1;
return GameRepo.GetTotalsGroupedByStatusAsync(_uid);
}
Step 9. In MainVM.cs, let’s create a function that will be triggered every time the page comes into focus.
![MainVM]()
Function code
private readonly SemaphoreSlim SetTotalsGroupedByStatusSemaphore = new(1, 1);
public async Task SetTotalsGroupedByStatus()
{
await SetTotalsGroupedByStatusSemaphore.WaitAsync();
try
{
var totalsGroupedByStatus = gameService.GetTotalsGroupedByStatus();
if (totalsGroupedByStatus is not null && totalsGroupedByStatus.Count > 0)
{
QtdWant = totalsGroupedByStatus.FirstOrDefault(x => x.Status == GameStatus.Want)?.Total ?? 0;
QtdPlaying = totalsGroupedByStatus.FirstOrDefault(x => x.Status == GameStatus.Playing)?.Total ?? 0;
QtdPlayed = totalsGroupedByStatus.FirstOrDefault(x => x.Status == GameStatus.Played)?.Total ?? 0;
}
else
{
QtdPlayed = QtdPlaying = QtdWant = 0;
}
}
finally
{
SetTotalsGroupedByStatusSemaphore.Release();
}
}
Code to call the function on Appearing.
_ = SetTotalsGroupedByStatus();
Step 10. Now, we have our totals displayed on the main screen.
![Main Screen]()
Next part: Horizontal List of Overlapping Images in MAUI [GamesCatalog] - Part 14