1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-15 02:08:21 +08:00

Add handling of expiration

This commit is contained in:
Dean Herbert 2024-03-25 14:28:23 +08:00
parent f0614928b1
commit 057f86dd14
No known key found for this signature in database
3 changed files with 102 additions and 22 deletions

View File

@ -1,6 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Graphics; using osu.Framework.Graphics;
@ -141,5 +142,64 @@ namespace osu.Game.Tests.Visual.Menus
return !images.First().IsPresent && images.Last().IsPresent; return !images.First().IsPresent && images.Last().IsPresent;
}); });
} }
[Test]
public void TestExpiry()
{
AddStep("set multiple images, second expiring soon", () => onlineMenuBanner.Current.Value = new APIMenuContent
{
Images = new[]
{
new APIMenuImage
{
Image = @"https://assets.ppy.sh/main-menu/project-loved-2@2x.png",
Url = @"https://osu.ppy.sh/home/news/2023-12-21-project-loved-december-2023",
},
new APIMenuImage
{
Image = @"https://assets.ppy.sh/main-menu/wf2023-vote@2x.png",
Url = @"https://osu.ppy.sh/community/contests/189",
Expires = DateTimeOffset.Now.AddSeconds(2),
}
},
});
AddUntilStep("wait for first image shown", () =>
{
var images = onlineMenuBanner.ChildrenOfType<OnlineMenuBanner.MenuImage>();
if (images.Count() != 2)
return false;
return images.First().IsPresent && !images.Last().IsPresent;
});
AddUntilStep("wait for second image shown", () =>
{
var images = onlineMenuBanner.ChildrenOfType<OnlineMenuBanner.MenuImage>();
if (images.Count() != 2)
return false;
return !images.First().IsPresent && images.Last().IsPresent;
});
AddUntilStep("wait for expiry", () =>
{
return onlineMenuBanner
.ChildrenOfType<OnlineMenuBanner.MenuImage>()
.Any(i => !i.Image.IsCurrent);
});
AddUntilStep("wait for first image shown", () =>
{
var images = onlineMenuBanner.ChildrenOfType<OnlineMenuBanner.MenuImage>();
if (images.Count() != 2)
return false;
return images.First().IsPresent && !images.Last().IsPresent;
});
}
} }
} }

View File

@ -20,6 +20,10 @@ namespace osu.Game.Online.API.Requests.Responses
[JsonProperty(@"url")] [JsonProperty(@"url")]
public string Url { get; init; } = string.Empty; public string Url { get; init; } = string.Empty;
public bool IsCurrent =>
(Begins == null || Begins < DateTimeOffset.UtcNow) &&
(Expires == null || Expires > DateTimeOffset.UtcNow);
/// <summary> /// <summary>
/// The time at which this item should begin displaying. If <c>null</c>, will display immediately. /// The time at which this item should begin displaying. If <c>null</c>, will display immediately.
/// </summary> /// </summary>

View File

@ -29,7 +29,7 @@ namespace osu.Game.Screens.Menu
private const float transition_duration = 500; private const float transition_duration = 500;
private Container content = null!; private Container<MenuImage> content = null!;
private CancellationTokenSource? cancellationTokenSource; private CancellationTokenSource? cancellationTokenSource;
private int displayIndex = -1; private int displayIndex = -1;
@ -43,7 +43,7 @@ namespace osu.Game.Screens.Menu
AutoSizeDuration = transition_duration; AutoSizeDuration = transition_duration;
AutoSizeEasing = Easing.OutQuint; AutoSizeEasing = Easing.OutQuint;
InternalChild = content = new Container InternalChild = content = new Container<MenuImage>
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
@ -59,8 +59,7 @@ namespace osu.Game.Screens.Menu
{ {
base.LoadComplete(); base.LoadComplete();
Current.BindValueChanged(_ => loadNewImages(), true); Current.BindValueChanged(loadNewImages, true);
checkForUpdates(); checkForUpdates();
} }
@ -86,25 +85,24 @@ namespace osu.Game.Screens.Menu
}); });
} }
private void loadNewImages() /// <summary>
/// Takes <see cref="Current"/> and materialises and displays drawables for all valid images to be displayed.
/// </summary>
/// <param name="images"></param>
private void loadNewImages(ValueChangedEvent<APIMenuContent> images)
{ {
nextDisplay?.Cancel(); nextDisplay?.Cancel();
cancellationTokenSource?.Cancel(); cancellationTokenSource?.Cancel();
cancellationTokenSource = null; cancellationTokenSource = null;
var newContent = Current.Value;
// A better fade out would be nice, but the menu content changes *very* rarely // A better fade out would be nice, but the menu content changes *very* rarely
// so let's keep things simple for now. // so let's keep things simple for now.
content.Clear(true); content.Clear(true);
if (newContent.Images.Length == 0) LoadComponentsAsync(images.NewValue.Images.Select(i => new MenuImage(i)), loaded =>
return;
LoadComponentsAsync(newContent.Images.Select(i => new MenuImage(i)), loaded =>
{ {
if (!newContent.Equals(Current.Value)) if (!images.NewValue.Equals(Current.Value))
return; return;
// start hidden // start hidden
@ -127,20 +125,38 @@ namespace osu.Game.Screens.Menu
if (!anyHovered) if (!anyHovered)
{ {
bool previousShowing = displayIndex >= 0; int previousIndex = displayIndex;
if (previousShowing)
content[displayIndex % content.Count].FadeOut(400, Easing.OutQuint);
displayIndex++; if (displayIndex == -1)
displayIndex = 0;
using (BeginDelayedSequence(previousShowing ? 300 : 0)) // To handle expiration simply, arrange all images in best-next order.
content[displayIndex % content.Count].Show(); // Fade in the first valid one, then handle fading out the last if required.
var currentRotation = content
.Skip(displayIndex + 1)
.Concat(content.Take(displayIndex + 1));
foreach (var image in currentRotation)
{
if (!image.Image.IsCurrent) continue;
using (BeginDelayedSequence(previousIndex >= 0 ? 300 : 0))
{
displayIndex = content.IndexOf(image);
if (displayIndex != previousIndex)
image.Show();
break;
}
}
if (previousIndex >= 0 && previousIndex != displayIndex)
content[previousIndex].FadeOut(400, Easing.OutQuint);
} }
if (content.Count > 1) // Re-scheduling this method will both handle rotation and re-checking for expiration dates.
{ nextDisplay = Scheduler.AddDelayed(showNext, DelayBetweenRotation);
nextDisplay = Scheduler.AddDelayed(showNext, DelayBetweenRotation);
}
} }
[LongRunningLoad] [LongRunningLoad]