From 17f5d58be2fcf5c2ead8d8b76a7b2ae5281d1197 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 28 Jul 2024 05:24:29 +0300 Subject: [PATCH] Add daily challenge streak display and tooltip --- ...tSceneUserProfileDailyChallengeOverview.cs | 63 +++++ .../Components/DailyChallengeStreakDisplay.cs | 112 ++++++++ .../Components/DailyChallengeStreakTooltip.cs | 243 ++++++++++++++++++ 3 files changed, 418 insertions(+) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallengeOverview.cs create mode 100644 osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs create mode 100644 osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallengeOverview.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallengeOverview.cs new file mode 100644 index 0000000000..e2d26f222c --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallengeOverview.cs @@ -0,0 +1,63 @@ +// Copyright (c) ppy Pty Ltd . 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 User = new Bindable(new UserProfileData(new APIUser(), new OsuRuleset().RulesetInfo)); + + [Cached] + private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Pink); + + protected override void LoadComplete() + { + base.LoadComplete(); + + DailyChallengeStreakDisplay 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)); + AddStep("create", () => + { + Clear(); + Add(new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background2, + }); + Add(display = new DailyChallengeStreakDisplay + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(1f), + User = { BindTarget = User }, + }); + }); + AddStep("hover", () => InputManager.MoveMouseTo(display)); + } + + private void update(Action change) + { + change.Invoke(User.Value!.User.DailyChallengeStatistics); + User.Value = new UserProfileData(User.Value.User, User.Value.Ruleset); + } + } +} diff --git a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs new file mode 100644 index 0000000000..2d9b107367 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakDisplay.cs @@ -0,0 +1,112 @@ +// Copyright (c) ppy Pty Ltd . 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.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; + +namespace osu.Game.Overlays.Profile.Header.Components +{ + public partial class DailyChallengeStreakDisplay : CompositeDrawable, IHasCustomTooltip + { + public readonly Bindable User = new Bindable(); + + public APIUserDailyChallengeStatistics? TooltipContent { get; private set; } + + private OsuSpriteText dailyStreak = null!; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + + [Resolved] + private OsuColour colours { 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, + // 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, + }, + dailyStreak = 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) + { + dailyStreak.Text = "-"; + return; + } + + var statistics = User.Value.User.DailyChallengeStatistics; + // dailyStreak.Text = UsersStrings.ShowDailyChallengeUnitDay(statistics.DailyStreakCurrent); + dailyStreak.Text = $"{statistics.DailyStreakCurrent}d"; + dailyStreak.Colour = colours.ForRankingTier(DailyChallengeStreakTooltip.TierForDaily(statistics.DailyStreakCurrent)); + TooltipContent = statistics; + } + + public ITooltip GetCustomTooltip() => new DailyChallengeStreakTooltip(colourProvider); + } +} diff --git a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs new file mode 100644 index 0000000000..a95de8cefd --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStreakTooltip.cs @@ -0,0 +1,243 @@ +// Copyright (c) ppy Pty Ltd . 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.LocalisationExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Localisation; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Scoring; +using osuTK; + +namespace osu.Game.Overlays.Profile.Header.Components +{ + public partial class DailyChallengeStreakTooltip : VisibilityContainer, ITooltip + { + [Cached] + private readonly OverlayColourProvider colourProvider; + + private StreakPiece currentDaily = null!; + private StreakPiece currentWeekly = null!; + private StatisticsPiece bestDaily = null!; + private StatisticsPiece bestWeekly = null!; + private StatisticsPiece topTen = null!; + private StatisticsPiece topFifty = null!; + + [Resolved] + private OsuColour colours { get; set; } = null!; + + public DailyChallengeStreakTooltip(OverlayColourProvider colourProvider) + { + this.colourProvider = colourProvider; + } + + [BackgroundDependencyLoader] + private void load() + { + AutoSizeAxes = Axes.Both; + CornerRadius = 20f; + Masking = true; + + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background4, + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new Container + { + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background5, + }, + 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), + currentDaily = new StreakPiece("Current Daily Streak"), + currentWeekly = new StreakPiece("Current Weekly Streak"), + } + }, + } + }, + 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), + bestDaily = new StatisticsPiece("Best Daily Streak"), + bestWeekly = new StatisticsPiece("Best Weekly Streak"), + topTen = new StatisticsPiece("Top 10% Placements"), + topFifty = new StatisticsPiece("Top 50% Placements"), + } + }, + } + } + }; + } + + public void SetContent(APIUserDailyChallengeStatistics content) + { + // currentDaily.Value = UsersStrings.ShowDailyChallengeUnitDay(content.DailyStreakCurrent.ToLocalisableString(@"N0")); + currentDaily.Value = $"{content.DailyStreakCurrent:N0}d"; + currentDaily.ValueColour = colours.ForRankingTier(TierForDaily(content.DailyStreakCurrent)); + + // currentWeekly.Value = UsersStrings.ShowDailyChallengeUnitWeek(content.WeeklyStreakCurrent.ToLocalisableString(@"N0")); + currentWeekly.Value = $"{content.WeeklyStreakCurrent:N0}w"; + currentWeekly.ValueColour = colours.ForRankingTier(TierForWeekly(content.WeeklyStreakCurrent)); + + // bestDaily.Value = UsersStrings.ShowDailyChallengeUnitDay(content.DailyStreakBest.ToLocalisableString(@"N0")); + bestDaily.Value = $"{content.DailyStreakBest:N0}d"; + bestDaily.ValueColour = colours.ForRankingTier(TierForDaily(content.DailyStreakBest)); + + // bestWeekly.Value = UsersStrings.ShowDailyChallengeUnitWeek(content.WeeklyStreakBest.ToLocalisableString(@"N0")); + bestWeekly.Value = $"{content.WeeklyStreakBest:N0}w"; + bestWeekly.ValueColour = colours.ForRankingTier(TierForWeekly(content.WeeklyStreakBest)); + + topTen.Value = content.Top10PercentPlacements.ToLocalisableString(@"N0"); + topFifty.Value = content.Top50PercentPlacements.ToLocalisableString(@"N0"); + } + + // 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 + { + // Colour = colour + 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), + } + }; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + valueText.Colour = colourProvider.Content2; + } + } + } +}