1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-05 10:33:22 +08:00

Merge pull request #30793 from bdach/close-playlists

Add ability to close playlists within grace period after creation
This commit is contained in:
Dean Herbert 2024-11-29 22:37:32 +09:00 committed by GitHub
commit f56b2b9aef
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 238 additions and 19 deletions

View File

@ -2,8 +2,13 @@
// 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.Screens; using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Rooms; using osu.Game.Online.Rooms;
using osu.Game.Online.Rooms.RoomStatuses; using osu.Game.Online.Rooms.RoomStatuses;
@ -14,6 +19,9 @@ namespace osu.Game.Tests.Visual.Playlists
{ {
public partial class TestScenePlaylistsRoomSubScreen : OnlinePlayTestScene public partial class TestScenePlaylistsRoomSubScreen : OnlinePlayTestScene
{ {
[Resolved]
private IAPIProvider api { get; set; } = null!;
protected new TestRoomManager RoomManager => (TestRoomManager)base.RoomManager; protected new TestRoomManager RoomManager => (TestRoomManager)base.RoomManager;
[Test] [Test]
@ -37,5 +45,29 @@ namespace osu.Game.Tests.Visual.Playlists
AddUntilStep("wait for screen load", () => roomScreen.IsCurrentScreen()); AddUntilStep("wait for screen load", () => roomScreen.IsCurrentScreen());
AddAssert("status is still ended", () => roomScreen.Room.Status, Is.TypeOf<RoomStatusEnded>); AddAssert("status is still ended", () => roomScreen.Room.Status, Is.TypeOf<RoomStatusEnded>);
} }
[Test]
public void TestCloseButtonGoesAwayAfterGracePeriod()
{
Room room = null!;
PlaylistsRoomSubScreen roomScreen = null!;
AddStep("create room", () =>
{
RoomManager.AddRoom(room = new Room
{
Name = @"Test Room",
Host = api.LocalUser.Value,
Category = RoomCategory.Normal,
StartDate = DateTimeOffset.Now.AddMinutes(-5).AddSeconds(3),
EndDate = DateTimeOffset.Now.AddMinutes(30)
});
});
AddStep("push screen", () => LoadScreen(roomScreen = new PlaylistsRoomSubScreen(room)));
AddUntilStep("wait for screen load", () => roomScreen.IsCurrentScreen());
AddAssert("close button present", () => roomScreen.ChildrenOfType<DangerousRoundedButton>().Any());
AddUntilStep("wait for close button to disappear", () => !roomScreen.ChildrenOfType<DangerousRoundedButton>().Any());
}
} }
} }

View File

@ -0,0 +1,27 @@
// 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.Net.Http;
using osu.Framework.IO.Network;
namespace osu.Game.Online.API.Requests
{
public class ClosePlaylistRequest : APIRequest
{
private readonly long roomId;
public ClosePlaylistRequest(long roomId)
{
this.roomId = roomId;
}
protected override WebRequest CreateWebRequest()
{
var request = base.CreateWebRequest();
request.Method = HttpMethod.Delete;
return request;
}
protected override string Target => $@"rooms/{roomId}";
}
}

View File

