1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-19 04:22:55 +08:00

Merge pull request #29157 from frenzibyte/user-profile-daily-challenge-streak-display

Add daily challenge stats display to user profile overlay
This commit is contained in:
Dean Herbert 2024-08-03 23:57:19 +09:00 committed by GitHub
commit 7765d0ff70
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 525 additions and 13 deletions

View File

@ -0,0 +1,64 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays;
using osu.Game.Overlays.Profile;
using osu.Game.Overlays.Profile.Header.Components;
using osu.Game.Rulesets.Osu;
using osuTK;
namespace osu.Game.Tests.Visual.Online
{
public partial class TestSceneUserProfileDailyChallenge : OsuManualInputManagerTestScene
{
[Cached]
public readonly Bindable<UserProfileData?> User = new Bindable<UserProfileData?>(new UserProfileData(new APIUser(), new OsuRuleset().RulesetInfo));
[Cached]
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Pink);
protected override void LoadComplete()
{
base.LoadComplete();
DailyChallengeStatsDisplay display = null!;
AddSliderStep("daily", 0, 999, 2, v => update(s => s.DailyStreakCurrent = v));
AddSliderStep("daily best", 0, 999, 2, v => update(s => s.DailyStreakBest = v));
AddSliderStep("weekly", 0, 250, 1, v => update(s => s.WeeklyStreakCurrent = v));
AddSliderStep("weekly best", 0, 250, 1, v => update(s => s.WeeklyStreakBest = v));
AddSliderStep("top 10%", 0, 999, 0, v => update(s => s.Top10PercentPlacements = v));
AddSliderStep("top 50%", 0, 999, 0, v => update(s => s.Top50PercentPlacements = v));
AddSliderStep("playcount", 0, 999, 0, v => update(s => s.PlayCount = v));
AddStep("create", () =>
{
Clear();
Add(new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colourProvider.Background2,
});
Add(display = new DailyChallengeStatsDisplay
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Scale = new Vector2(1f),
User = { BindTarget = User },
});
});
AddStep("hover", () => InputManager.MoveMouseTo(display));
}
private void update(Action<APIUserDailyChallengeStatistics> change)
{
change.Invoke(User.Value!.User.DailyChallengeStatistics);
User.Value = new UserProfileData(User.Value.User, User.Value.Ruleset);
}
}
}

View File

@ -4,6 +4,7 @@
using System;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Online.API;
@ -24,7 +25,17 @@ namespace osu.Game.Tests.Visual.Online
[SetUpSteps]
public void SetUp()
{
AddStep("create profile overlay", () => Child = profile = new UserProfileOverlay());
AddStep("create profile overlay", () =>
{
profile = new UserProfileOverlay();
Child = new DependencyProvidingContainer
{
RelativeSizeAxes = Axes.Both,
CachedDependencies = new (Type, object)[] { (typeof(UserProfileOverlay), profile) },
Child = profile,
};
});
}
[Test]
@ -131,6 +142,7 @@ namespace osu.Game.Tests.Visual.Online
CountryCode = CountryCode.JP,
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg",
ProfileHue = hue,
PlayMode = "osu",
});
return true;
}
@ -174,6 +186,7 @@ namespace osu.Game.Tests.Visual.Online
CountryCode = CountryCode.JP,
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg",
ProfileHue = hue,
PlayMode = "osu",
}));
int hue2 = 0;
@ -189,6 +202,7 @@ namespace osu.Game.Tests.Visual.Online
CountryCode = CountryCode.JP,
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg",
ProfileHue = hue2,
PlayMode = "osu",
}));
}
@ -282,6 +296,15 @@ namespace osu.Game.Tests.Visual.Online
ImageUrlLowRes = "https://assets.ppy.sh/profile-badges/contributor.png",
},
},
DailyChallengeStatistics = new APIUserDailyChallengeStatistics
{
DailyStreakCurrent = 231,
WeeklyStreakCurrent = 18,
DailyStreakBest = 370,
WeeklyStreakBest = 51,
Top10PercentPlacements = 345,
Top50PercentPlacements = 427,
},
Title = "osu!volunteer",
Colour = "ff0000",
Achievements = Array.Empty<APIUserAchievement>(),

