From 577a31d00f00318272875cb063c0b997e2076f53 Mon Sep 17 00:00:00 2001
From: dend <1389609+dend@users.noreply.github.com>
Date: Tue, 27 Feb 2024 18:33:27 -0800
Subject: [PATCH] QOL improvements and fixes
- Fixes bug where data-based logs were done even if logging is disabled.
- Adds the ability to see medal details when tapped on one of them in the medal view.
- Adds a query that can find all matches based on medal ID.
- Rudimentary support for matches browsing from medal view
---
.../Controls/MatchesGridControl.xaml | 200 ++++++++++++++++++
.../Controls/MatchesGridControl.xaml.cs | 64 ++++++
src/OpenSpartan.Workshop/Data/DataHandler.cs | 163 ++++++++------
.../Data/MedalMatchesSource.cs | 38 ++++
src/OpenSpartan.Workshop/MainWindow.xaml.cs | 11 +-
.../OpenSpartan.Workshop.csproj | 11 +
.../OpenSpartan.Workshop.csproj.user | 6 +
.../Select/PlayerMatchesBasedOnMedal.sql | 99 +++++++++
.../Shared/RelayCommand.cs | 38 ++++
.../Shared/UserContextManager.cs | 32 +++
.../ViewModels/MedalMatchesViewModel.cs | 52 +++++
.../ViewModels/MedalsViewModel.cs | 17 +-
src/OpenSpartan.Workshop/Views/HomeView.xaml | 16 +-
.../Views/MatchesView.xaml | 176 +--------------
.../Views/MedalMatchesView.xaml | 34 +++
.../Views/MedalMatchesView.xaml.cs | 35 +++
.../Views/MedalsView.xaml | 13 +-
.../Views/MedalsView.xaml.cs | 46 ++++
18 files changed, 788 insertions(+), 263 deletions(-)
create mode 100644 src/OpenSpartan.Workshop/Controls/MatchesGridControl.xaml
create mode 100644 src/OpenSpartan.Workshop/Controls/MatchesGridControl.xaml.cs
create mode 100644 src/OpenSpartan.Workshop/Data/MedalMatchesSource.cs
create mode 100644 src/OpenSpartan.Workshop/Queries/Select/PlayerMatchesBasedOnMedal.sql
create mode 100644 src/OpenSpartan.Workshop/Shared/RelayCommand.cs
create mode 100644 src/OpenSpartan.Workshop/ViewModels/MedalMatchesViewModel.cs
create mode 100644 src/OpenSpartan.Workshop/Views/MedalMatchesView.xaml
create mode 100644 src/OpenSpartan.Workshop/Views/MedalMatchesView.xaml.cs
diff --git a/src/OpenSpartan.Workshop/Controls/MatchesGridControl.xaml b/src/OpenSpartan.Workshop/Controls/MatchesGridControl.xaml
new file mode 100644
index 0000000..b2cd50e
--- /dev/null
+++ b/src/OpenSpartan.Workshop/Controls/MatchesGridControl.xaml
@@ -0,0 +1,200 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/OpenSpartan.Workshop/Controls/MatchesGridControl.xaml.cs b/src/OpenSpartan.Workshop/Controls/MatchesGridControl.xaml.cs
new file mode 100644
index 0000000..20263c2
--- /dev/null
+++ b/src/OpenSpartan.Workshop/Controls/MatchesGridControl.xaml.cs
@@ -0,0 +1,64 @@
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Media;
+using CommunityToolkit.WinUI.UI.Controls;
+using System.Collections;
+
+namespace OpenSpartan.Workshop.Controls
+{
+ public sealed partial class MatchesGridControl : UserControl
+ {
+ public static readonly DependencyProperty MatchSourceProperty =
+ DependencyProperty.Register(
+ "MatchSource", // Name of the property
+ typeof(IEnumerable), // Type of the property
+ typeof(MatchesGridControl), // Type of the owner (your user control)
+ new PropertyMetadata(null) // Optional metadata, e.g., default value
+ );
+
+ public IEnumerable MatchSource
+ {
+ get { return (IEnumerable)GetValue(MatchSourceProperty); }
+ set { SetValue(MatchSourceProperty, value); }
+ }
+
+ public MatchesGridControl()
+ {
+ this.InitializeComponent();
+ }
+
+ private void dgdMatches_PointerReleased(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
+ {
+ DataGridRow row = FindParent((UIElement)e.OriginalSource);
+
+ if (row != null)
+ {
+ if (row.DetailsVisibility == Visibility.Visible)
+ {
+ row.DetailsVisibility = Visibility.Collapsed;
+ }
+ else
+ {
+ row.DetailsVisibility = Visibility.Visible;
+ }
+ }
+ }
+
+ public static T FindParent(DependencyObject childElement) where T : Control
+ {
+ DependencyObject currentElement = childElement;
+
+ while (currentElement != null)
+ {
+ if (currentElement is T matchingElement)
+ {
+ return matchingElement;
+ }
+
+ currentElement = VisualTreeHelper.GetParent(currentElement);
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/src/OpenSpartan.Workshop/Data/DataHandler.cs b/src/OpenSpartan.Workshop/Data/DataHandler.cs
index b88e261..dedb873 100644
--- a/src/OpenSpartan.Workshop/Data/DataHandler.cs
+++ b/src/OpenSpartan.Workshop/Data/DataHandler.cs
@@ -53,12 +53,12 @@ internal static string SetWALJournalingMode()
}
else
{
- Logger.Error($"WAL journaling mode not set.");
+ if (SettingsViewModel.Instance.EnableLogging) Logger.Error($"WAL journaling mode not set.");
}
}
catch (Exception ex)
{
- Logger.Error($"Journaling mode modification exception: {ex.Message}");
+ if (SettingsViewModel.Instance.EnableLogging) Logger.Error($"Journaling mode modification exception: {ex.Message}");
}
return null;
@@ -91,7 +91,7 @@ internal static bool BootstrapDatabase()
}
catch (Exception ex)
{
- Logger.Error($"Database bootstrapping failure: {ex.Message}");
+ if (SettingsViewModel.Instance.EnableLogging) Logger.Error($"Database bootstrapping failure: {ex.Message}");
return false;
}
}
@@ -119,11 +119,11 @@ private static void SetupIndices(SqliteConnection connection)
if (outcome > 0)
{
- Logger.Info("Indices provisioned.");
+ if (SettingsViewModel.Instance.EnableLogging) Logger.Info("Indices provisioned.");
}
else
{
- Logger.Warn("Indices could not be set up. If this is not the first run, then those are likely already configured.");
+ if (SettingsViewModel.Instance.EnableLogging) Logger.Warn("Indices could not be set up. If this is not the first run, then those are likely already configured.");
}
}
@@ -151,7 +151,7 @@ internal static bool InsertServiceRecordEntry(string serviceRecordJson)
}
catch (Exception ex)
{
- Logger.Error($"Error inserting service record entry. {ex.Message}");
+ if (SettingsViewModel.Instance.EnableLogging) Logger.Error($"Error inserting service record entry. {ex.Message}");
return false;
}
}
@@ -179,12 +179,12 @@ internal static List GetMatchIds()
}
else
{
- Logger.Warn($"No rows returned for distinct match IDs.");
+ if (SettingsViewModel.Instance.EnableLogging) Logger.Warn($"No rows returned for distinct match IDs.");
}
}
catch (Exception ex)
{
- Logger.Error($"An error occurred obtaining unique match IDs. {ex.Message}");
+ if (SettingsViewModel.Instance.EnableLogging) Logger.Error($"An error occurred obtaining unique match IDs. {ex.Message}");
}
return null;
@@ -215,18 +215,28 @@ internal static RewardTrackMetadata GetOperationResponseBody(string operationPat
}
else
{
- Logger.Warn($"No rows returned for operations.");
+ if (SettingsViewModel.Instance.EnableLogging) Logger.Warn($"No rows returned for operations.");
}
}
catch (Exception ex)
{
- Logger.Error($"An error occurred obtaining operations from database. {ex.Message}");
+ if (SettingsViewModel.Instance.EnableLogging) Logger.Error($"An error occurred obtaining operations from database. {ex.Message}");
}
return null;
}
internal static List GetMatches(string playerXuid, string boundaryTime, int boundaryLimit)
+ {
+ return GetMatchesInternal(playerXuid, null, boundaryTime, boundaryLimit);
+ }
+
+ internal static List GetMatchesWithMedal(string playerXuid, long medalNameId, string boundaryTime, int boundaryLimit)
+ {
+ return GetMatchesInternal(playerXuid, medalNameId, boundaryTime, boundaryLimit);
+ }
+
+ private static List GetMatchesInternal(string playerXuid, long? medalNameId, string boundaryTime, int boundaryLimit)
{
try
{
@@ -234,7 +244,16 @@ internal static List GetMatches(string playerXuid, string boun
connection.Open();
using var command = connection.CreateCommand();
- command.CommandText = GetQuery("Select", "PlayerMatches");
+ if (medalNameId.HasValue)
+ {
+ command.CommandText = GetQuery("Select", "PlayerMatchesBasedOnMedal");
+ command.Parameters.AddWithValue("MedalNameId", medalNameId.Value);
+ }
+ else
+ {
+ command.CommandText = GetQuery("Select", "PlayerMatches");
+ }
+
command.Parameters.AddWithValue("PlayerXuid", playerXuid);
command.Parameters.AddWithValue("BoundaryTime", boundaryTime);
command.Parameters.AddWithValue("BoundaryLimit", boundaryLimit);
@@ -242,64 +261,68 @@ internal static List GetMatches(string playerXuid, string boun
using var reader = command.ExecuteReader();
if (reader.HasRows)
{
- List matches = new();
+ List matches = new List();
while (reader.Read())
{
- var matchOrdinal = reader.GetOrdinal("MatchId");
- var startTimeOrdinal = reader.GetOrdinal("StartTime");
- var rankOrdinal = reader.GetOrdinal("Rank");
- var outcomeOrdinal = reader.GetOrdinal("Outcome");
- var gameVariantCategoryOrdinal = reader.GetOrdinal("GameVariantCategory");
- var mapOrdinal = reader.GetOrdinal("Map");
- var playlistOrdinal = reader.GetOrdinal("Playlist");
- var gameVariantOrdinal = reader.GetOrdinal("GameVariant");
- var durationOrdinal = reader.GetOrdinal("Duration");
- var lastTeamIdOrdinal = reader.GetOrdinal("LastTeamId");
- var teamsOrdinal = reader.GetOrdinal("Teams");
- var participationInfoOrdinal = reader.GetOrdinal("ParticipationInfo");
- var playerTeamStatsOrdinal = reader.GetOrdinal("PlayerTeamStats");
- var teamMmrOrdinal = reader.GetOrdinal("TeamMmr");
- var expectedDeathsOrdinal = reader.GetOrdinal("ExpectedDeaths");
- var expectedKillsOrdinal = reader.GetOrdinal("ExpectedKills");
-
- MatchTableEntity entity = new()
- {
- MatchId = reader.IsDBNull(matchOrdinal) ? string.Empty : reader.GetFieldValue(matchOrdinal),
- StartTime = reader.IsDBNull(startTimeOrdinal) ? DateTimeOffset.UnixEpoch : reader.GetFieldValue(startTimeOrdinal).ToLocalTime(),
- Rank = reader.IsDBNull(rankOrdinal) ? 0 : reader.GetFieldValue(rankOrdinal),
- Outcome = reader.IsDBNull(outcomeOrdinal) ? Outcome.DidNotFinish : reader.GetFieldValue(outcomeOrdinal),
- Category = reader.IsDBNull(gameVariantCategoryOrdinal) ? GameVariantCategory.None : reader.GetFieldValue(gameVariantCategoryOrdinal),
- Map = reader.IsDBNull(mapOrdinal) ? string.Empty : reader.GetFieldValue(mapOrdinal),
- Playlist = reader.IsDBNull(playlistOrdinal) ? string.Empty : reader.GetFieldValue(playlistOrdinal),
- GameVariant = reader.IsDBNull(gameVariantOrdinal) ? string.Empty : reader.GetFieldValue(gameVariantOrdinal),
- Duration = reader.IsDBNull(durationOrdinal) ? TimeSpan.Zero : XmlConvert.ToTimeSpan(reader.GetFieldValue(durationOrdinal)),
- LastTeamId = reader.IsDBNull(durationOrdinal) ? null : reader.GetFieldValue(lastTeamIdOrdinal),
- Teams = reader.IsDBNull(teamsOrdinal) ? null : JsonSerializer.Deserialize>(reader.GetFieldValue(teamsOrdinal), serializerOptions),
- ParticipationInfo = reader.IsDBNull(teamsOrdinal) ? null : JsonSerializer.Deserialize(reader.GetFieldValue(participationInfoOrdinal), serializerOptions),
- PlayerTeamStats = reader.IsDBNull(teamsOrdinal) ? null : JsonSerializer.Deserialize>(reader.GetFieldValue(playerTeamStatsOrdinal), serializerOptions),
- TeamMmr = reader.IsDBNull(teamMmrOrdinal) ? null : reader.GetFieldValue(teamMmrOrdinal),
- ExpectedDeaths = reader.IsDBNull(expectedDeathsOrdinal) ? null : reader.GetFieldValue(expectedDeathsOrdinal),
- ExpectedKills = reader.IsDBNull(expectedKillsOrdinal) ? null : reader.GetFieldValue(expectedKillsOrdinal),
- };
-
- matches.Add(entity);
+ matches.Add(ReadMatchTableEntity(reader));
}
return matches;
}
else
{
- Logger.Warn($"No rows returned for player match IDs.");
+ if (SettingsViewModel.Instance.EnableLogging) Logger.Warn($"No rows returned for player match IDs.");
}
}
catch (Exception ex)
{
- Logger.Error($"An error occurred obtaining matches. {ex.Message}");
+ if (SettingsViewModel.Instance.EnableLogging) Logger.Error($"An error occurred obtaining matches. {ex.Message}");
}
return null;
}
+ private static MatchTableEntity ReadMatchTableEntity(SqliteDataReader reader)
+ {
+ var matchOrdinal = reader.GetOrdinal("MatchId");
+ var startTimeOrdinal = reader.GetOrdinal("StartTime");
+ var rankOrdinal = reader.GetOrdinal("Rank");
+ var outcomeOrdinal = reader.GetOrdinal("Outcome");
+ var gameVariantCategoryOrdinal = reader.GetOrdinal("GameVariantCategory");
+ var mapOrdinal = reader.GetOrdinal("Map");
+ var playlistOrdinal = reader.GetOrdinal("Playlist");
+ var gameVariantOrdinal = reader.GetOrdinal("GameVariant");
+ var durationOrdinal = reader.GetOrdinal("Duration");
+ var lastTeamIdOrdinal = reader.GetOrdinal("LastTeamId");
+ var teamsOrdinal = reader.GetOrdinal("Teams");
+ var participationInfoOrdinal = reader.GetOrdinal("ParticipationInfo");
+ var playerTeamStatsOrdinal = reader.GetOrdinal("PlayerTeamStats");
+ var teamMmrOrdinal = reader.GetOrdinal("TeamMmr");
+ var expectedDeathsOrdinal = reader.GetOrdinal("ExpectedDeaths");
+ var expectedKillsOrdinal = reader.GetOrdinal("ExpectedKills");
+
+ return new MatchTableEntity
+ {
+ MatchId = reader.IsDBNull(matchOrdinal) ? string.Empty : reader.GetFieldValue(matchOrdinal),
+ StartTime = reader.IsDBNull(startTimeOrdinal) ? DateTimeOffset.UnixEpoch : reader.GetFieldValue(startTimeOrdinal).ToLocalTime(),
+ Rank = reader.IsDBNull(rankOrdinal) ? 0 : reader.GetFieldValue(rankOrdinal),
+ Outcome = reader.IsDBNull(outcomeOrdinal) ? Outcome.DidNotFinish : reader.GetFieldValue(outcomeOrdinal),
+ Category = reader.IsDBNull(gameVariantCategoryOrdinal) ? GameVariantCategory.None : reader.GetFieldValue(gameVariantCategoryOrdinal),
+ Map = reader.IsDBNull(mapOrdinal) ? string.Empty : reader.GetFieldValue(mapOrdinal),
+ Playlist = reader.IsDBNull(playlistOrdinal) ? string.Empty : reader.GetFieldValue(playlistOrdinal),
+ GameVariant = reader.IsDBNull(gameVariantOrdinal) ? string.Empty : reader.GetFieldValue(gameVariantOrdinal),
+ Duration = reader.IsDBNull(durationOrdinal) ? TimeSpan.Zero : XmlConvert.ToTimeSpan(reader.GetFieldValue(durationOrdinal)),
+ LastTeamId = reader.IsDBNull(durationOrdinal) ? null : reader.GetFieldValue(lastTeamIdOrdinal),
+ Teams = reader.IsDBNull(teamsOrdinal) ? null : JsonSerializer.Deserialize>(reader.GetFieldValue(teamsOrdinal), serializerOptions),
+ ParticipationInfo = reader.IsDBNull(teamsOrdinal) ? null : JsonSerializer.Deserialize(reader.GetFieldValue(participationInfoOrdinal), serializerOptions),
+ PlayerTeamStats = reader.IsDBNull(teamsOrdinal) ? null : JsonSerializer.Deserialize>(reader.GetFieldValue(playerTeamStatsOrdinal), serializerOptions),
+ TeamMmr = reader.IsDBNull(teamMmrOrdinal) ? null : reader.GetFieldValue(teamMmrOrdinal),
+ ExpectedDeaths = reader.IsDBNull(expectedDeathsOrdinal) ? null : reader.GetFieldValue(expectedDeathsOrdinal),
+ ExpectedKills = reader.IsDBNull(expectedKillsOrdinal) ? null : reader.GetFieldValue(expectedKillsOrdinal),
+ };
+ }
+
+
internal static Tuple GetMatchStatsAvailability(string matchId)
{
try
@@ -328,7 +351,7 @@ internal static Tuple GetMatchStatsAvailability(string matchId)
}
catch (Exception ex)
{
- Logger.Error($"An error occurred obtaining match and stats availability. {ex.Message}");
+ if (SettingsViewModel.Instance.EnableLogging) Logger.Error($"An error occurred obtaining match and stats availability. {ex.Message}");
}
return null;
@@ -355,7 +378,7 @@ internal static bool InsertPlayerMatchStats(string matchId, string statsBody)
}
catch (Exception ex)
{
- Logger.Error($"An error occurred inserting player match and stats. {ex.Message}");
+ if (SettingsViewModel.Instance.EnableLogging) Logger.Error($"An error occurred inserting player match and stats. {ex.Message}");
}
return false;
@@ -381,7 +404,7 @@ internal static bool InsertMatchStats (string matchBody)
}
catch (Exception ex)
{
- Logger.Error($"An error occurred inserting match and stats. {ex.Message}");
+ if (SettingsViewModel.Instance.EnableLogging) Logger.Error($"An error occurred inserting match and stats. {ex.Message}");
}
return false;
@@ -447,7 +470,7 @@ internal static async Task UpdateMatchAssetRecords(MatchStats result)
if (insertionResult > 0)
{
- Logger.Info($"Stored map: {result.MatchInfo.MapVariant.AssetId}/{result.MatchInfo.MapVariant.VersionId}");
+ if (SettingsViewModel.Instance.EnableLogging) Logger.Info($"Stored map: {result.MatchInfo.MapVariant.AssetId}/{result.MatchInfo.MapVariant.VersionId}");
}
}
}
@@ -466,7 +489,7 @@ internal static async Task UpdateMatchAssetRecords(MatchStats result)
if (insertionResult > 0)
{
- Logger.Info($"Stored playlist: {result.MatchInfo.Playlist.AssetId}/{result.MatchInfo.Playlist.VersionId}");
+ if (SettingsViewModel.Instance.EnableLogging) Logger.Info($"Stored playlist: {result.MatchInfo.Playlist.AssetId}/{result.MatchInfo.Playlist.VersionId}");
}
}
}
@@ -485,7 +508,7 @@ internal static async Task UpdateMatchAssetRecords(MatchStats result)
if (insertionResult > 0)
{
- Logger.Info($"Stored playlist + map mode pair: {result.MatchInfo.PlaylistMapModePair.AssetId}/{result.MatchInfo.PlaylistMapModePair.VersionId}");
+ if (SettingsViewModel.Instance.EnableLogging) Logger.Info($"Stored playlist + map mode pair: {result.MatchInfo.PlaylistMapModePair.AssetId}/{result.MatchInfo.PlaylistMapModePair.VersionId}");
}
}
}
@@ -507,7 +530,7 @@ internal static async Task UpdateMatchAssetRecords(MatchStats result)
if (insertionResult > 0)
{
- Logger.Info($"Stored game variant: {result.MatchInfo.UgcGameVariant.AssetId}/{result.MatchInfo.UgcGameVariant.VersionId}");
+ if (SettingsViewModel.Instance.EnableLogging) Logger.Info($"Stored game variant: {result.MatchInfo.UgcGameVariant.AssetId}/{result.MatchInfo.UgcGameVariant.VersionId}");
}
}
@@ -539,7 +562,7 @@ internal static async Task UpdateMatchAssetRecords(MatchStats result)
if (insertionResult > 0)
{
- Logger.Info($"Stored engine game variant: {engineGameVariant.Result.AssetId}/{engineGameVariant.Result.VersionId}");
+ if (SettingsViewModel.Instance.EnableLogging) Logger.Info($"Stored engine game variant: {engineGameVariant.Result.AssetId}/{engineGameVariant.Result.VersionId}");
}
}
}
@@ -548,7 +571,7 @@ internal static async Task UpdateMatchAssetRecords(MatchStats result)
}
catch (Exception ex)
{
- Logger.Error($"Error updating match stats. {ex.Message}");
+ if (SettingsViewModel.Instance.EnableLogging) Logger.Error($"Error updating match stats. {ex.Message}");
return false;
}
}
@@ -571,7 +594,7 @@ internal static List GetMedals()
using var reader = command.ExecuteReader();
if (reader.HasRows)
{
- List matchIds = new();
+ List matchIds = [];
while (reader.Read())
{
matchIds.AddRange(JsonSerializer.Deserialize>(reader.GetString(0)));
@@ -581,12 +604,12 @@ internal static List GetMedals()
}
else
{
- Logger.Warn($"No rows returned for medals.");
+ if (SettingsViewModel.Instance.EnableLogging) Logger.Warn($"No rows returned for medals.");
}
}
catch (Exception ex)
{
- Logger.Error($"An error occurred obtaining medals from the database. {ex.Message}");
+ if (SettingsViewModel.Instance.EnableLogging) Logger.Error($"An error occurred obtaining medals from the database. {ex.Message}");
}
return null;
@@ -608,7 +631,7 @@ internal static bool UpdateOperationRewardTracks(string response, string path)
if (insertionResult > 0)
{
- Logger.Info($"Stored reward track {path}.");
+ if (SettingsViewModel.Instance.EnableLogging) Logger.Info($"Stored reward track {path}.");
return true;
}
else
@@ -633,7 +656,7 @@ internal static bool UpdateInventoryItems(string response, string path)
if (insertionResult > 0)
{
- Logger.Info($"Stored inventory item {path}.");
+ if (SettingsViewModel.Instance.EnableLogging) Logger.Info($"Stored inventory item {path}.");
return true;
}
else
@@ -721,12 +744,12 @@ internal static InGameItem GetInventoryItem(string path)
}
else
{
- Logger.Info($"No rows returned for inventory items query.");
+ if (SettingsViewModel.Instance.EnableLogging) Logger.Info($"No rows returned for inventory items query.");
}
}
catch (Exception ex)
{
- Logger.Error($"An error occurred obtaining inventory items. {ex.Message}");
+ if (SettingsViewModel.Instance.EnableLogging) Logger.Error($"An error occurred obtaining inventory items. {ex.Message}");
}
return null;
@@ -752,11 +775,11 @@ internal static bool InsertOwnedInventoryItems(PlayerInventory result)
var insertionResult = insertionCommand.ExecuteNonQuery();
if (insertionResult > 0)
{
- Logger.Info($"Stored owned inventory item {item.ItemId}.");
+ if (SettingsViewModel.Instance.EnableLogging) Logger.Info($"Stored owned inventory item {item.ItemId}.");
}
else
{
- Logger.Error($"Could not store owned inventory item {item.ItemId}.");
+ if (SettingsViewModel.Instance.EnableLogging) Logger.Error($"Could not store owned inventory item {item.ItemId}.");
}
}
diff --git a/src/OpenSpartan.Workshop/Data/MedalMatchesSource.cs b/src/OpenSpartan.Workshop/Data/MedalMatchesSource.cs
new file mode 100644
index 0000000..8996ae5
--- /dev/null
+++ b/src/OpenSpartan.Workshop/Data/MedalMatchesSource.cs
@@ -0,0 +1,38 @@
+using CommunityToolkit.Common.Collections;
+using OpenSpartan.Workshop.Models;
+using OpenSpartan.Workshop.Shared;
+using OpenSpartan.Workshop.ViewModels;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace OpenSpartan.Workshop.Data
+{
+ public class MedalMatchesSource : IIncrementalSource
+ {
+ public MedalMatchesSource()
+ {
+ Task.Run(() =>
+ {
+ UserContextManager.GetPlayerMatches();
+ });
+ }
+
+ Task> IIncrementalSource.GetPagedItemsAsync(int pageIndex, int pageSize, CancellationToken cancellationToken)
+ {
+ if (MedalMatchesViewModel.Instance.MatchList != null && MedalMatchesViewModel.Instance.MatchList.Count > 0)
+ {
+ var date = MedalMatchesViewModel.Instance.MatchList.Min(a => a.StartTime).ToString("o", CultureInfo.InvariantCulture);
+ var matches = Task.Run(() => (IEnumerable)DataHandler.GetMatchesWithMedal($"xuid({HomeViewModel.Instance.Xuid})", MedalMatchesViewModel.Instance.Medal.NameId, date, pageSize));
+
+ return matches;
+ }
+ else
+ {
+ return null;
+ }
+ }
+ }
+}
diff --git a/src/OpenSpartan.Workshop/MainWindow.xaml.cs b/src/OpenSpartan.Workshop/MainWindow.xaml.cs
index 50c9847..82df4dc 100644
--- a/src/OpenSpartan.Workshop/MainWindow.xaml.cs
+++ b/src/OpenSpartan.Workshop/MainWindow.xaml.cs
@@ -2,6 +2,8 @@
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media.Animation;
using Microsoft.UI.Xaml.Navigation;
+using OpenSpartan.Workshop.ViewModels;
+using OpenSpartan.Workshop.Views;
using System;
using System.Linq;
@@ -34,9 +36,12 @@ private void On_Navigated(object sender, NavigationEventArgs e)
else if (ContentFrame.SourcePageType != null)
{
// Select the nav view item that corresponds to the page being navigated to.
- nvRoot.SelectedItem = nvRoot.MenuItems
- .OfType()
- .First(i => i.Tag.Equals(ContentFrame.SourcePageType.FullName.ToString()));
+ if (ContentFrame.SourcePageType != typeof(MedalMatchesView))
+ {
+ nvRoot.SelectedItem = nvRoot.MenuItems
+ .OfType()
+ .First(i => i.Tag.Equals(ContentFrame.SourcePageType.FullName.ToString()));
+ }
}
}
diff --git a/src/OpenSpartan.Workshop/OpenSpartan.Workshop.csproj b/src/OpenSpartan.Workshop/OpenSpartan.Workshop.csproj
index 25931ef..487bb83 100644
--- a/src/OpenSpartan.Workshop/OpenSpartan.Workshop.csproj
+++ b/src/OpenSpartan.Workshop/OpenSpartan.Workshop.csproj
@@ -14,12 +14,14 @@
true
+
+
@@ -63,6 +65,9 @@
+
+ MSBuild:Compile
+
Always
@@ -158,9 +163,15 @@
Always
+
+ Always
+
Always
+
+ MSBuild:Compile
+
MSBuild:Compile
diff --git a/src/OpenSpartan.Workshop/OpenSpartan.Workshop.csproj.user b/src/OpenSpartan.Workshop/OpenSpartan.Workshop.csproj.user
index 63ea68b..42188ad 100644
--- a/src/OpenSpartan.Workshop/OpenSpartan.Workshop.csproj.user
+++ b/src/OpenSpartan.Workshop/OpenSpartan.Workshop.csproj.user
@@ -23,6 +23,12 @@
Designer
+
+ Designer
+
+
+ Designer
+
Designer
diff --git a/src/OpenSpartan.Workshop/Queries/Select/PlayerMatchesBasedOnMedal.sql b/src/OpenSpartan.Workshop/Queries/Select/PlayerMatchesBasedOnMedal.sql
new file mode 100644
index 0000000..3c2a521
--- /dev/null
+++ b/src/OpenSpartan.Workshop/Queries/Select/PlayerMatchesBasedOnMedal.sql
@@ -0,0 +1,99 @@
+WITH RAW_MATCHES AS (
+ SELECT
+ MS.MatchId,
+ MS.Teams,
+ json_extract(MS.MatchInfo, '$.StartTime') AS StartTime,
+ json_extract(MS.MatchInfo, '$.Duration') AS Duration,
+ json_extract(MS.MatchInfo, '$.GameVariantCategory') AS GameVariantCategory,
+ json_extract(MS.MatchInfo, '$.MapVariant.AssetId') AS Map,
+ json_extract(MS.MatchInfo, '$.Playlist.AssetId') AS Playlist,
+ json_extract(MS.MatchInfo, '$.UgcGameVariant.AssetId') AS UgcGameVariant,
+ json_extract(value, '$.Rank') AS "Rank",
+ json_extract(value, '$.Outcome') AS Outcome,
+ json_extract(value, '$.LastTeamId') AS LastTeamId,
+ json_extract(value, '$.ParticipationInfo') AS ParticipationInfo,
+ json_extract(value, '$.PlayerTeamStats') AS PlayerTeamStats
+ FROM
+ MatchStats MS
+ JOIN
+ json_each(MS.Players) PE ON json_extract(PE.value, '$.PlayerId') = $PlayerXuid
+ WHERE
+ MS.MatchInfo IS NOT NULL
+ AND EXISTS (
+ SELECT 1
+ FROM json_each(PlayerTeamStats) AS PTS
+ WHERE EXISTS (
+ SELECT 1
+ FROM json_each(PTS.value, '$.Stats.CoreStats.Medals') AS MedalEntry
+ WHERE json_extract(MedalEntry.value, '$.NameId') = $MedalNameId
+ )
+ )
+ ORDER BY
+ StartTime DESC
+),
+MATCH_DETAILS AS (
+ SELECT
+ PMS.MatchId,
+ json_extract(PE.value, '$.Result.TeamMmr') AS TeamMmr,
+ json_extract(PE.value, '$.Result.Counterfactuals.SelfCounterfactuals.Deaths') AS ExpectedDeaths,
+ json_extract(PE.value, '$.Result.Counterfactuals.SelfCounterfactuals.Kills') AS ExpectedKills
+ FROM
+ PlayerMatchStats PMS
+ JOIN
+ json_each(PMS.PlayerStats) PE ON json_extract(PE.value, '$.Id') = $PlayerXuid
+ WHERE
+ PMS.PlayerStats IS NOT NULL
+),
+SELECTIVE_MATCHES AS (
+ SELECT
+ MatchId,
+ Teams,
+ StartTime,
+ Duration,
+ "Rank",
+ Outcome,
+ LastTeamId,
+ ParticipationInfo,
+ PlayerTeamStats,
+ GameVariantCategory,
+ Map,
+ Playlist,
+ UgcGameVariant
+ FROM
+ RAW_MATCHES
+ WHERE
+ StartTime <= $BoundaryTime
+ LIMIT
+ $BoundaryLimit
+)
+SELECT
+ SM.MatchId,
+ SM.Teams,
+ SM.StartTime,
+ SM.Duration,
+ SM."Rank",
+ SM.Outcome,
+ SM.LastTeamId,
+ SM.ParticipationInfo,
+ SM.PlayerTeamStats,
+ SM.GameVariantCategory,
+ M.PublicName AS Map,
+ P.PublicName AS Playlist,
+ GV.PublicName AS GameVariant,
+ MD.TeamMmr AS TeamMmr,
+ MD.ExpectedDeaths AS ExpectedDeaths,
+ MD.ExpectedKills AS ExpectedKills
+FROM
+ SELECTIVE_MATCHES SM
+LEFT JOIN
+ Maps M ON M.AssetId = SM.Map
+LEFT JOIN
+ Playlists P ON P.AssetId = SM.Playlist
+LEFT JOIN
+ GameVariants GV ON GV.AssetId = SM.UgcGameVariant
+LEFT JOIN
+ MATCH_DETAILS MD ON MD.MatchId = SM.MatchId
+GROUP BY
+ SM.MatchId
+ORDER BY
+ StartTime DESC;
diff --git a/src/OpenSpartan.Workshop/Shared/RelayCommand.cs b/src/OpenSpartan.Workshop/Shared/RelayCommand.cs
new file mode 100644
index 0000000..864c156
--- /dev/null
+++ b/src/OpenSpartan.Workshop/Shared/RelayCommand.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Input;
+
+namespace OpenSpartan.Workshop.Shared
+{
+ public class RelayCommand : ICommand
+ {
+ private readonly Action _execute;
+ private readonly Func _canExecute;
+
+ public event EventHandler CanExecuteChanged;
+
+ public RelayCommand(Action execute, Func canExecute = null)
+ {
+ _execute = execute ?? throw new ArgumentNullException(nameof(execute));
+ _canExecute = canExecute;
+ }
+
+ public bool CanExecute(object parameter)
+ {
+ return _canExecute == null || _canExecute((T)parameter);
+ }
+
+ public void Execute(object parameter)
+ {
+ _execute((T)parameter);
+ }
+
+ public void RaiseCanExecuteChanged()
+ {
+ CanExecuteChanged?.Invoke(this, EventArgs.Empty);
+ }
+ }
+}
diff --git a/src/OpenSpartan.Workshop/Shared/UserContextManager.cs b/src/OpenSpartan.Workshop/Shared/UserContextManager.cs
index 1525a13..aa99725 100644
--- a/src/OpenSpartan.Workshop/Shared/UserContextManager.cs
+++ b/src/OpenSpartan.Workshop/Shared/UserContextManager.cs
@@ -704,6 +704,38 @@ await dispatcherWindow.DispatcherQueue.EnqueueAsync(() =>
}
}
+ internal static async void PopulateMedalMatchData(long medalNameId)
+ {
+ if (HomeViewModel.Instance.Xuid != null)
+ {
+ List matches = null;
+ string date = DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture);
+
+ if (MedalMatchesViewModel.Instance.MatchList.Count == 0)
+ {
+ matches = DataHandler.GetMatchesWithMedal($"xuid({HomeViewModel.Instance.Xuid})", medalNameId, date, 100);
+ }
+ else
+ {
+ date = MedalMatchesViewModel.Instance.MatchList.Min(a => a.StartTime).ToString("o", CultureInfo.InvariantCulture);
+ matches = DataHandler.GetMatchesWithMedal($"xuid({HomeViewModel.Instance.Xuid})", medalNameId, date, 10);
+ }
+
+ if (matches != null)
+ {
+ var dispatcherWindow = ((Application.Current as App)?.MainWindow) as MainWindow;
+ await dispatcherWindow.DispatcherQueue.EnqueueAsync(() =>
+ {
+ MedalMatchesViewModel.Instance.MatchList.AddRange(matches);
+ });
+ }
+ else
+ {
+ if (SettingsViewModel.Instance.EnableLogging) Logger.Error("Could not get the list of matches for the specified parameters.");
+ }
+ }
+ }
+
internal static async Task PopulateMedalData()
{
try
diff --git a/src/OpenSpartan.Workshop/ViewModels/MedalMatchesViewModel.cs b/src/OpenSpartan.Workshop/ViewModels/MedalMatchesViewModel.cs
new file mode 100644
index 0000000..4986688
--- /dev/null
+++ b/src/OpenSpartan.Workshop/ViewModels/MedalMatchesViewModel.cs
@@ -0,0 +1,52 @@
+using CommunityToolkit.WinUI;
+using Den.Dev.Orion.Models.HaloInfinite;
+using OpenSpartan.Workshop.Data;
+using OpenSpartan.Workshop.Models;
+using OpenSpartan.Workshop.Shared;
+using System.Runtime.CompilerServices;
+
+namespace OpenSpartan.Workshop.ViewModels
+{
+ internal class MedalMatchesViewModel : Observable
+ {
+ public static MedalMatchesViewModel Instance { get; } = new MedalMatchesViewModel();
+
+ private IncrementalLoadingCollection _matchList;
+ private Medal _medal;
+
+ public MedalMatchesViewModel()
+ {
+ MatchList = new IncrementalLoadingCollection();
+ }
+
+ public Medal Medal
+ {
+ get => _medal;
+ set
+ {
+ if (_medal != value)
+ {
+ _medal = value;
+ NotifyPropertyChanged();
+ }
+ }
+ }
+ public IncrementalLoadingCollection MatchList
+ {
+ get => _matchList;
+ set
+ {
+ if (_matchList != value)
+ {
+ _matchList = value;
+ NotifyPropertyChanged();
+ }
+ }
+ }
+
+ public void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
+ {
+ OnPropertyChanged(propertyName);
+ }
+ }
+}
diff --git a/src/OpenSpartan.Workshop/ViewModels/MedalsViewModel.cs b/src/OpenSpartan.Workshop/ViewModels/MedalsViewModel.cs
index 7e6be87..7da08c0 100644
--- a/src/OpenSpartan.Workshop/ViewModels/MedalsViewModel.cs
+++ b/src/OpenSpartan.Workshop/ViewModels/MedalsViewModel.cs
@@ -1,5 +1,8 @@
using Den.Dev.Orion.Models.HaloInfinite;
+using Microsoft.UI.Xaml.Controls;
using OpenSpartan.Workshop.Shared;
+using OpenSpartan.Workshop.Views;
+using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Runtime.CompilerServices;
@@ -10,10 +13,17 @@ internal class MedalsViewModel : Observable
{
public static MedalsViewModel Instance { get; } = new MedalsViewModel();
- private MedalsViewModel() { }
+ private MedalsViewModel()
+ {
+ NavigateCommand = new RelayCommand(NavigateToAnotherView);
+ }
private ObservableCollection> _medals;
+ public RelayCommand NavigateCommand { get; }
+
+ public event EventHandler NavigationRequested;
+
public ObservableCollection> Medals
{
get => _medals;
@@ -27,6 +37,11 @@ public ObservableCollection> Medals
}
}
+ private void NavigateToAnotherView(long parameter)
+ {
+ NavigationRequested?.Invoke(this, parameter);
+ }
+
public void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
{
OnPropertyChanged(propertyName);
diff --git a/src/OpenSpartan.Workshop/Views/HomeView.xaml b/src/OpenSpartan.Workshop/Views/HomeView.xaml
index d2f1d80..2fdcd71 100644
--- a/src/OpenSpartan.Workshop/Views/HomeView.xaml
+++ b/src/OpenSpartan.Workshop/Views/HomeView.xaml
@@ -14,18 +14,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/src/OpenSpartan.Workshop/Views/MedalMatchesView.xaml b/src/OpenSpartan.Workshop/Views/MedalMatchesView.xaml
new file mode 100644
index 0000000..0af45ab
--- /dev/null
+++ b/src/OpenSpartan.Workshop/Views/MedalMatchesView.xaml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/OpenSpartan.Workshop/Views/MedalMatchesView.xaml.cs b/src/OpenSpartan.Workshop/Views/MedalMatchesView.xaml.cs
new file mode 100644
index 0000000..33e8fa5
--- /dev/null
+++ b/src/OpenSpartan.Workshop/Views/MedalMatchesView.xaml.cs
@@ -0,0 +1,35 @@
+using System.Linq;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Navigation;
+using OpenSpartan.Workshop.Shared;
+using OpenSpartan.Workshop.ViewModels;
+
+namespace OpenSpartan.Workshop.Views
+{
+ ///
+ /// An empty page that can be used on its own or navigated to within a Frame.
+ ///
+ public sealed partial class MedalMatchesView : Page
+ {
+ public MedalMatchesView()
+ {
+ this.InitializeComponent();
+ }
+
+ protected override void OnNavigatedTo(NavigationEventArgs e)
+ {
+ base.OnNavigatedTo(e);
+
+ // Access the parameter using QueryParameter
+ if (e.Parameter != null && e.Parameter is long parameter)
+ {
+ // Make sure to reset the match list.
+ MedalMatchesViewModel.Instance.MatchList = new CommunityToolkit.WinUI.IncrementalLoadingCollection();
+
+ UserContextManager.PopulateMedalMatchData(parameter);
+
+ MedalMatchesViewModel.Instance.Medal = MedalsViewModel.Instance.Medals.SelectMany(group => group).FirstOrDefault(i => i.NameId == parameter);
+ }
+ }
+ }
+}
diff --git a/src/OpenSpartan.Workshop/Views/MedalsView.xaml b/src/OpenSpartan.Workshop/Views/MedalsView.xaml
index d6aacb4..7c55f35 100644
--- a/src/OpenSpartan.Workshop/Views/MedalsView.xaml
+++ b/src/OpenSpartan.Workshop/Views/MedalsView.xaml
@@ -6,6 +6,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
+ x:Name="MedalsViewPage"
xmlns:converters="using:OpenSpartan.Workshop.Converters"
xmlns:viewmodels="using:OpenSpartan.Workshop.ViewModels"
DataContext="{x:Bind viewmodels:MedalsViewModel.Instance}"
@@ -43,7 +44,7 @@
-
+
@@ -66,6 +67,16 @@
+
+
+
diff --git a/src/OpenSpartan.Workshop/Views/MedalsView.xaml.cs b/src/OpenSpartan.Workshop/Views/MedalsView.xaml.cs
index 8be8e14..48e7d88 100644
--- a/src/OpenSpartan.Workshop/Views/MedalsView.xaml.cs
+++ b/src/OpenSpartan.Workshop/Views/MedalsView.xaml.cs
@@ -1,5 +1,8 @@
+using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Media;
using OpenSpartan.Workshop.Shared;
+using OpenSpartan.Workshop.ViewModels;
namespace OpenSpartan.Workshop.Views
{
@@ -8,6 +11,17 @@ public sealed partial class MedalsView : Page
public MedalsView()
{
InitializeComponent();
+ this.Loaded += MedalsView_Loaded;
+ }
+
+ private void MedalsView_Loaded(object sender, RoutedEventArgs e)
+ {
+ ((MedalsViewModel)this.DataContext).NavigationRequested += ViewModel_NavigationRequested;
+ }
+
+ private void ViewModel_NavigationRequested(object sender, long e)
+ {
+ Frame.Navigate(typeof(MedalMatchesView), e);
}
private async void btnRefreshMedals_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
@@ -15,5 +29,37 @@ private async void btnRefreshMedals_Click(object sender, Microsoft.UI.Xaml.Route
await UserContextManager.PopulateServiceRecordData();
await UserContextManager.PopulateMedalData();
}
+
+ private void MedalGridItem_Tapped(object sender, Microsoft.UI.Xaml.Input.TappedRoutedEventArgs e)
+ {
+ var teachingTip = FindChildElement((DependencyObject)sender);
+ if (teachingTip != null)
+ {
+ teachingTip.IsOpen = true;
+ }
+ }
+
+ public static T FindChildElement(DependencyObject parent) where T : FrameworkElement
+ {
+ if (parent == null)
+ return null;
+
+ int childCount = VisualTreeHelper.GetChildrenCount(parent);
+ for (int i = 0; i < childCount; i++)
+ {
+ DependencyObject child = VisualTreeHelper.GetChild(parent, i);
+
+ if (child is T typedChild)
+ {
+ return typedChild;
+ }
+
+ T result = FindChildElement(child);
+ if (result != null)
+ return result;
+ }
+
+ return null;
+ }
}
}