@ -375,6 +375,7 @@ namespace osu.Game.Online.Rooms
Type = other.Type; Type = other.Type;
MaxParticipants = other.MaxParticipants; MaxParticipants = other.MaxParticipants;
ParticipantCount = other.ParticipantCount; ParticipantCount = other.ParticipantCount;
StartDate = other.StartDate;
EndDate = other.EndDate; EndDate = other.EndDate;
UserScore = other.UserScore; UserScore = other.UserScore;
QueueMode = other.QueueMode; QueueMode = other.QueueMode;

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.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using osu.Framework.Allocation; using osu.Framework.Allocation;
@ -22,9 +23,14 @@ using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Input.Bindings; using osu.Game.Input.Bindings;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.Rooms; using osu.Game.Online.Rooms;
using osu.Game.Online.Rooms.RoomStatuses;
using osu.Game.Overlays;
using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Screens.OnlinePlay.Lounge.Components; using osu.Game.Screens.OnlinePlay.Lounge.Components;
using osu.Game.Screens.OnlinePlay.Playlists;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
using Container = osu.Framework.Graphics.Containers.Container; using Container = osu.Framework.Graphics.Containers.Container;
@ -48,6 +54,12 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
[Resolved(canBeNull: true)] [Resolved(canBeNull: true)]
private LoungeSubScreen? lounge { get; set; } private LoungeSubScreen? lounge { get; set; }
[Resolved]
private IDialogOverlay? dialogOverlay { get; set; }
[Resolved]
private IAPIProvider api { get; set; } = null!;
private readonly BindableWithCurrent<Room?> selectedRoom = new BindableWithCurrent<Room?>(); private readonly BindableWithCurrent<Room?> selectedRoom = new BindableWithCurrent<Room?>();
private Sample? sampleSelect; private Sample? sampleSelect;
private Sample? sampleJoin; private Sample? sampleJoin;
@ -144,13 +156,34 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
public Popover GetPopover() => new PasswordEntryPopover(Room); public Popover GetPopover() => new PasswordEntryPopover(Room);
public MenuItem[] ContextMenuItems => new MenuItem[] public MenuItem[] ContextMenuItems
{ {
new OsuMenuItem("Create copy", MenuItemType.Standard, () => get
{ {
lounge?.OpenCopy(Room); var items = new List<MenuItem>
}) {
}; new OsuMenuItem("Create copy", MenuItemType.Standard, () =>
{
lounge?.OpenCopy(Room);
})
};
if (Room.Type == MatchType.Playlists && Room.Host?.Id == api.LocalUser.Value.Id && Room.StartDate?.AddMinutes(5) >= DateTimeOffset.Now && Room.Status is not RoomStatusEnded)
{
items.Add(new OsuMenuItem("Close playlist", MenuItemType.Destructive, () =>
{
dialogOverlay?.Push(new ClosePlaylistDialog(Room, () =>
{
var request = new ClosePlaylistRequest(Room.RoomID!.Value);
request.Success += () => lounge?.RefreshRooms();
api.Queue(request);
}));
}));
}
return items.ToArray();
}
}
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e) public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
{ {

View File

@ -379,6 +379,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
this.Push(CreateRoomSubScreen(room)); this.Push(CreateRoomSubScreen(room));
} }
public void RefreshRooms() => ListingPollingComponent.PollImmediately();
private void updateLoadingLayer() private void updateLoadingLayer()
{ {
if (operationInProgress.Value || !ListingPollingComponent.InitialRoomsReceived.Value) if (operationInProgress.Value || !ListingPollingComponent.InitialRoomsReceived.Value)

View File

@ -71,7 +71,7 @@ namespace osu.Game.Screens.OnlinePlay.Match
protected RulesetStore Rulesets { get; private set; } = null!; protected RulesetStore Rulesets { get; private set; } = null!;
[Resolved] [Resolved]
private IAPIProvider api { get; set; } = null!; protected IAPIProvider API { get; private set; } = null!;
[Resolved(canBeNull: true)] [Resolved(canBeNull: true)]
protected OnlinePlayScreen? ParentScreen { get; private set; } protected OnlinePlayScreen? ParentScreen { get; private set; }
@ -80,7 +80,7 @@ namespace osu.Game.Screens.OnlinePlay.Match
private PreviewTrackManager previewTrackManager { get; set; } = null!; private PreviewTrackManager previewTrackManager { get; set; } = null!;
[Resolved(canBeNull: true)] [Resolved(canBeNull: true)]
private IDialogOverlay? dialogOverlay { get; set; } protected IDialogOverlay? DialogOverlay { get; private set; }
[Cached] [Cached]
private readonly OnlinePlayBeatmapAvailabilityTracker beatmapAvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker(); private readonly OnlinePlayBeatmapAvailabilityTracker beatmapAvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker();
@ -282,7 +282,7 @@ namespace osu.Game.Screens.OnlinePlay.Match
} }
} }
protected virtual bool IsConnected => api.State.Value == APIState.Online; protected virtual bool IsConnected => API.State.Value == APIState.Online;
public override bool OnBackButton() public override bool OnBackButton()
{ {
@ -361,17 +361,17 @@ namespace osu.Game.Screens.OnlinePlay.Match
bool hasUnsavedChanges = Room.RoomID == null && Room.Playlist.Count > 0; bool hasUnsavedChanges = Room.RoomID == null && Room.Playlist.Count > 0;
if (dialogOverlay == null || !hasUnsavedChanges) if (DialogOverlay == null || !hasUnsavedChanges)
return true; return true;
// if the dialog is already displayed, block exiting until the user explicitly makes a decision. // if the dialog is already displayed, block exiting until the user explicitly makes a decision.
if (dialogOverlay.CurrentDialog is ConfirmDiscardChangesDialog discardChangesDialog) if (DialogOverlay.CurrentDialog is ConfirmDiscardChangesDialog discardChangesDialog)
{ {
discardChangesDialog.Flash(); discardChangesDialog.Flash();
return false; return false;
} }
dialogOverlay.Push(new ConfirmDiscardChangesDialog(() => DialogOverlay.Push(new ConfirmDiscardChangesDialog(() =>
{ {
ExitConfirmed = true; ExitConfirmed = true;
settingsOverlay.Hide(); settingsOverlay.Hide();

View File

@ -0,0 +1,19 @@
// 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.Game.Online.Rooms;
using osu.Game.Overlays.Dialog;
namespace osu.Game.Screens.OnlinePlay.Playlists
{
public partial class ClosePlaylistDialog : DeletionDialog
{
public ClosePlaylistDialog(Room room, Action closeAction)
{
HeaderText = "Are you sure you want to close the following playlist:";
BodyText = room.Name;
DangerousAction = closeAction;
}
}
}

View File

@ -2,9 +2,14 @@
// 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.ComponentModel;
using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API;
using osu.Game.Online.Rooms; using osu.Game.Online.Rooms;
using osu.Game.Online.Rooms.RoomStatuses;
using osuTK; using osuTK;
namespace osu.Game.Screens.OnlinePlay.Playlists namespace osu.Game.Screens.OnlinePlay.Playlists
@ -12,22 +17,104 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
public partial class PlaylistsRoomFooter : CompositeDrawable public partial class PlaylistsRoomFooter : CompositeDrawable
{ {
public Action? OnStart; public Action? OnStart;
public Action? OnClose;
private readonly Room room;
private DangerousRoundedButton closeButton = null!;
[Resolved]
private IAPIProvider api { get; set; } = null!;
public PlaylistsRoomFooter(Room room) public PlaylistsRoomFooter(Room room)
{
this.room = room;
}
[BackgroundDependencyLoader]
private void load()
{ {
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
InternalChildren = new[] InternalChild = new FillFlowContainer
{ {
new PlaylistsReadyButton(room) AutoSizeAxes = Axes.X,
RelativeSizeAxes = Axes.Y,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(10),
Children = new Drawable[]
{ {
Anchor = Anchor.Centre, new PlaylistsReadyButton(room)
Origin = Anchor.Centre, {
RelativeSizeAxes = Axes.Y, Anchor = Anchor.Centre,
Size = new Vector2(600, 1), Origin = Anchor.Centre,
Action = () => OnStart?.Invoke() RelativeSizeAxes = Axes.Y,
Size = new Vector2(600, 1),
Action = () => OnStart?.Invoke()
},
closeButton = new DangerousRoundedButton
{
Text = "Close",
Action = () => OnClose?.Invoke(),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(120, 1),
Alpha = 0,
RelativeSizeAxes = Axes.Y,
}
} }
}; };
} }
protected override void LoadComplete()
{
base.LoadComplete();
room.PropertyChanged += onRoomChanged;
updateState();
}
private void hideCloseButton()
{
closeButton.ResizeWidthTo(0, 100, Easing.OutQuint)
.Then().FadeOut().Expire();
}
private void onRoomChanged(object? sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case nameof(Room.Status):
case nameof(Room.Host):
case nameof(Room.StartDate):
updateState();
break;
}
}
private void updateState()
{
TimeSpan? deletionGracePeriodRemaining = room.StartDate?.AddMinutes(5) - DateTimeOffset.Now;
if (room.Host?.Id == api.LocalUser.Value.Id)
{
if (deletionGracePeriodRemaining > TimeSpan.Zero && room.Status is not RoomStatusEnded)
{
closeButton.FadeIn();
using (BeginDelayedSequence(deletionGracePeriodRemaining.Value.TotalMilliseconds))
hideCloseButton();
}
else if (closeButton.Alpha > 0)
hideCloseButton();
}
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
room.PropertyChanged -= onRoomChanged;
}
} }
} }

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.ComponentModel; using System.ComponentModel;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
@ -13,7 +14,9 @@ using osu.Framework.Screens;
using osu.Game.Graphics.Cursor; using osu.Game.Graphics.Cursor;
using osu.Game.Input; using osu.Game.Input;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.Rooms; using osu.Game.Online.Rooms;
using osu.Game.Online.Rooms.RoomStatuses;
using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Screens.OnlinePlay.Match; using osu.Game.Screens.OnlinePlay.Match;
using osu.Game.Screens.OnlinePlay.Match.Components; using osu.Game.Screens.OnlinePlay.Match.Components;
@ -259,7 +262,8 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
protected override Drawable CreateFooter() => new PlaylistsRoomFooter(Room) protected override Drawable CreateFooter() => new PlaylistsRoomFooter(Room)
{ {
OnStart = StartPlay OnStart = StartPlay,
OnClose = closePlaylist,
}; };
protected override RoomSettingsOverlay CreateRoomSettingsOverlay(Room room) => new PlaylistsRoomSettingsOverlay(room) protected override RoomSettingsOverlay CreateRoomSettingsOverlay(Room room) => new PlaylistsRoomSettingsOverlay(room)
@ -277,6 +281,20 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
Logger.Log($"Polling adjusted (selection: {selectionPollingComponent.TimeBetweenPolls.Value})"); Logger.Log($"Polling adjusted (selection: {selectionPollingComponent.TimeBetweenPolls.Value})");
} }
private void closePlaylist()
{
DialogOverlay?.Push(new ClosePlaylistDialog(Room, () =>
{
var request = new ClosePlaylistRequest(Room.RoomID!.Value);
request.Success += () =>
{
Room.Status = new RoomStatusEnded();
Room.EndDate = DateTimeOffset.UtcNow;
};
API.Queue(request);
}));
}
protected override Screen CreateGameplayScreen(PlaylistItem selectedItem) protected override Screen CreateGameplayScreen(PlaylistItem selectedItem)
{ {
return new PlayerLoader(() => new PlaylistsPlayer(Room, selectedItem) return new PlayerLoader(() => new PlaylistsPlayer(Room, selectedItem)