mirror of
https://github.com/claunia/marechai.git
synced 2026-07-08 17:57:08 +00:00
Add Uno app software rankings for parity with Blazor
Adds a Software Rankings index page (grouped by gameplay/genre/setting/category/platform) and a ranking detail leaderboard page, reusing the already-generated Kiota rankings endpoints. Wires ranking placement chips into the software detail page and adds a sidebar entry point, mirroring the existing Blazor Rankings/RankingDetail pages.
This commit is contained in:
@@ -297,6 +297,8 @@ public partial class App : PrismApplication
|
||||
containerRegistry.Register<SoftwareViewViewModel>();
|
||||
containerRegistry.Register<SoftwareReleaseViewViewModel>();
|
||||
containerRegistry.Register<SoftwareCompilationViewViewModel>();
|
||||
containerRegistry.Register<SoftwareRankingsViewModel>();
|
||||
containerRegistry.Register<SoftwareRankingDetailViewModel>();
|
||||
containerRegistry.Register<ProfileViewModel>();
|
||||
containerRegistry.Register<AdminSoftwareViewModel>();
|
||||
containerRegistry.Register<AdminSoftwareFamiliesViewModel>();
|
||||
@@ -389,6 +391,8 @@ public partial class App : PrismApplication
|
||||
containerRegistry.RegisterForNavigation<SoftwareViewPage, SoftwareViewViewModel>();
|
||||
containerRegistry.RegisterForNavigation<SoftwareReleaseViewPage, SoftwareReleaseViewViewModel>();
|
||||
containerRegistry.RegisterForNavigation<SoftwareCompilationViewPage, SoftwareCompilationViewViewModel>();
|
||||
containerRegistry.RegisterForNavigation<SoftwareRankingsPage, SoftwareRankingsViewModel>();
|
||||
containerRegistry.RegisterForNavigation<SoftwareRankingDetailPage, SoftwareRankingDetailViewModel>();
|
||||
containerRegistry.RegisterForNavigation<ProfilePage, ProfileViewModel>();
|
||||
containerRegistry.RegisterForNavigation<AdminSoftwarePage, AdminSoftwareViewModel>();
|
||||
containerRegistry.RegisterForNavigation<AdminSoftwareFamiliesPage, AdminSoftwareFamiliesViewModel>();
|
||||
|
||||
@@ -18,5 +18,6 @@
|
||||
<local:CountToVisibilityConverter x:Key="CountToVisibilityConverter" />
|
||||
<local:NullableToVisibilityConverter x:Key="NullableToVisibilityConverter" />
|
||||
<local:BoolToInfoBarSeverityConverter x:Key="BoolToInfoBarSeverityConverter" />
|
||||
<local:MarechaiScoreToBrushConverter x:Key="MarechaiScoreToBrushConverter" />
|
||||
|
||||
</ResourceDictionary>
|
||||
|
||||
@@ -30,6 +30,7 @@ public static class NavParamKeys
|
||||
public const string ScreenshotId = "ScreenshotId";
|
||||
public const string CoverId = "CoverId";
|
||||
public const string SoftwarePromoArtId = "SoftwarePromoArtId";
|
||||
public const string RankingId = "RankingId";
|
||||
public const string WwpcImportId = "WwpcImportId";
|
||||
public const string SearchQuery = "SearchQuery";
|
||||
public const string MachineFamilyId = "MachineFamilyId";
|
||||
|
||||
@@ -257,6 +257,18 @@
|
||||
BorderThickness="0"
|
||||
CornerRadius="0" />
|
||||
|
||||
<!-- Software Rankings -->
|
||||
<Button Content="{Binding RankingsButtonText}"
|
||||
Command="{Binding NavigateToRankingsCommand}"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Left"
|
||||
Padding="16,10,16,10"
|
||||
FontSize="13"
|
||||
Background="Transparent"
|
||||
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
|
||||
BorderThickness="0"
|
||||
CornerRadius="0" />
|
||||
|
||||
<!-- Sound Synthesizers -->
|
||||
<Button Content="{Binding SoundSynthesizersButtonText}"
|
||||
Command="{Binding NavigateToSoundSynthesizersCommand}"
|
||||
|
||||
@@ -163,6 +163,33 @@ public class NullableToVisibilityConverter : IValueConverter
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps a 0-10 Marechai score to a themed brush (>=8 success, >=6 info, >=4 warning, otherwise neutral).
|
||||
/// </summary>
|
||||
public class MarechaiScoreToBrushConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
double? score = value switch
|
||||
{
|
||||
double d => d,
|
||||
float f => f,
|
||||
_ => null
|
||||
};
|
||||
|
||||
return score switch
|
||||
{
|
||||
>= 8 => new Microsoft.UI.Xaml.Media.SolidColorBrush(Microsoft.UI.Colors.Green),
|
||||
>= 6 => new Microsoft.UI.Xaml.Media.SolidColorBrush(Microsoft.UI.Colors.DodgerBlue),
|
||||
>= 4 => new Microsoft.UI.Xaml.Media.SolidColorBrush(Microsoft.UI.Colors.Orange),
|
||||
_ => new Microsoft.UI.Xaml.Media.SolidColorBrush(Microsoft.UI.Colors.Gray)
|
||||
};
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language) =>
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps success/failure booleans to InfoBar severity.
|
||||
/// </summary>
|
||||
|
||||
@@ -59,6 +59,7 @@ public partial class MainViewModel : ObservableObject
|
||||
public string PeopleButtonText => _localizer["PeopleButton"];
|
||||
public string ProcessorsButtonText => _localizer["ProcessorsButton"];
|
||||
public string SoftwareButtonText => _localizer["SoftwareButton"];
|
||||
public string RankingsButtonText => _localizer["RankingsButton"];
|
||||
public string SoundSynthesizersButtonText => _localizer["SoundSynthesizersButton"];
|
||||
public string MessagesButtonText => "Messages";
|
||||
public string UserManagementButtonText => _localizer["UserManagementButton"];
|
||||
@@ -136,6 +137,7 @@ public partial class MainViewModel : ObservableObject
|
||||
NavigateToPeopleCommand = new RelayCommand(() => NavigateTo(nameof(PeoplePage)));
|
||||
NavigateToProcessorsCommand = new RelayCommand(() => NavigateTo(nameof(ProcessorListPage)));
|
||||
NavigateToSoftwareCommand = new RelayCommand(() => NavigateTo(nameof(SoftwarePage)));
|
||||
NavigateToRankingsCommand = new RelayCommand(() => NavigateTo(nameof(SoftwareRankingsPage)));
|
||||
NavigateToSoundSynthesizersCommand = new RelayCommand(() => NavigateTo(nameof(SoundSynthListPage)));
|
||||
NavigateToMessagesCommand = new RelayCommand(() => NavigateTo(nameof(MessagesPage)));
|
||||
NavigateToUsersCommand = new RelayCommand(() => NavigateTo(nameof(UsersPage)));
|
||||
@@ -204,6 +206,7 @@ public partial class MainViewModel : ObservableObject
|
||||
public ICommand NavigateToPeopleCommand { get; }
|
||||
public ICommand NavigateToProcessorsCommand { get; }
|
||||
public ICommand NavigateToSoftwareCommand { get; }
|
||||
public ICommand NavigateToRankingsCommand { get; }
|
||||
public ICommand NavigateToSoundSynthesizersCommand { get; }
|
||||
public ICommand NavigateToMessagesCommand { get; }
|
||||
public ICommand NavigateToUsersCommand { get; }
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using Marechai.ApiClient.Models;
|
||||
using Marechai.App.Navigation;
|
||||
using Marechai.App.Presentation.Views;
|
||||
using Marechai.App.Services;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
|
||||
namespace Marechai.App.Presentation.ViewModels;
|
||||
|
||||
[Bindable]
|
||||
public partial class SoftwareRankingDetailViewModel : ObservableObject, IRegionAware
|
||||
{
|
||||
private readonly SoftwareBrowsingService _browsingService;
|
||||
private readonly IStringLocalizer _localizer;
|
||||
private readonly ILogger<SoftwareRankingDetailViewModel> _logger;
|
||||
private readonly IRegionManager _regionManager;
|
||||
|
||||
private int _rankingId;
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<SoftwareRankingDto> _entries = [];
|
||||
|
||||
[ObservableProperty]
|
||||
private string _errorMessage = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _hasError;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isDataLoaded;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isLoading;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isEmpty;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _pageTitle = string.Empty;
|
||||
|
||||
public SoftwareRankingDetailViewModel(SoftwareBrowsingService browsingService,
|
||||
IStringLocalizer localizer,
|
||||
ILogger<SoftwareRankingDetailViewModel> logger,
|
||||
IRegionManager regionManager)
|
||||
{
|
||||
_browsingService = browsingService;
|
||||
_localizer = localizer;
|
||||
_logger = logger;
|
||||
_regionManager = regionManager;
|
||||
LoadData = new AsyncRelayCommand(LoadDataAsync);
|
||||
GoBackCommand = new AsyncRelayCommand(GoBackAsync);
|
||||
NavigateToSoftwareCommand = new AsyncRelayCommand<SoftwareRankingDto>(NavigateToSoftwareAsync);
|
||||
}
|
||||
|
||||
public IAsyncRelayCommand LoadData { get; }
|
||||
public ICommand GoBackCommand { get; }
|
||||
public IAsyncRelayCommand<SoftwareRankingDto> NavigateToSoftwareCommand { get; }
|
||||
|
||||
public bool IsNavigationTarget(NavigationContext navigationContext) => false;
|
||||
|
||||
public void OnNavigatedFrom(NavigationContext navigationContext) { }
|
||||
|
||||
public void OnNavigatedTo(NavigationContext navigationContext)
|
||||
{
|
||||
if(navigationContext.Parameters.TryGetValue<int>(NavParamKeys.RankingId, out int rankingId))
|
||||
{
|
||||
_rankingId = rankingId;
|
||||
_ = LoadDataAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadDataAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
ErrorMessage = string.Empty;
|
||||
HasError = false;
|
||||
IsDataLoaded = false;
|
||||
IsEmpty = false;
|
||||
Entries.Clear();
|
||||
|
||||
await LoadTitleAsync();
|
||||
|
||||
System.Collections.Generic.List<SoftwareRankingDto> entries =
|
||||
await _browsingService.GetRankingAsync(_rankingId);
|
||||
|
||||
foreach(SoftwareRankingDto entry in entries) Entries.Add(entry);
|
||||
|
||||
if(Entries.Count == 0)
|
||||
IsEmpty = true;
|
||||
else
|
||||
IsDataLoaded = true;
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error loading ranking {RankingId}", _rankingId);
|
||||
ErrorMessage = _localizer["Failed to load ranking. Please try again later."].Value;
|
||||
HasError = true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadTitleAsync()
|
||||
{
|
||||
RankingIndexResponseDto? index = await _browsingService.GetRankingsIndexAsync();
|
||||
|
||||
RankingIndexEntryDto? entry = index?.Rankings?.FirstOrDefault(r => r.Id == _rankingId);
|
||||
|
||||
PageTitle = entry?.Dimension == 0
|
||||
? _localizer["Top250SoftwareLabel"]
|
||||
: entry?.DimensionName ?? _localizer["RankingsTitle"];
|
||||
}
|
||||
|
||||
private Task GoBackAsync()
|
||||
{
|
||||
_regionManager.RequestNavigate(RegionNames.Content, nameof(SoftwareRankingsPage));
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task NavigateToSoftwareAsync(SoftwareRankingDto? entry)
|
||||
{
|
||||
if(entry?.SoftwareId is null) return Task.CompletedTask;
|
||||
|
||||
var parameters = new NavigationParameters
|
||||
{
|
||||
{ NavParamKeys.SoftwareId, entry.SoftwareId.Value },
|
||||
{ NavParamKeys.NavigationSource, nameof(SoftwareRankingDetailViewModel) }
|
||||
};
|
||||
|
||||
_regionManager.RequestNavigate(RegionNames.Content, nameof(SoftwareViewPage), parameters);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using Marechai.ApiClient.Models;
|
||||
using Marechai.App.Navigation;
|
||||
using Marechai.App.Presentation.Views;
|
||||
using Marechai.App.Services;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
|
||||
namespace Marechai.App.Presentation.ViewModels;
|
||||
|
||||
[Bindable]
|
||||
public partial class SoftwareRankingsViewModel : ObservableObject, IRegionAware
|
||||
{
|
||||
private readonly SoftwareBrowsingService _browsingService;
|
||||
private readonly IStringLocalizer _localizer;
|
||||
private readonly ILogger<SoftwareRankingsViewModel> _logger;
|
||||
private readonly IRegionManager _regionManager;
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<RankingGroup> _rankingGroups = [];
|
||||
|
||||
[ObservableProperty]
|
||||
private RankingIndexEntryDto? _overallRanking;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _errorMessage = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _hasError;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isDataLoaded;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isLoading;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _statusText = string.Empty;
|
||||
|
||||
public SoftwareRankingsViewModel(SoftwareBrowsingService browsingService,
|
||||
IStringLocalizer localizer,
|
||||
ILogger<SoftwareRankingsViewModel> logger,
|
||||
IRegionManager regionManager)
|
||||
{
|
||||
_browsingService = browsingService;
|
||||
_localizer = localizer;
|
||||
_logger = logger;
|
||||
_regionManager = regionManager;
|
||||
LoadData = new AsyncRelayCommand(LoadDataAsync);
|
||||
GoBackCommand = new AsyncRelayCommand(GoBackAsync);
|
||||
NavigateToRankingCommand = new AsyncRelayCommand<RankingIndexEntryDto>(NavigateToRankingAsync);
|
||||
}
|
||||
|
||||
public IAsyncRelayCommand LoadData { get; }
|
||||
public ICommand GoBackCommand { get; }
|
||||
public IAsyncRelayCommand<RankingIndexEntryDto> NavigateToRankingCommand { get; }
|
||||
|
||||
public bool IsNavigationTarget(NavigationContext navigationContext) => false;
|
||||
|
||||
public void OnNavigatedFrom(NavigationContext navigationContext) { }
|
||||
|
||||
public void OnNavigatedTo(NavigationContext navigationContext)
|
||||
{
|
||||
_ = LoadDataAsync();
|
||||
}
|
||||
|
||||
private async Task LoadDataAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
ErrorMessage = string.Empty;
|
||||
HasError = false;
|
||||
IsDataLoaded = false;
|
||||
RankingGroups.Clear();
|
||||
OverallRanking = null;
|
||||
|
||||
RankingIndexResponseDto? response = await _browsingService.GetRankingsIndexAsync();
|
||||
|
||||
if(response?.Rankings is null || response.Rankings.Count == 0)
|
||||
{
|
||||
ErrorMessage = _localizer["No rankings found"].Value;
|
||||
HasError = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateStatusText(response.Status);
|
||||
|
||||
OverallRanking = response.Rankings.FirstOrDefault(r => r.Dimension == 0);
|
||||
|
||||
List<RankingIndexEntryDto> byGenre = response.Rankings.Where(r => r.Dimension == 1).ToList();
|
||||
List<RankingIndexEntryDto> byPlatform = response.Rankings.Where(r => r.Dimension == 2).ToList();
|
||||
|
||||
AddGenreGroup(byGenre, genreType: 2, _localizer["By Gameplay"]);
|
||||
AddGenreGroup(byGenre, genreType: 0, _localizer["By Genre"]);
|
||||
AddGenreGroup(byGenre, genreType: 3, _localizer["By Setting"]);
|
||||
AddGenreGroup(byGenre, genreType: 4, _localizer["By Category"]);
|
||||
|
||||
if(byPlatform.Count > 0)
|
||||
{
|
||||
RankingGroups.Add(new RankingGroup
|
||||
{
|
||||
Title = _localizer["By Platform"],
|
||||
Entries = new ObservableCollection<RankingIndexEntryDto>(
|
||||
byPlatform.OrderBy(r => r.DimensionName))
|
||||
});
|
||||
}
|
||||
|
||||
IsDataLoaded = true;
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error loading rankings index");
|
||||
ErrorMessage = _localizer["Failed to load rankings. Please try again later."].Value;
|
||||
HasError = true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void AddGenreGroup(List<RankingIndexEntryDto> byGenre, byte genreType, string title)
|
||||
{
|
||||
List<RankingIndexEntryDto> entries = byGenre.Where(r => r.GenreType == genreType)
|
||||
.OrderBy(r => r.DimensionName)
|
||||
.ToList();
|
||||
|
||||
if(entries.Count == 0) return;
|
||||
|
||||
RankingGroups.Add(new RankingGroup
|
||||
{
|
||||
Title = title,
|
||||
Entries = new ObservableCollection<RankingIndexEntryDto>(entries)
|
||||
});
|
||||
}
|
||||
|
||||
private void UpdateStatusText(RankingsStatusDto? status)
|
||||
{
|
||||
if(status is null) return;
|
||||
|
||||
StatusText = status.IsComputing == true
|
||||
? _localizer["Rankings are being computed"]
|
||||
: status.LastComputedAt.HasValue
|
||||
? string.Format(_localizer["Last computed: {0}"], status.LastComputedAt.Value.LocalDateTime)
|
||||
: string.Empty;
|
||||
}
|
||||
|
||||
private Task GoBackAsync()
|
||||
{
|
||||
_regionManager.RequestNavigate(RegionNames.Content, nameof(SoftwarePage));
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task NavigateToRankingAsync(RankingIndexEntryDto? entry)
|
||||
{
|
||||
if(entry?.Id is null) return Task.CompletedTask;
|
||||
|
||||
var parameters = new NavigationParameters
|
||||
{
|
||||
{ NavParamKeys.RankingId, entry.Id.Value },
|
||||
{ NavParamKeys.NavigationSource, nameof(SoftwareRankingsViewModel) }
|
||||
};
|
||||
|
||||
_regionManager.RequestNavigate(RegionNames.Content, nameof(SoftwareRankingDetailPage), parameters);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
public class RankingGroup
|
||||
{
|
||||
public string Title { get; set; } = string.Empty;
|
||||
|
||||
public ObservableCollection<RankingIndexEntryDto> Entries { get; set; } = [];
|
||||
}
|
||||
@@ -45,6 +45,9 @@ public partial class SoftwareViewViewModel : ObservableObject, IRegionAware
|
||||
private string? _currentUserId;
|
||||
private SoftwareUserReviewDto? _myReview;
|
||||
|
||||
[ObservableProperty]
|
||||
private ObservableCollection<SoftwareRankingPlacementDto> _rankingPlacements = [];
|
||||
|
||||
[ObservableProperty]
|
||||
private string _softwareName = string.Empty;
|
||||
|
||||
@@ -651,6 +654,9 @@ public partial class SoftwareViewViewModel : ObservableObject, IRegionAware
|
||||
_logger.LogError("Failed to load software description: {Exception}", ex.Message);
|
||||
}
|
||||
|
||||
// Load ranking placements
|
||||
await LoadRankingPlacementsAsync(softwareId);
|
||||
|
||||
UpdateVisibilities();
|
||||
IsDataLoaded = true;
|
||||
IsLoading = false;
|
||||
@@ -664,6 +670,39 @@ public partial class SoftwareViewViewModel : ObservableObject, IRegionAware
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadRankingPlacementsAsync(int softwareId)
|
||||
{
|
||||
try
|
||||
{
|
||||
List<SoftwareRankingPlacementDto> placements =
|
||||
await _browsingService.GetRankingPlacementsAsync(softwareId);
|
||||
|
||||
RankingPlacements.Clear();
|
||||
|
||||
foreach(SoftwareRankingPlacementDto placement in placements) RankingPlacements.Add(placement);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error loading ranking placements for software {SoftwareId}", softwareId);
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public Task NavigateToRanking(SoftwareRankingPlacementDto? placement)
|
||||
{
|
||||
if(placement?.RankingId is null) return Task.CompletedTask;
|
||||
|
||||
var parameters = new NavigationParameters
|
||||
{
|
||||
{ NavParamKeys.RankingId, placement.RankingId.Value },
|
||||
{ NavParamKeys.NavigationSource, nameof(SoftwareViewViewModel) }
|
||||
};
|
||||
|
||||
_regionManager.RequestNavigate(RegionNames.Content, nameof(SoftwareRankingDetailPage), parameters);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public Task ViewScreenshot(ScreenshotDisplayItem? item)
|
||||
{
|
||||
|
||||
202
Marechai.App/Presentation/Views/SoftwareRankingDetailPage.xaml
Normal file
202
Marechai.App/Presentation/Views/SoftwareRankingDetailPage.xaml
Normal file
@@ -0,0 +1,202 @@
|
||||
<?xml version="1.0"
|
||||
encoding="utf-8"?>
|
||||
|
||||
<Page x:Class="Marechai.App.Presentation.Views.SoftwareRankingDetailPage"
|
||||
x:Name="PageRoot"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:loc="using:Marechai.App.Presentation.Converters"
|
||||
NavigationCacheMode="Required"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
|
||||
<Grid RowSpacing="0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" /> <RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Header with Back Button and Title -->
|
||||
<Grid Grid.Row="0"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
BorderBrush="{ThemeResource DividerStrokeColorDefaultBrush}"
|
||||
BorderThickness="0,0,0,1"
|
||||
Padding="16,12,16,12">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Button Grid.Column="0"
|
||||
Command="{Binding GoBackCommand}"
|
||||
Style="{ThemeResource AlternateButtonStyle}"
|
||||
ToolTipService.ToolTip="Go back"
|
||||
Padding="8"
|
||||
MinWidth="44"
|
||||
MinHeight="44"
|
||||
VerticalAlignment="Center">
|
||||
<FontIcon Glyph=""
|
||||
FontSize="16" />
|
||||
</Button>
|
||||
|
||||
<TextBlock Grid.Column="1"
|
||||
Text="{Binding PageTitle}"
|
||||
Margin="16,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="20"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource TextControlForeground}" />
|
||||
</Grid>
|
||||
|
||||
<!-- Main Content -->
|
||||
<Grid Grid.Row="1">
|
||||
|
||||
<!-- Loading State -->
|
||||
<StackPanel Visibility="{Binding IsLoading}"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center"
|
||||
Padding="32"
|
||||
Spacing="16">
|
||||
<ProgressRing IsActive="True"
|
||||
IsIndeterminate="True"
|
||||
Height="64"
|
||||
Width="64"
|
||||
Foreground="{ThemeResource SystemAccentColor}" />
|
||||
<TextBlock Text="{Binding Source={StaticResource Strings}, Path=LoadingText}"
|
||||
FontSize="14"
|
||||
TextAlignment="Center"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Error State -->
|
||||
<StackPanel Visibility="{Binding HasError}"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center"
|
||||
Padding="24"
|
||||
Spacing="16"
|
||||
MaxWidth="400">
|
||||
<InfoBar IsOpen="True"
|
||||
Severity="Error"
|
||||
Title="{Binding Source={StaticResource Strings}, Path=SoftwareErrorInfoBar_Title}"
|
||||
Message="{Binding ErrorMessage}"
|
||||
IsClosable="False" />
|
||||
<Button Content="{Binding Source={StaticResource Strings}, Path=RetryButton}"
|
||||
Command="{Binding LoadData}"
|
||||
HorizontalAlignment="Center"
|
||||
Style="{ThemeResource AccentButtonStyle}" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Empty State -->
|
||||
<StackPanel Visibility="{Binding IsEmpty}"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center"
|
||||
Padding="24"
|
||||
Spacing="8">
|
||||
<TextBlock Text="{Binding Source={StaticResource Strings}, Path=NoRankedSoftwareLabel}"
|
||||
FontSize="14"
|
||||
TextAlignment="Center"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Ranking List -->
|
||||
<Grid Visibility="{Binding IsDataLoaded}">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto"
|
||||
HorizontalScrollBarVisibility="Disabled">
|
||||
<ItemsControl Padding="8"
|
||||
ItemsSource="{Binding Entries}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Vertical"
|
||||
Spacing="0"
|
||||
HorizontalAlignment="Stretch" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Button Padding="0"
|
||||
Margin="0,0,0,8"
|
||||
MinHeight="76"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
VerticalContentAlignment="Stretch"
|
||||
HorizontalAlignment="Stretch"
|
||||
Command="{Binding DataContext.NavigateToSoftwareCommand, ElementName=PageRoot}"
|
||||
CommandParameter="{Binding}"
|
||||
Background="Transparent"
|
||||
BorderThickness="0">
|
||||
<Border Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="8"
|
||||
Padding="16,12">
|
||||
<Grid ColumnSpacing="16">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Rank -->
|
||||
<Border Grid.Column="0"
|
||||
Width="40"
|
||||
Height="40"
|
||||
CornerRadius="20"
|
||||
Background="{ThemeResource CardBackgroundFillColorSecondaryBrush}"
|
||||
VerticalAlignment="Center">
|
||||
<TextBlock Text="{Binding Rank}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="14"
|
||||
FontWeight="Bold" />
|
||||
</Border>
|
||||
|
||||
<!-- Name / Family -->
|
||||
<StackPanel Grid.Column="1"
|
||||
Spacing="4"
|
||||
VerticalAlignment="Center">
|
||||
<TextBlock Text="{Binding Name}"
|
||||
FontSize="16"
|
||||
FontWeight="SemiBold"
|
||||
TextTrimming="CharacterEllipsis" />
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Spacing="12">
|
||||
<TextBlock Text="{Binding Family}"
|
||||
FontSize="13"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}"
|
||||
Visibility="{Binding Family, Converter={StaticResource NullToVisibilityConverter}}" />
|
||||
<TextBlock FontSize="12"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}"
|
||||
Visibility="{Binding CriticReviewCount, Converter={StaticResource CountToVisibilityConverter}}">
|
||||
<Run Text="{Binding CriticReviewCount}" />
|
||||
<Run Text="{Binding Source={StaticResource Strings}, Path=CriticReviewsCountLabel}" />
|
||||
</TextBlock>
|
||||
<TextBlock FontSize="12"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}"
|
||||
Visibility="{Binding UserRatingCount, Converter={StaticResource CountToVisibilityConverter}}">
|
||||
<Run Text="{Binding UserRatingCount}" />
|
||||
<Run Text="{Binding Source={StaticResource Strings}, Path=UserRatingsCountLabel}" />
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Marechai Score -->
|
||||
<Border Grid.Column="2"
|
||||
CornerRadius="12"
|
||||
Padding="10,4"
|
||||
VerticalAlignment="Center"
|
||||
Background="{Binding MarechaiScore, Converter={StaticResource MarechaiScoreToBrushConverter}}">
|
||||
<TextBlock Text="{Binding MarechaiScore}"
|
||||
FontSize="14"
|
||||
FontWeight="Bold"
|
||||
Foreground="White" />
|
||||
</Border>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
|
||||
</Page>
|
||||
@@ -0,0 +1,13 @@
|
||||
#nullable enable
|
||||
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace Marechai.App.Presentation.Views;
|
||||
|
||||
public sealed partial class SoftwareRankingDetailPage : Page
|
||||
{
|
||||
public SoftwareRankingDetailPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
190
Marechai.App/Presentation/Views/SoftwareRankingsPage.xaml
Normal file
190
Marechai.App/Presentation/Views/SoftwareRankingsPage.xaml
Normal file
@@ -0,0 +1,190 @@
|
||||
<?xml version="1.0"
|
||||
encoding="utf-8"?>
|
||||
|
||||
<Page x:Class="Marechai.App.Presentation.Views.SoftwareRankingsPage"
|
||||
x:Name="PageRoot"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:loc="using:Marechai.App.Presentation.Converters"
|
||||
NavigationCacheMode="Required"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
|
||||
<Grid RowSpacing="0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" /> <RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Header with Back Button and Title -->
|
||||
<Grid Grid.Row="0"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
BorderBrush="{ThemeResource DividerStrokeColorDefaultBrush}"
|
||||
BorderThickness="0,0,0,1"
|
||||
Padding="16,12,16,12">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Button Grid.Column="0"
|
||||
Command="{Binding GoBackCommand}"
|
||||
Style="{ThemeResource AlternateButtonStyle}"
|
||||
ToolTipService.ToolTip="Go back"
|
||||
Padding="8"
|
||||
MinWidth="44"
|
||||
MinHeight="44"
|
||||
VerticalAlignment="Center">
|
||||
<FontIcon Glyph=""
|
||||
FontSize="16" />
|
||||
</Button>
|
||||
|
||||
<StackPanel Grid.Column="1"
|
||||
Margin="16,0,0,0"
|
||||
VerticalAlignment="Center">
|
||||
<TextBlock Text="{Binding Source={StaticResource Strings}, Path=RankingsTitle}"
|
||||
FontSize="20"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource TextControlForeground}" />
|
||||
<TextBlock Text="{Binding StatusText}"
|
||||
FontSize="12"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}"
|
||||
Margin="0,4,0,0"
|
||||
Visibility="{Binding StatusText, Converter={StaticResource StringToVisibilityConverter}}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<!-- Main Content -->
|
||||
<Grid Grid.Row="1">
|
||||
|
||||
<!-- Loading State -->
|
||||
<StackPanel Visibility="{Binding IsLoading}"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center"
|
||||
Padding="32"
|
||||
Spacing="16">
|
||||
<ProgressRing IsActive="True"
|
||||
IsIndeterminate="True"
|
||||
Height="64"
|
||||
Width="64"
|
||||
Foreground="{ThemeResource SystemAccentColor}" />
|
||||
<TextBlock Text="{Binding Source={StaticResource Strings}, Path=LoadingText}"
|
||||
FontSize="14"
|
||||
TextAlignment="Center"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Error State -->
|
||||
<StackPanel Visibility="{Binding HasError}"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center"
|
||||
Padding="24"
|
||||
Spacing="16"
|
||||
MaxWidth="400">
|
||||
<InfoBar IsOpen="True"
|
||||
Severity="Error"
|
||||
Title="{Binding Source={StaticResource Strings}, Path=SoftwareErrorInfoBar_Title}"
|
||||
Message="{Binding ErrorMessage}"
|
||||
IsClosable="False" />
|
||||
<Button Content="{Binding Source={StaticResource Strings}, Path=RetryButton}"
|
||||
Command="{Binding LoadData}"
|
||||
HorizontalAlignment="Center"
|
||||
Style="{ThemeResource AccentButtonStyle}" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Rankings -->
|
||||
<Grid Visibility="{Binding IsDataLoaded}">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto"
|
||||
HorizontalScrollBarVisibility="Disabled">
|
||||
<StackPanel Padding="16"
|
||||
Spacing="24">
|
||||
|
||||
<!-- Overall / Top 250 -->
|
||||
<Button Padding="16,12"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Left"
|
||||
Command="{Binding NavigateToRankingCommand}"
|
||||
CommandParameter="{Binding OverallRanking}"
|
||||
Visibility="{Binding OverallRanking, Converter={StaticResource NullToVisibilityConverter}}"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="8">
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Spacing="12">
|
||||
<FontIcon Glyph=""
|
||||
FontSize="22"
|
||||
Foreground="{ThemeResource SystemAccentColor}" />
|
||||
<StackPanel Spacing="2">
|
||||
<TextBlock Text="{Binding Source={StaticResource Strings}, Path=Top250SoftwareLabel}"
|
||||
FontSize="16"
|
||||
FontWeight="SemiBold" />
|
||||
<TextBlock Text="{Binding OverallRanking.EntryCount}"
|
||||
FontSize="12"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
|
||||
<!-- Grouped dimensions -->
|
||||
<ItemsControl ItemsSource="{Binding RankingGroups}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Vertical"
|
||||
Spacing="16" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Spacing="8">
|
||||
<TextBlock Text="{Binding Title}"
|
||||
FontSize="16"
|
||||
FontWeight="SemiBold"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
<ItemsControl ItemsSource="{Binding Entries}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Vertical"
|
||||
Spacing="6" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Button Padding="12,8"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Left"
|
||||
Command="{Binding DataContext.NavigateToRankingCommand, ElementName=PageRoot}"
|
||||
CommandParameter="{Binding}"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="6">
|
||||
<Grid ColumnSpacing="8">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0"
|
||||
Text="{Binding DimensionName}"
|
||||
FontSize="14"
|
||||
TextTrimming="CharacterEllipsis" />
|
||||
<TextBlock Grid.Column="1"
|
||||
Text="{Binding EntryCount}"
|
||||
FontSize="12"
|
||||
Foreground="{ThemeResource SystemBaseMediumColor}" />
|
||||
</Grid>
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
|
||||
</Page>
|
||||
13
Marechai.App/Presentation/Views/SoftwareRankingsPage.xaml.cs
Normal file
13
Marechai.App/Presentation/Views/SoftwareRankingsPage.xaml.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
#nullable enable
|
||||
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace Marechai.App.Presentation.Views;
|
||||
|
||||
public sealed partial class SoftwareRankingsPage : Page
|
||||
{
|
||||
public SoftwareRankingsPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -198,6 +198,40 @@
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Ranking Placements -->
|
||||
<ItemsControl ItemsSource="{Binding RankingPlacements}"
|
||||
Visibility="{Binding RankingPlacements.Count, Converter={StaticResource CountToVisibilityConverter}}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Spacing="8" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Button Padding="10,4"
|
||||
Command="{Binding DataContext.NavigateToRankingCommand, ElementName=PageRoot}"
|
||||
CommandParameter="{Binding}"
|
||||
Background="{ThemeResource CardBackgroundFillColorSecondaryBrush}"
|
||||
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="12">
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Spacing="6">
|
||||
<FontIcon Glyph=""
|
||||
FontSize="12"
|
||||
Foreground="{ThemeResource SystemAccentColor}" />
|
||||
<TextBlock FontSize="12">
|
||||
<Run Text="#" />
|
||||
<Run Text="{Binding Rank}" />
|
||||
<Run Text="{Binding DimensionName}" />
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
<!-- Description -->
|
||||
<StackPanel Visibility="{Binding ShowDescription}"
|
||||
Spacing="8">
|
||||
|
||||
@@ -107,6 +107,9 @@
|
||||
<data name="SoftwareButton" xml:space="preserve">
|
||||
<value>Software</value>
|
||||
</data>
|
||||
<data name="RankingsButton" xml:space="preserve">
|
||||
<value>Ranglisten</value>
|
||||
</data>
|
||||
<data name="SoundSynthesizersButton" xml:space="preserve">
|
||||
<value>Soundsynthesizer</value>
|
||||
</data>
|
||||
@@ -2546,4 +2549,19 @@
|
||||
<data name="ChangePasswordPage.Error.Mismatch" xml:space="preserve"><value>Die Passwörter stimmen nicht überein.</value></data>
|
||||
<data name="ChangePasswordPage.Error.Generic" xml:space="preserve"><value>Das Passwort konnte nicht geändert werden.</value></data>
|
||||
<data name="ChangePasswordPage.Success" xml:space="preserve"><value>Passwort erfolgreich geändert.</value></data>
|
||||
<data name="RankingsTitle" xml:space="preserve"><value>Ranglisten</value></data>
|
||||
<data name="Top250SoftwareLabel" xml:space="preserve"><value>Top 250 Software aller Zeiten</value></data>
|
||||
<data name="NoRankedSoftwareLabel" xml:space="preserve"><value>Noch keine bewertete Software.</value></data>
|
||||
<data name="CriticReviewsCountLabel" xml:space="preserve"><value>Kritikerbewertungen</value></data>
|
||||
<data name="UserRatingsCountLabel" xml:space="preserve"><value>Nutzerbewertungen</value></data>
|
||||
<data name="No rankings found" xml:space="preserve"><value>Keine Ranglisten gefunden</value></data>
|
||||
<data name="Rankings are being computed" xml:space="preserve"><value>Ranglisten werden berechnet</value></data>
|
||||
<data name="Last computed: {0}" xml:space="preserve"><value>Zuletzt berechnet: {0}</value></data>
|
||||
<data name="Failed to load rankings. Please try again later." xml:space="preserve"><value>Ranglisten konnten nicht geladen werden. Bitte versuchen Sie es später erneut.</value></data>
|
||||
<data name="Failed to load ranking. Please try again later." xml:space="preserve"><value>Rangliste konnte nicht geladen werden. Bitte versuchen Sie es später erneut.</value></data>
|
||||
<data name="By Gameplay" xml:space="preserve"><value>Nach Gameplay</value></data>
|
||||
<data name="By Genre" xml:space="preserve"><value>Nach Genre</value></data>
|
||||
<data name="By Setting" xml:space="preserve"><value>Nach Setting</value></data>
|
||||
<data name="By Category" xml:space="preserve"><value>Nach Kategorie</value></data>
|
||||
<data name="By Platform" xml:space="preserve"><value>Nach Plattform</value></data>
|
||||
</root>
|
||||
|
||||
@@ -107,6 +107,9 @@
|
||||
<data name="SoftwareButton" xml:space="preserve">
|
||||
<value>Software</value>
|
||||
</data>
|
||||
<data name="RankingsButton" xml:space="preserve">
|
||||
<value>Clasificaciones</value>
|
||||
</data>
|
||||
<data name="SoundSynthesizersButton" xml:space="preserve">
|
||||
<value>Sintetizadores de Sonido</value>
|
||||
</data>
|
||||
@@ -2686,4 +2689,19 @@
|
||||
<data name="ChangePasswordPage.Error.Mismatch" xml:space="preserve"><value>Las contraseñas no coinciden.</value></data>
|
||||
<data name="ChangePasswordPage.Error.Generic" xml:space="preserve"><value>No se pudo cambiar la contraseña.</value></data>
|
||||
<data name="ChangePasswordPage.Success" xml:space="preserve"><value>Contraseña cambiada correctamente.</value></data>
|
||||
<data name="RankingsTitle" xml:space="preserve"><value>Clasificaciones</value></data>
|
||||
<data name="Top250SoftwareLabel" xml:space="preserve"><value>Los 250 mejores programas de todos los tiempos</value></data>
|
||||
<data name="NoRankedSoftwareLabel" xml:space="preserve"><value>Todavía no hay software clasificado.</value></data>
|
||||
<data name="CriticReviewsCountLabel" xml:space="preserve"><value>reseñas de críticos</value></data>
|
||||
<data name="UserRatingsCountLabel" xml:space="preserve"><value>valoraciones de usuarios</value></data>
|
||||
<data name="No rankings found" xml:space="preserve"><value>No se encontraron clasificaciones</value></data>
|
||||
<data name="Rankings are being computed" xml:space="preserve"><value>Las clasificaciones se están calculando</value></data>
|
||||
<data name="Last computed: {0}" xml:space="preserve"><value>Última actualización: {0}</value></data>
|
||||
<data name="Failed to load rankings. Please try again later." xml:space="preserve"><value>No se pudieron cargar las clasificaciones. Inténtelo de nuevo más tarde.</value></data>
|
||||
<data name="Failed to load ranking. Please try again later." xml:space="preserve"><value>No se pudo cargar la clasificación. Inténtelo de nuevo más tarde.</value></data>
|
||||
<data name="By Gameplay" xml:space="preserve"><value>Por jugabilidad</value></data>
|
||||
<data name="By Genre" xml:space="preserve"><value>Por género</value></data>
|
||||
<data name="By Setting" xml:space="preserve"><value>Por ambientación</value></data>
|
||||
<data name="By Category" xml:space="preserve"><value>Por categoría</value></data>
|
||||
<data name="By Platform" xml:space="preserve"><value>Por plataforma</value></data>
|
||||
</root>
|
||||
|
||||
@@ -107,6 +107,9 @@
|
||||
<data name="SoftwareButton" xml:space="preserve">
|
||||
<value>Logiciels</value>
|
||||
</data>
|
||||
<data name="RankingsButton" xml:space="preserve">
|
||||
<value>Classements</value>
|
||||
</data>
|
||||
<data name="SoundSynthesizersButton" xml:space="preserve">
|
||||
<value>Synthétiseurs de Son</value>
|
||||
</data>
|
||||
@@ -2650,4 +2653,19 @@
|
||||
<data name="ChangePasswordPage.Error.Mismatch" xml:space="preserve"><value>Les mots de passe ne correspondent pas.</value></data>
|
||||
<data name="ChangePasswordPage.Error.Generic" xml:space="preserve"><value>Impossible de changer le mot de passe.</value></data>
|
||||
<data name="ChangePasswordPage.Success" xml:space="preserve"><value>Mot de passe changé avec succès.</value></data>
|
||||
<data name="RankingsTitle" xml:space="preserve"><value>Classements</value></data>
|
||||
<data name="Top250SoftwareLabel" xml:space="preserve"><value>Top 250 des logiciels de tous les temps</value></data>
|
||||
<data name="NoRankedSoftwareLabel" xml:space="preserve"><value>Aucun logiciel classé pour le moment.</value></data>
|
||||
<data name="CriticReviewsCountLabel" xml:space="preserve"><value>critiques</value></data>
|
||||
<data name="UserRatingsCountLabel" xml:space="preserve"><value>évaluations d'utilisateurs</value></data>
|
||||
<data name="No rankings found" xml:space="preserve"><value>Aucun classement trouvé</value></data>
|
||||
<data name="Rankings are being computed" xml:space="preserve"><value>Les classements sont en cours de calcul</value></data>
|
||||
<data name="Last computed: {0}" xml:space="preserve"><value>Dernier calcul : {0}</value></data>
|
||||
<data name="Failed to load rankings. Please try again later." xml:space="preserve"><value>Impossible de charger les classements. Veuillez réessayer plus tard.</value></data>
|
||||
<data name="Failed to load ranking. Please try again later." xml:space="preserve"><value>Impossible de charger le classement. Veuillez réessayer plus tard.</value></data>
|
||||
<data name="By Gameplay" xml:space="preserve"><value>Par gameplay</value></data>
|
||||
<data name="By Genre" xml:space="preserve"><value>Par genre</value></data>
|
||||
<data name="By Setting" xml:space="preserve"><value>Par univers</value></data>
|
||||
<data name="By Category" xml:space="preserve"><value>Par catégorie</value></data>
|
||||
<data name="By Platform" xml:space="preserve"><value>Par plateforme</value></data>
|
||||
</root>
|
||||
|
||||
@@ -107,6 +107,9 @@
|
||||
<data name="SoftwareButton" xml:space="preserve">
|
||||
<value>Software</value>
|
||||
</data>
|
||||
<data name="RankingsButton" xml:space="preserve">
|
||||
<value>Classificações</value>
|
||||
</data>
|
||||
<data name="SoundSynthesizersButton" xml:space="preserve">
|
||||
<value>Sintetizadores de Som</value>
|
||||
</data>
|
||||
@@ -2546,4 +2549,19 @@
|
||||
<data name="ChangePasswordPage.Error.Mismatch" xml:space="preserve"><value>As senhas não coincidem.</value></data>
|
||||
<data name="ChangePasswordPage.Error.Generic" xml:space="preserve"><value>Não foi possível alterar a senha.</value></data>
|
||||
<data name="ChangePasswordPage.Success" xml:space="preserve"><value>Senha alterada com sucesso.</value></data>
|
||||
<data name="RankingsTitle" xml:space="preserve"><value>Classificações</value></data>
|
||||
<data name="Top250SoftwareLabel" xml:space="preserve"><value>Top 250 softwares de todos os tempos</value></data>
|
||||
<data name="NoRankedSoftwareLabel" xml:space="preserve"><value>Ainda não há software classificado.</value></data>
|
||||
<data name="CriticReviewsCountLabel" xml:space="preserve"><value>avaliações de críticos</value></data>
|
||||
<data name="UserRatingsCountLabel" xml:space="preserve"><value>avaliações de usuários</value></data>
|
||||
<data name="No rankings found" xml:space="preserve"><value>Nenhuma classificação encontrada</value></data>
|
||||
<data name="Rankings are being computed" xml:space="preserve"><value>As classificações estão sendo calculadas</value></data>
|
||||
<data name="Last computed: {0}" xml:space="preserve"><value>Última atualização: {0}</value></data>
|
||||
<data name="Failed to load rankings. Please try again later." xml:space="preserve"><value>Não foi possível carregar as classificações. Tente novamente mais tarde.</value></data>
|
||||
<data name="Failed to load ranking. Please try again later." xml:space="preserve"><value>Não foi possível carregar a classificação. Tente novamente mais tarde.</value></data>
|
||||
<data name="By Gameplay" xml:space="preserve"><value>Por jogabilidade</value></data>
|
||||
<data name="By Genre" xml:space="preserve"><value>Por gênero</value></data>
|
||||
<data name="By Setting" xml:space="preserve"><value>Por ambientação</value></data>
|
||||
<data name="By Category" xml:space="preserve"><value>Por categoria</value></data>
|
||||
<data name="By Platform" xml:space="preserve"><value>Por plataforma</value></data>
|
||||
</root>
|
||||
|
||||
@@ -107,6 +107,9 @@
|
||||
<data name="SoftwareButton" xml:space="preserve">
|
||||
<value>Software</value>
|
||||
</data>
|
||||
<data name="RankingsButton" xml:space="preserve">
|
||||
<value>Rankings</value>
|
||||
</data>
|
||||
<data name="SoundSynthesizersButton" xml:space="preserve">
|
||||
<value>Sound Synthesizers</value>
|
||||
</data>
|
||||
@@ -3076,4 +3079,19 @@
|
||||
<data name="ChangePasswordPage.Error.Mismatch" xml:space="preserve"><value>Passwords do not match.</value></data>
|
||||
<data name="ChangePasswordPage.Error.Generic" xml:space="preserve"><value>Failed to change password.</value></data>
|
||||
<data name="ChangePasswordPage.Success" xml:space="preserve"><value>Password changed successfully.</value></data>
|
||||
<data name="RankingsTitle" xml:space="preserve"><value>Rankings</value></data>
|
||||
<data name="Top250SoftwareLabel" xml:space="preserve"><value>Top 250 Software of All Time</value></data>
|
||||
<data name="NoRankedSoftwareLabel" xml:space="preserve"><value>No ranked software yet.</value></data>
|
||||
<data name="CriticReviewsCountLabel" xml:space="preserve"><value>critic reviews</value></data>
|
||||
<data name="UserRatingsCountLabel" xml:space="preserve"><value>user ratings</value></data>
|
||||
<data name="No rankings found" xml:space="preserve"><value>No rankings found</value></data>
|
||||
<data name="Rankings are being computed" xml:space="preserve"><value>Rankings are being computed</value></data>
|
||||
<data name="Last computed: {0}" xml:space="preserve"><value>Last computed: {0}</value></data>
|
||||
<data name="Failed to load rankings. Please try again later." xml:space="preserve"><value>Failed to load rankings. Please try again later.</value></data>
|
||||
<data name="Failed to load ranking. Please try again later." xml:space="preserve"><value>Failed to load ranking. Please try again later.</value></data>
|
||||
<data name="By Gameplay" xml:space="preserve"><value>By Gameplay</value></data>
|
||||
<data name="By Genre" xml:space="preserve"><value>By Genre</value></data>
|
||||
<data name="By Setting" xml:space="preserve"><value>By Setting</value></data>
|
||||
<data name="By Category" xml:space="preserve"><value>By Category</value></data>
|
||||
<data name="By Platform" xml:space="preserve"><value>By Platform</value></data>
|
||||
</root>
|
||||
|
||||
@@ -723,6 +723,11 @@ public class LocalizedStrings
|
||||
|
||||
// Software user-facing pages
|
||||
public string AllSoftwareButton => _l["AllSoftwareButton"];
|
||||
public string RankingsTitle => _l["RankingsTitle"];
|
||||
public string Top250SoftwareLabel => _l["Top250SoftwareLabel"];
|
||||
public string NoRankedSoftwareLabel => _l["NoRankedSoftwareLabel"];
|
||||
public string CriticReviewsCountLabel => _l["CriticReviewsCountLabel"];
|
||||
public string UserRatingsCountLabel => _l["UserRatingsCountLabel"];
|
||||
public string SoftwareLabel => _l["SoftwareLabel"];
|
||||
public string SoftwareErrorInfoBar_Title => _l["SoftwareErrorInfoBar_Title"];
|
||||
public string SoftwareFamilyLabel => _l["SoftwareFamilyLabel"];
|
||||
|
||||
@@ -983,6 +983,69 @@ public class SoftwareBrowsingService
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<RankingIndexResponseDto?> GetRankingsIndexAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
string lang = GetIso639CodeFromCulture();
|
||||
|
||||
_logger.LogInformation("Fetching rankings index");
|
||||
|
||||
return await _apiClient.Software.Rankings.Index.GetAsync(config =>
|
||||
{
|
||||
config.QueryParameters.Lang = lang;
|
||||
});
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error fetching rankings index");
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<SoftwareRankingDto>> GetRankingAsync(int rankingId)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("Fetching ranking {RankingId}", rankingId);
|
||||
|
||||
List<SoftwareRankingDto>? entries = await _apiClient.Software.Rankings[rankingId].GetAsync();
|
||||
|
||||
return entries ?? [];
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error fetching ranking {RankingId}", rankingId);
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<SoftwareRankingPlacementDto>> GetRankingPlacementsAsync(int softwareId)
|
||||
{
|
||||
try
|
||||
{
|
||||
string lang = GetIso639CodeFromCulture();
|
||||
|
||||
_logger.LogInformation("Fetching ranking placements for software {SoftwareId}", softwareId);
|
||||
|
||||
List<SoftwareRankingPlacementDto>? placements = await _apiClient.Software[softwareId].Rankings.GetAsync(
|
||||
config =>
|
||||
{
|
||||
config.QueryParameters.Lang = lang;
|
||||
});
|
||||
|
||||
return placements ?? [];
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error fetching ranking placements for software {SoftwareId}", softwareId);
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetIso639CodeFromCulture()
|
||||
{
|
||||
string twoLetter = System.Globalization.CultureInfo.CurrentUICulture.TwoLetterISOLanguageName;
|
||||
|
||||
Reference in New Issue
Block a user