Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add notification on daily challenge conclusion & start of new one #29188

Merged
merged 9 commits into from
Jul 31, 2024
46 changes: 46 additions & 0 deletions osu.Game.Tests/Visual/DailyChallenge/TestSceneDailyChallenge.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,34 @@
using System;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Screens;
using osu.Game.Online.API;
using osu.Game.Online.Metadata;
using osu.Game.Online.Rooms;
using osu.Game.Overlays;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Tests.Resources;
using osu.Game.Tests.Visual.Metadata;
using osu.Game.Tests.Visual.OnlinePlay;

namespace osu.Game.Tests.Visual.DailyChallenge
{
public partial class TestSceneDailyChallenge : OnlinePlayTestScene
{
[Cached(typeof(MetadataClient))]
private TestMetadataClient metadataClient = new TestMetadataClient();

[Cached(typeof(INotificationOverlay))]
private NotificationOverlay notificationOverlay = new NotificationOverlay();

[BackgroundDependencyLoader]
private void load()
{
base.Content.Add(notificationOverlay);
base.Content.Add(metadataClient);
}

[Test]
public void TestDailyChallenge()
{
Expand All @@ -36,5 +54,33 @@ public void TestDailyChallenge()
AddStep("add room", () => API.Perform(new CreateRoomRequest(room)));
AddStep("push screen", () => LoadScreen(new Screens.OnlinePlay.DailyChallenge.DailyChallenge(room)));
}

[Test]
public void TestNotifications()
{
var room = new Room
{
RoomID = { Value = 1234 },
Name = { Value = "Daily Challenge: June 4, 2024" },
Playlist =
{
new PlaylistItem(TestResources.CreateTestBeatmapSetInfo().Beatmaps.First())
{
RequiredMods = [new APIMod(new OsuModTraceable())],
AllowedMods = [new APIMod(new OsuModDoubleTime())]
}
},
EndDate = { Value = DateTimeOffset.Now.AddHours(12) },
Category = { Value = RoomCategory.DailyChallenge }
};

AddStep("add room", () => API.Perform(new CreateRoomRequest(room)));
AddStep("set daily challenge info", () => metadataClient.DailyChallengeInfo.Value = new DailyChallengeInfo { RoomID = 1234 });

Screens.OnlinePlay.DailyChallenge.DailyChallenge screen = null!;
AddStep("push screen", () => LoadScreen(screen = new Screens.OnlinePlay.DailyChallenge.DailyChallenge(room)));
AddUntilStep("wait for screen", () => screen.IsCurrentScreen());
AddStep("daily challenge ended", () => metadataClient.DailyChallengeInfo.Value = null);
}
}
}
40 changes: 34 additions & 6 deletions osu.Game.Tests/Visual/UserInterface/TestSceneMainMenuButton.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.

using System;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
Expand All @@ -10,6 +11,7 @@
using osu.Game.Online.API;
using osu.Game.Online.Metadata;
using osu.Game.Online.Rooms;
using osu.Game.Overlays;
using osu.Game.Screens.Menu;
using osuTK.Input;
using Color4 = osuTK.Graphics.Color4;
Expand Down Expand Up @@ -39,8 +41,6 @@ public void TestStandardButton()
[Test]
public void TestDailyChallengeButton()
{
AddStep("beatmap of the day not active", () => metadataClient.DailyChallengeUpdated(null));

AddStep("set up API", () => dummyAPI.HandleRequest = req =>
{
switch (req)
Expand All @@ -67,17 +67,45 @@ public void TestDailyChallengeButton()
}
});

AddStep("add button", () => Child = new DailyChallengeButton(@"button-default-select", new Color4(102, 68, 204, 255), _ => { }, 0, Key.D)
NotificationOverlay notificationOverlay = null!;
DependencyProvidingContainer buttonContainer = null!;

AddStep("beatmap of the day active", () => metadataClient.DailyChallengeUpdated(new DailyChallengeInfo
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
ButtonSystemState = ButtonSystemState.TopLevel,
RoomID = 1234,
}));
AddStep("add content", () =>
{
notificationOverlay = new NotificationOverlay();
Children = new Drawable[]
{
notificationOverlay,
buttonContainer = new DependencyProvidingContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both,
CachedDependencies = [(typeof(INotificationOverlay), notificationOverlay)],
Child = new DailyChallengeButton(@"button-default-select", new Color4(102, 68, 204, 255), _ => { }, 0, Key.D)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
ButtonSystemState = ButtonSystemState.TopLevel,
},
},
};
});
AddAssert("no notification posted", () => notificationOverlay.AllNotifications.Count(), () => Is.Zero);