View File

@ -272,6 +272,9 @@ namespace osu.Game.Online.API.Requests.Responses
[JsonProperty("groups")]
public APIUserGroup[] Groups;
[JsonProperty("daily_challenge_user_stats")]
public APIUserDailyChallengeStatistics DailyChallengeStatistics = new APIUserDailyChallengeStatistics();
public override string ToString() => Username;
/// <summary>

View File

@ -0,0 +1,41 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using Newtonsoft.Json;
namespace osu.Game.Online.API.Requests.Responses
{
public class APIUserDailyChallengeStatistics
{
[JsonProperty("user_id")]
public int UserID;
[JsonProperty("daily_streak_best")]
public int DailyStreakBest;
[JsonProperty("daily_streak_current")]
public int DailyStreakCurrent;
[JsonProperty("weekly_streak_best")]
public int WeeklyStreakBest;
[JsonProperty("weekly_streak_current")]
public int WeeklyStreakCurrent;
[JsonProperty("top_10p_placements")]
public int Top10PercentPlacements;
[JsonProperty("top_50p_placements")]
public int Top50PercentPlacements;
[JsonProperty("playcount")]
public int PlayCount;
[JsonProperty("last_update")]
public DateTimeOffset? LastUpdate;
[JsonProperty("last_weekly_streak")]
public DateTimeOffset? LastWeeklyStreak;
}
}

View File

@ -0,0 +1,121 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Scoring;
namespace osu.Game.Overlays.Profile.Header.Components
{
public partial class DailyChallengeStatsDisplay : CompositeDrawable, IHasCustomTooltip<DailyChallengeTooltipData>
{
public readonly Bindable<UserProfileData?> User = new Bindable<UserProfileData?>();
public DailyChallengeTooltipData? TooltipContent { get; private set; }
private OsuSpriteText dailyPlayCount = null!;
[Resolved]
private OsuColour colours { get; set; } = null!;
[Resolved]
private OverlayColourProvider colourProvider { get; set; } = null!;
[BackgroundDependencyLoader]
private void load()
{
AutoSizeAxes = Axes.Both;
CornerRadius = 5;
Masking = true;
InternalChildren = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colourProvider.Background4,
},
new FillFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Padding = new MarginPadding(5f),
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Children = new Drawable[]
{
new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(size: 12))
{
AutoSizeAxes = Axes.Both,
// can't use this because osu-web does weird stuff with \\n.
// Text = UsersStrings.ShowDailyChallengeTitle.,
Text = "Daily\nChallenge",
Margin = new MarginPadding { Horizontal = 5f, Bottom = 2f },
},
new Container
{
AutoSizeAxes = Axes.X,
RelativeSizeAxes = Axes.Y,
CornerRadius = 5f,
Masking = true,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colourProvider.Background6,
},
dailyPlayCount = new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
UseFullGlyphHeight = false,
Colour = colourProvider.Content2,
Margin = new MarginPadding { Horizontal = 10f, Vertical = 5f },
},
}
},
}
},
};
}
protected override void LoadComplete()
{
base.LoadComplete();
User.BindValueChanged(_ => updateDisplay(), true);
}
private void updateDisplay()
{
if (User.Value == null || User.Value.Ruleset.OnlineID != 0)
{
Hide();
return;
}
APIUserDailyChallengeStatistics stats = User.Value.User.DailyChallengeStatistics;
dailyPlayCount.Text = UsersStrings.ShowDailyChallengeUnitDay(stats.PlayCount.ToLocalisableString("N0"));
dailyPlayCount.Colour = colours.ForRankingTier(tierForPlayCount(stats.PlayCount));
TooltipContent = new DailyChallengeTooltipData(colourProvider, stats);
Show();
static RankingTier tierForPlayCount(int playCount) => DailyChallengeStatsTooltip.TierForDaily(playCount / 3);
}
public ITooltip<DailyChallengeTooltipData> GetCustomTooltip() => new DailyChallengeStatsTooltip();
}
}

