1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-02 07:25:05 +08:00

Merge pull request #29188 from bdach/daily-challenge/better-messaging

Add notification on daily challenge conclusion & start of new one
This commit is contained in:
Dean Herbert 2024-07-31 13:35:25 +09:00 committed by GitHub
commit e9ed9ff58b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 194 additions and 15 deletions

View File

@ -4,16 +4,34 @@
using System; using System;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Screens;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Online.Metadata;
using osu.Game.Online.Rooms; using osu.Game.Online.Rooms;
using osu.Game.Overlays;
using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Tests.Resources; using osu.Game.Tests.Resources;
using osu.Game.Tests.Visual.Metadata;
using osu.Game.Tests.Visual.OnlinePlay; using osu.Game.Tests.Visual.OnlinePlay;
namespace osu.Game.Tests.Visual.DailyChallenge namespace osu.Game.Tests.Visual.DailyChallenge
{ {
public partial class TestSceneDailyChallenge : OnlinePlayTestScene 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] [Test]
public void TestDailyChallenge() public void TestDailyChallenge()
{ {
@ -36,5 +54,33 @@ namespace osu.Game.Tests.Visual.DailyChallenge
AddStep("add room", () => API.Perform(new CreateRoomRequest(room))); AddStep("add room", () => API.Perform(new CreateRoomRequest(room)));
AddStep("push screen", () => LoadScreen(new Screens.OnlinePlay.DailyChallenge.DailyChallenge(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);
}
} }
} }

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
@ -10,6 +11,7 @@ using osu.Game.Localisation;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Online.Metadata; using osu.Game.Online.Metadata;
using osu.Game.Online.Rooms; using osu.Game.Online.Rooms;
using osu.Game.Overlays;
using osu.Game.Screens.Menu; using osu.Game.Screens.Menu;
using osuTK.Input; using osuTK.Input;
using Color4 = osuTK.Graphics.Color4; using Color4 = osuTK.Graphics.Color4;
@ -39,8 +41,6 @@ namespace osu.Game.Tests.Visual.UserInterface
[Test] [Test]
public void TestDailyChallengeButton() public void TestDailyChallengeButton()
{ {
AddStep("beatmap of the day not active", () => metadataClient.DailyChallengeUpdated(null));
AddStep("set up API", () => dummyAPI.HandleRequest = req => AddStep("set up API", () => dummyAPI.HandleRequest = req =>
{ {
switch (req) switch (req)
@ -67,17 +67,45 @@ namespace osu.Game.Tests.Visual.UserInterface
} }
}); });
AddStep("add button", () => Child = new DailyChallengeButton(@"button-default-select", new Color4(102, 68, 204, 255), _ => { }, 0, Key.D) NotificationOverlay notificationOverlay = null!;
{ DependencyProvidingContainer buttonContainer = null!;
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
ButtonSystemState = ButtonSystemState.TopLevel,
});
AddStep("beatmap of the day active", () => metadataClient.DailyChallengeUpdated(new DailyChallengeInfo AddStep("beatmap of the day active", () => metadataClient.DailyChallengeUpdated(new DailyChallengeInfo
{ {
RoomID = 1234, 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));
} }
} }
} }

View File

@ -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}";
}
}

View File