AddStep("beatmap of the day not active", () => metadataClient.DailyChallengeUpdated(null));
AddAssert("no notification posted", () => notificationOverlay.AllNotifications.Count(), () => Is.Zero);

AddStep("hide button's parent", () => buttonContainer.Hide());
AddStep("beatmap of the day active", () => metadataClient.DailyChallengeUpdated(new DailyChallengeInfo
{
RoomID = 1234,
}));
AddAssert("notification posted", () => notificationOverlay.AllNotifications.Count(), () => Is.EqualTo(1));
}
}
}
29 changes: 29 additions & 0 deletions osu.Game/Localisation/DailyChallengeStrings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// 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.Localisation;

namespace osu.Game.Localisation
{
public static class DailyChallengeStrings
{
private const string prefix = @"osu.Game.Resources.Localisation.DailyChallenge";

/// <summary>
/// "Today&#39;s daily challenge has concluded – thanks for playing!
///
/// Tomorrow&#39;s challenge is now being prepared and will appear soon."
/// </summary>
public static LocalisableString ChallengeEndedNotification => new TranslatableString(getKey(@"todays_daily_challenge_has_concluded"),
@"Today's daily challenge has concluded – thanks for playing!

Tomorrow's challenge is now being prepared and will appear soon.");

/// <summary>
/// "Today&#39;s daily challenge is now live! Click here to play."
/// </summary>
public static LocalisableString ChallengeLiveNotification => new TranslatableString(getKey(@"todays_daily_challenge_is_now"), @"Today's daily challenge is now live! Click here to play.");

private static string getKey(string key) => $@"{prefix}:{key}";
}
}
16 changes: 12 additions & 4 deletions osu.Game/Screens/Menu/DailyChallengeButton.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
using osu.Game.Online.Metadata;
using osu.Game.Online.Rooms;
using osu.Game.Overlays;
using osu.Game.Screens.OnlinePlay.DailyChallenge;
using osuTK;
using osuTK.Graphics;
using osuTK.Input;
Expand All @@ -44,6 +45,9 @@ public partial class DailyChallengeButton : MainMenuButton, IHasCustomTooltip<AP
[Resolved]
private IAPIProvider api { get; set; } = null!;

[Resolved]
private INotificationOverlay? notificationOverlay { get; set; }

public DailyChallengeButton(string sampleName, Color4 colour, Action<MainMenuButton>? clickAction = null, params Key[] triggerKeys)
: base(ButtonSystemStrings.DailyChallenge, sampleName, OsuIcon.DailyChallenge, colour, clickAction, triggerKeys)
{
Expand Down Expand Up @@ -100,7 +104,8 @@ protected override void LoadComplete()
{
base.LoadComplete();

info.BindValueChanged(updateDisplay, true);
info.BindValueChanged(_ => dailyChallengeChanged(postNotification: true));
dailyChallengeChanged(postNotification: false);
}

protected override void Update()
Expand All @@ -126,27 +131,30 @@ protected override void Update()
}
}

private void updateDisplay(ValueChangedEvent<DailyChallengeInfo?> info)
private void dailyChallengeChanged(bool postNotification)
{
UpdateState();

scheduledCountdownUpdate?.Cancel();
scheduledCountdownUpdate = null;

if (info.NewValue == null)
if (info.Value == null)
{
Room = null;
cover.OnlineInfo = TooltipContent = null;
}
else
{
var roomRequest = new GetRoomRequest(info.NewValue.Value.RoomID);
var roomRequest = new GetRoomRequest(info.Value.Value.RoomID);

roomRequest.Success += room =>
{
Room = room;
cover.OnlineInfo = TooltipContent = room.Playlist.FirstOrDefault()?.Beatmap.BeatmapSet as APIBeatmapSet;

if (postNotification)
notificationOverlay?.Post(new NewDailyChallengeNotification(room));

updateCountdown();
Scheduler.AddDelayed(updateCountdown, 1000, true);
};
Expand Down
18 changes: 17 additions & 1 deletion osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms;
using osu.Game.Overlays;
using osu.Game.Overlays.Notifications;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Screens.OnlinePlay.Components;
Expand All @@ -54,6 +55,7 @@ public partial class DailyChallenge : OsuScreen, IPreviewTrackOwner
private readonly Bindable<IReadOnlyList<Mod>> userMods = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());

private readonly IBindable<APIState> apiState = new Bindable<APIState>();
private readonly IBindable<DailyChallengeInfo?> dailyChallengeInfo = new Bindable<DailyChallengeInfo?>();

private OnlinePlayScreenWaveContainer waves = null!;
private DailyChallengeLeaderboard leaderboard = null!;
Expand Down Expand Up @@ -98,6 +100,9 @@ public partial class DailyChallenge : OsuScreen, IPreviewTrackOwner
[Resolved]
private PreviewTrackManager previewTrackManager { get; set; } = null!;

[Resolved]
private INotificationOverlay? notificationOverlay { get; set; }

public override bool DisallowExternalBeatmapRulesetChanges => true;

public override bool? ApplyModTrackAdjustments => true;
Expand Down Expand Up @@ -336,6 +341,7 @@ [new MatchChatDisplay(room) { RelativeSizeAxes = Axes.Both }]
}