View File

@ -0,0 +1,241 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Effects;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Scoring;
using osuTK;
using Box = osu.Framework.Graphics.Shapes.Box;
using Color4 = osuTK.Graphics.Color4;
namespace osu.Game.Overlays.Profile.Header.Components
{
public partial class DailyChallengeStatsTooltip : VisibilityContainer, ITooltip<DailyChallengeTooltipData>
{
private StreakPiece currentDaily = null!;
private StreakPiece currentWeekly = null!;
private StatisticsPiece bestDaily = null!;
private StatisticsPiece bestWeekly = null!;
private StatisticsPiece topTen = null!;
private StatisticsPiece topFifty = null!;
private Box topBackground = null!;
private Box background = null!;
[Resolved]
private OsuColour colours { get; set; } = null!;
[BackgroundDependencyLoader]
private void load()
{
AutoSizeAxes = Axes.Both;
CornerRadius = 20f;
Masking = true;
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
Colour = Color4.Black.Opacity(0.25f),
Radius = 30f,
};
Children = new Drawable[]
{
background = new Box
{
RelativeSizeAxes = Axes.Both,
},
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new Container
{
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
topBackground = new Box
{
RelativeSizeAxes = Axes.Both,
},
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Padding = new MarginPadding(15f),
Spacing = new Vector2(30f),
Children = new[]
{
currentDaily = new StreakPiece(UsersStrings.ShowDailyChallengeDailyStreakCurrent),
currentWeekly = new StreakPiece(UsersStrings.ShowDailyChallengeWeeklyStreakCurrent),
}
},
}
},
new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Padding = new MarginPadding(15f),
Spacing = new Vector2(10f),
Children = new[]
{
bestDaily = new StatisticsPiece(UsersStrings.ShowDailyChallengeDailyStreakBest),
bestWeekly = new StatisticsPiece(UsersStrings.ShowDailyChallengeWeeklyStreakBest),
topTen = new StatisticsPiece(UsersStrings.ShowDailyChallengeTop10pPlacements),
topFifty = new StatisticsPiece(UsersStrings.ShowDailyChallengeTop50pPlacements),
}
},
}
}
};
}
public void SetContent(DailyChallengeTooltipData content)
{
var statistics = content.Statistics;
var colourProvider = content.ColourProvider;
background.Colour = colourProvider.Background4;
topBackground.Colour = colourProvider.Background5;
currentDaily.Value = UsersStrings.ShowDailyChallengeUnitDay(content.Statistics.DailyStreakCurrent.ToLocalisableString(@"N0"));
currentDaily.ValueColour = colours.ForRankingTier(TierForDaily(statistics.DailyStreakCurrent));
currentWeekly.Value = UsersStrings.ShowDailyChallengeUnitWeek(statistics.WeeklyStreakCurrent.ToLocalisableString(@"N0"));
currentWeekly.ValueColour = colours.ForRankingTier(TierForWeekly(statistics.WeeklyStreakCurrent));
bestDaily.Value = UsersStrings.ShowDailyChallengeUnitDay(statistics.DailyStreakBest.ToLocalisableString(@"N0"));
bestDaily.ValueColour = colours.ForRankingTier(TierForDaily(statistics.DailyStreakBest));
bestWeekly.Value = UsersStrings.ShowDailyChallengeUnitWeek(statistics.WeeklyStreakBest.ToLocalisableString(@"N0"));
bestWeekly.ValueColour = colours.ForRankingTier(TierForWeekly(statistics.WeeklyStreakBest));
topTen.Value = statistics.Top10PercentPlacements.ToLocalisableString(@"N0");
topTen.ValueColour = colourProvider.Content2;
topFifty.Value = statistics.Top50PercentPlacements.ToLocalisableString(@"N0");
topFifty.ValueColour = colourProvider.Content2;
}
// reference: https://github.com/ppy/osu-web/blob/8206e0e91eeea80ccf92f0586561346dd40e085e/resources/js/profile-page/daily-challenge.tsx#L13-L43
public static RankingTier TierForDaily(int daily)
{
if (daily > 360)
return RankingTier.Lustrous;
if (daily > 240)
return RankingTier.Radiant;
if (daily > 120)
return RankingTier.Rhodium;
if (daily > 60)
return RankingTier.Platinum;
if (daily > 30)
return RankingTier.Gold;
if (daily > 10)
return RankingTier.Silver;
if (daily > 5)
return RankingTier.Bronze;
return RankingTier.Iron;
}
public static RankingTier TierForWeekly(int weekly) => TierForDaily((weekly - 1) * 7);
protected override void PopIn() => this.FadeIn(200, Easing.OutQuint);
protected override void PopOut() => this.FadeOut(200, Easing.OutQuint);
public void Move(Vector2 pos) => Position = pos;
private partial class StreakPiece : FillFlowContainer
{
private readonly OsuSpriteText valueText;
public LocalisableString Value
{
set => valueText.Text = value;
}
public ColourInfo ValueColour
{
set => valueText.Colour = value;
}
public StreakPiece(LocalisableString title)
{
AutoSizeAxes = Axes.Both;
Direction = FillDirection.Vertical;
Children = new Drawable[]
{
new OsuSpriteText
{
Font = OsuFont.GetFont(size: 12),
Text = title,
},
valueText = new OsuSpriteText
{
Font = OsuFont.GetFont(size: 40, weight: FontWeight.Light),
}
};
}
}
private partial class StatisticsPiece : CompositeDrawable
{
private readonly OsuSpriteText valueText;
public LocalisableString Value
{
set => valueText.Text = value;
}
public ColourInfo ValueColour
{
set => valueText.Colour = value;
}
public StatisticsPiece(LocalisableString title)
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
InternalChildren = new Drawable[]
{
new OsuSpriteText
{
Font = OsuFont.GetFont(size: 12),
Text = title,
},
valueText = new OsuSpriteText
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
Font = OsuFont.GetFont(size: 12),
}
};
}
}
}
public record DailyChallengeTooltipData(OverlayColourProvider ColourProvider, APIUserDailyChallengeStatistics Statistics);
}