@ -23,6 +23,7 @@ using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Metadata; using osu.Game.Online.Metadata;
using osu.Game.Online.Rooms; using osu.Game.Online.Rooms;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Screens.OnlinePlay.DailyChallenge;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
using osuTK.Input; using osuTK.Input;
@ -44,6 +45,9 @@ namespace osu.Game.Screens.Menu
[Resolved] [Resolved]
private IAPIProvider api { get; set; } = null!; 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) public DailyChallengeButton(string sampleName, Color4 colour, Action<MainMenuButton>? clickAction = null, params Key[] triggerKeys)
: base(ButtonSystemStrings.DailyChallenge, sampleName, OsuIcon.DailyChallenge, colour, clickAction, triggerKeys) : base(ButtonSystemStrings.DailyChallenge, sampleName, OsuIcon.DailyChallenge, colour, clickAction, triggerKeys)
{ {
@ -100,7 +104,8 @@ namespace osu.Game.Screens.Menu
{ {
base.LoadComplete(); base.LoadComplete();
info.BindValueChanged(updateDisplay, true); info.BindValueChanged(_ => dailyChallengeChanged(postNotification: true));
dailyChallengeChanged(postNotification: false);
} }
protected override void Update() protected override void Update()
@ -126,27 +131,30 @@ namespace osu.Game.Screens.Menu
} }
} }
private void updateDisplay(ValueChangedEvent<DailyChallengeInfo?> info) private void dailyChallengeChanged(bool postNotification)
{ {
UpdateState(); UpdateState();
scheduledCountdownUpdate?.Cancel(); scheduledCountdownUpdate?.Cancel();
scheduledCountdownUpdate = null; scheduledCountdownUpdate = null;
if (info.NewValue == null) if (info.Value == null)
{ {
Room = null; Room = null;
cover.OnlineInfo = TooltipContent = null; cover.OnlineInfo = TooltipContent = null;
} }
else else
{ {
var roomRequest = new GetRoomRequest(info.NewValue.Value.RoomID); var roomRequest = new GetRoomRequest(info.Value.Value.RoomID);
roomRequest.Success += room => roomRequest.Success += room =>
{ {
Room = room; Room = room;
cover.OnlineInfo = TooltipContent = room.Playlist.FirstOrDefault()?.Beatmap.BeatmapSet as APIBeatmapSet; cover.OnlineInfo = TooltipContent = room.Playlist.FirstOrDefault()?.Beatmap.BeatmapSet as APIBeatmapSet;
if (postNotification)
notificationOverlay?.Post(new NewDailyChallengeNotification(room));
updateCountdown(); updateCountdown();
Scheduler.AddDelayed(updateCountdown, 1000, true); Scheduler.AddDelayed(updateCountdown, 1000, true);
}; };

View File

@ -30,6 +30,7 @@ using osu.Game.Online.Metadata;
using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms; using osu.Game.Online.Rooms;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Overlays.Notifications;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Components;
@ -54,6 +55,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
private readonly Bindable<IReadOnlyList<Mod>> userMods = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>()); private readonly Bindable<IReadOnlyList<Mod>> userMods = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
private readonly IBindable<APIState> apiState = new Bindable<APIState>(); private readonly IBindable<APIState> apiState = new Bindable<APIState>();
private readonly IBindable<DailyChallengeInfo?> dailyChallengeInfo = new Bindable<DailyChallengeInfo?>();
private OnlinePlayScreenWaveContainer waves = null!; private OnlinePlayScreenWaveContainer waves = null!;
private DailyChallengeLeaderboard leaderboard = null!; private DailyChallengeLeaderboard leaderboard = null!;
@ -98,6 +100,9 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
[Resolved] [Resolved]
private PreviewTrackManager previewTrackManager { get; set; } = null!; private PreviewTrackManager previewTrackManager { get; set; } = null!;
[Resolved]
private INotificationOverlay? notificationOverlay { get; set; }
public override bool DisallowExternalBeatmapRulesetChanges => true; public override bool DisallowExternalBeatmapRulesetChanges => true;
public override bool? ApplyModTrackAdjustments => true; public override bool? ApplyModTrackAdjustments => true;
@ -336,6 +341,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
} }
metadataClient.MultiplayerRoomScoreSet += onRoomScoreSet; metadataClient.MultiplayerRoomScoreSet += onRoomScoreSet;
dailyChallengeInfo.BindTo(metadataClient.DailyChallengeInfo);
((IBindable<MultiplayerScore?>)breakdown.UserBestScore).BindTo(leaderboard.UserBestScore); ((IBindable<MultiplayerScore?>)breakdown.UserBestScore).BindTo(leaderboard.UserBestScore);
} }
@ -388,6 +394,8 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
apiState.BindTo(API.State); apiState.BindTo(API.State);
apiState.BindValueChanged(onlineStateChanged, true); apiState.BindValueChanged(onlineStateChanged, true);
dailyChallengeInfo.BindValueChanged(dailyChallengeChanged);
} }
private void trySetDailyChallengeBeatmap() private void trySetDailyChallengeBeatmap()
@ -405,9 +413,17 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
Schedule(forcefullyExit); 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() 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 // This is temporary since we don't currently have a way to force screens to be exited
// See also: `OnlinePlayScreen.forcefullyExit()` // See also: `OnlinePlayScreen.forcefullyExit()`

View File

@ -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;
}
}
}

View File

@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual.Metadata
public override IBindableDictionary<int, UserPresence> UserStates => userStates; public override IBindableDictionary<int, UserPresence> UserStates => userStates;
private readonly BindableDictionary<int, UserPresence> userStates = new BindableDictionary<int, UserPresence>(); 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?>(); private readonly Bindable<DailyChallengeInfo?> dailyChallengeInfo = new Bindable<DailyChallengeInfo?>();
[Resolved] [Resolved]
@ -88,7 +88,14 @@ namespace osu.Game.Tests.Visual.Metadata
} }
public override Task<MultiplayerPlaylistItemStats[]> BeginWatchingMultiplayerRoom(long id) 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; public override Task EndWatchingMultiplayerRoom(long id) => Task.CompletedTask;
} }