metadataClient.MultiplayerRoomScoreSet += onRoomScoreSet;
dailyChallengeInfo.BindTo(metadataClient.DailyChallengeInfo);

((IBindable<MultiplayerScore?>)breakdown.UserBestScore).BindTo(leaderboard.UserBestScore);
}
Expand Down Expand Up @@ -388,6 +394,8 @@ protected override void LoadComplete()

apiState.BindTo(API.State);
apiState.BindValueChanged(onlineStateChanged, true);

dailyChallengeInfo.BindValueChanged(dailyChallengeChanged);
}

private void trySetDailyChallengeBeatmap()
Expand All @@ -405,9 +413,17 @@ private void onlineStateChanged(ValueChangedEvent<APIState> state) => Schedule((
Schedule(forcefullyExit);
});

private void dailyChallengeChanged(ValueChangedEvent<DailyChallengeInfo?> change)
{
if (change.OldValue?.RoomID == room.RoomID.Value && change.NewValue == null)
{
notificationOverlay?.Post(new SimpleNotification { Text = DailyChallengeStrings.ChallengeEndedNotification });
}
}

private void forcefullyExit()
{
Logger.Log($"{this} forcefully exiting due to loss of API connection");
Logger.Log(@$"{this} forcefully exiting due to loss of API connection");

// This is temporary since we don't currently have a way to force screens to be exited
// See also: `OnlinePlayScreen.forcefullyExit()`
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// 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.Linq;
using osu.Framework.Allocation;
using osu.Framework.Screens;
using osu.Game.Beatmaps.Drawables.Cards;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Rooms;
using osu.Game.Overlays.Notifications;
using osu.Game.Screens.Menu;
using osu.Game.Localisation;

namespace osu.Game.Screens.OnlinePlay.DailyChallenge
{
public partial class NewDailyChallengeNotification : SimpleNotification
{
private readonly Room room;

private BeatmapCardNano card = null!;

public NewDailyChallengeNotification(Room room)
{
this.room = room;
}

[BackgroundDependencyLoader]
private void load(OsuGame? game)
{
Text = DailyChallengeStrings.ChallengeLiveNotification;
Content.Add(card = new BeatmapCardNano((APIBeatmapSet)room.Playlist.Single().Beatmap.BeatmapSet!));
Activated = () =>
{
game?.PerformFromScreen(s => s.Push(new DailyChallenge(room)), [typeof(MainMenu)]);
return true;
};
}

protected override void Update()
{
base.Update();
card.Width = Content.DrawWidth;
}
}
}
11 changes: 9 additions & 2 deletions osu.Game/Tests/Visual/Metadata/TestMetadataClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public partial class TestMetadataClient : MetadataClient
public override IBindableDictionary<int, UserPresence> UserStates => userStates;
private readonly BindableDictionary<int, UserPresence> userStates = new BindableDictionary<int, UserPresence>();

public override IBindable<DailyChallengeInfo?> DailyChallengeInfo => dailyChallengeInfo;
public override Bindable<DailyChallengeInfo?> DailyChallengeInfo => dailyChallengeInfo;
private readonly Bindable<DailyChallengeInfo?> dailyChallengeInfo = new Bindable<DailyChallengeInfo?>();

[Resolved]
Expand Down Expand Up @@ -88,7 +88,14 @@ public override Task DailyChallengeUpdated(DailyChallengeInfo? info)
}

public override Task<MultiplayerPlaylistItemStats[]> BeginWatchingMultiplayerRoom(long id)
=> Task.FromResult(new MultiplayerPlaylistItemStats[MultiplayerPlaylistItemStats.TOTAL_SCORE_DISTRIBUTION_BINS]);
{
var stats = new MultiplayerPlaylistItemStats[MultiplayerPlaylistItemStats.TOTAL_SCORE_DISTRIBUTION_BINS];

for (int i = 0; i < stats.Length; i++)
stats[i] = new MultiplayerPlaylistItemStats { PlaylistItemID = i };

return Task.FromResult(stats);
}

public override Task EndWatchingMultiplayerRoom(long id) => Task.CompletedTask;
}
Expand Down
Loading