View File

@ -44,22 +44,41 @@ namespace osu.Game.Overlays.Profile.Header.Components
Spacing = new Vector2(0, 15),
Children = new Drawable[]
{
new FillFlowContainer
new GridContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(20),
Children = new Drawable[]
ColumnDimensions = new[]
{
detailGlobalRank = new ProfileValueDisplay(true)
new Dimension(GridSizeMode.AutoSize),
new Dimension(GridSizeMode.Absolute, 20),
new Dimension(),
new Dimension(GridSizeMode.AutoSize),
},
RowDimensions = new[]
{
new Dimension(GridSizeMode.AutoSize),
},
Content = new[]
{
new[]
{
Title = UsersStrings.ShowRankGlobalSimple,
},
detailCountryRank = new ProfileValueDisplay(true)
{
Title = UsersStrings.ShowRankCountrySimple,
},
detailGlobalRank = new ProfileValueDisplay(true)
{
Title = UsersStrings.ShowRankGlobalSimple,
},
Empty(),
detailCountryRank = new ProfileValueDisplay(true)
{
Title = UsersStrings.ShowRankCountrySimple,
},
new DailyChallengeStatsDisplay
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
User = { BindTarget = User },
}
}
}
},
new Container

View File

@ -36,7 +36,7 @@
</PackageReference>
<PackageReference Include="Realm" Version="11.5.0" />
<PackageReference Include="ppy.osu.Framework" Version="2024.720.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2024.731.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2024.802.0" />
<PackageReference Include="Sentry" Version="4.3.0" />
<!-- Held back due to 0.34.0 failing AOT compilation on ZstdSharp.dll dependency. -->
<PackageReference Include="SharpCompress" Version="0.36.0" />