diff --git a/osu.Android.props b/osu.Android.props
index 33d3a623ed..ec223f98c2 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -52,7 +52,7 @@
-
+
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs
index 0ffa5209e3..8f20429bf0 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs
@@ -25,7 +25,7 @@ using osu.Game.Screens;
using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Screens.OnlinePlay.Lounge;
using osu.Game.Screens.OnlinePlay.Lounge.Components;
-using osu.Game.Screens.OnlinePlay.Match.Components;
+using osu.Game.Screens.OnlinePlay.Match;
using osu.Game.Screens.OnlinePlay.Multiplayer;
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
using osu.Game.Tests.Resources;
@@ -312,6 +312,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
InputManager.Click(MouseButton.Left);
});
+ AddUntilStep("wait for spectating user state", () => client.LocalUser?.State == MultiplayerUserState.Spectating);
+
AddStep("start match externally", () => client.StartMatch());
AddAssert("play not started", () => multiplayerScreen.IsCurrentScreen());
@@ -348,6 +350,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
InputManager.Click(MouseButton.Left);
});
+ AddUntilStep("wait for spectating user state", () => client.LocalUser?.State == MultiplayerUserState.Spectating);
+
AddStep("start match externally", () => client.StartMatch());
AddStep("restore beatmap", () =>
@@ -396,7 +400,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
}
});
- AddStep("open mod overlay", () => this.ChildrenOfType().ElementAt(2).TriggerClick());
+ AddStep("open mod overlay", () => this.ChildrenOfType().Single().TriggerClick());
AddStep("invoke on back button", () => multiplayerScreen.OnBackButton());
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs
index 955be6ca21..ea10fc1b8b 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs
@@ -129,6 +129,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
InputManager.Click(MouseButton.Left);
});
+ AddUntilStep("wait for spectating user state", () => Client.LocalUser?.State == MultiplayerUserState.Spectating);
+
AddUntilStep("wait for ready button to be enabled", () => this.ChildrenOfType().Single().ChildrenOfType().Single().Enabled.Value);
AddStep("click ready button", () =>
diff --git a/osu.Game.Tests/Visual/Online/TestSceneCommentsPage.cs b/osu.Game.Tests/Visual/Online/TestSceneOfflineCommentsContainer.cs
similarity index 70%
rename from osu.Game.Tests/Visual/Online/TestSceneCommentsPage.cs
rename to osu.Game.Tests/Visual/Online/TestSceneOfflineCommentsContainer.cs
index 7fdf0708e0..628ae0971b 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneCommentsPage.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneOfflineCommentsContainer.cs
@@ -3,84 +3,52 @@
using System;
using System.Collections.Generic;
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics;
using osu.Game.Overlays.Comments;
using osu.Game.Overlays;
using osu.Framework.Allocation;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Users;
-using osu.Game.Graphics.UserInterface;
-using osu.Framework.Bindables;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics;
-using osuTK;
using JetBrains.Annotations;
-using NUnit.Framework;
+using osu.Framework.Testing;
namespace osu.Game.Tests.Visual.Online
{
- public class TestSceneCommentsPage : OsuTestScene
+ public class TestSceneOfflineCommentsContainer : OsuTestScene
{
[Cached]
- private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
+ private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
- private readonly BindableBool showDeleted = new BindableBool();
- private readonly Container content;
+ private TestCommentsContainer comments;
- private TestCommentsPage commentsPage;
-
- public TestSceneCommentsPage()
+ [SetUp]
+ public void SetUp() => Schedule(() =>
{
- Add(new FillFlowContainer
+ Clear();
+ Add(new BasicScrollContainer
{
- AutoSizeAxes = Axes.Y,
- RelativeSizeAxes = Axes.X,
- Direction = FillDirection.Vertical,
- Spacing = new Vector2(0, 10),
- Children = new Drawable[]
- {
- new Container
- {
- AutoSizeAxes = Axes.Y,
- Width = 200,
- Child = new OsuCheckbox
- {
- Current = showDeleted,
- LabelText = @"Show Deleted"
- }
- },
- content = new Container
- {
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- }
- }
+ RelativeSizeAxes = Axes.Both,
+ Child = comments = new TestCommentsContainer()
});
- }
+ });
[Test]
public void TestAppendDuplicatedComment()
{
- AddStep("Create page", () => createPage(getCommentBundle()));
- AddAssert("Dictionary length is 10", () => commentsPage?.DictionaryLength == 10);
- AddStep("Append existing comment", () => commentsPage?.AppendComments(getCommentSubBundle()));
- AddAssert("Dictionary length is 10", () => commentsPage?.DictionaryLength == 10);
+ AddStep("Add comment bundle", () => comments.ShowComments(getCommentBundle()));
+ AddUntilStep("Dictionary length is 10", () => comments.DictionaryLength == 10);
+ AddStep("Append existing comment", () => comments.AppendComments(getCommentSubBundle()));
+ AddAssert("Dictionary length is 10", () => comments.DictionaryLength == 10);
}
[Test]
- public void TestEmptyBundle()
+ public void TestLocalCommentBundle()
{
- AddStep("Create page", () => createPage(getEmptyCommentBundle()));
- AddAssert("Dictionary length is 0", () => commentsPage?.DictionaryLength == 0);
- }
-
- private void createPage(CommentBundle commentBundle)
- {
- commentsPage = null;
- content.Clear();
- content.Add(commentsPage = new TestCommentsPage(commentBundle)
- {
- ShowDeleted = { BindTarget = showDeleted }
- });
+ AddStep("Add comment bundle", () => comments.ShowComments(getCommentBundle()));
+ AddStep("Add empty comment bundle", () => comments.ShowComments(getEmptyCommentBundle()));
}
private CommentBundle getEmptyCommentBundle() => new CommentBundle
@@ -193,6 +161,7 @@ namespace osu.Game.Tests.Visual.Online
Username = "Good_Admin"
}
},
+ Total = 10
};
private CommentBundle getCommentSubBundle() => new CommentBundle
@@ -211,16 +180,18 @@ namespace osu.Game.Tests.Visual.Online
IncludedComments = new List(),
};
- private class TestCommentsPage : CommentsPage
+ private class TestCommentsContainer : CommentsContainer
{
- public TestCommentsPage(CommentBundle commentBundle)
- : base(commentBundle)
- {
- }
-
public new void AppendComments([NotNull] CommentBundle bundle) => base.AppendComments(bundle);
public int DictionaryLength => CommentDictionary.Count;
+
+ public void ShowComments(CommentBundle bundle)
+ {
+ this.ChildrenOfType().Single().Current.Value = 0;
+ ClearComments();
+ OnSuccess(bundle);
+ }
}
}
}
diff --git a/osu.Game/Overlays/Comments/CommentsContainer.cs b/osu.Game/Overlays/Comments/CommentsContainer.cs
index 513fabf52a..fe8d6f0178 100644
--- a/osu.Game/Overlays/Comments/CommentsContainer.cs
+++ b/osu.Game/Overlays/Comments/CommentsContainer.cs
@@ -14,6 +14,9 @@ using System.Linq;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Threading;
using osu.Game.Users;
+using System.Collections.Generic;
+using JetBrains.Annotations;
+using osu.Game.Graphics.Sprites;
namespace osu.Game.Overlays.Comments
{
@@ -147,7 +150,7 @@ namespace osu.Game.Overlays.Comments
private void refetchComments()
{
- clearComments();
+ ClearComments();
getComments();
}
@@ -160,50 +163,125 @@ namespace osu.Game.Overlays.Comments
loadCancellation?.Cancel();
scheduledCommentsLoad?.Cancel();
request = new GetCommentsRequest(id.Value, type.Value, Sort.Value, currentPage++, 0);
- request.Success += res => scheduledCommentsLoad = Schedule(() => onSuccess(res));
+ request.Success += res => scheduledCommentsLoad = Schedule(() => OnSuccess(res));
api.PerformAsync(request);
}
- private void clearComments()
+ protected void ClearComments()
{
currentPage = 1;
deletedCommentsCounter.Count.Value = 0;
moreButton.Show();
moreButton.IsLoading = true;
content.Clear();
+ CommentDictionary.Clear();
}
- private void onSuccess(CommentBundle response)
+ protected readonly Dictionary CommentDictionary = new Dictionary();
+
+ protected void OnSuccess(CommentBundle response)
{
- loadCancellation = new CancellationTokenSource();
+ commentCounter.Current.Value = response.Total;
- LoadComponentAsync(new CommentsPage(response)
+ if (!response.Comments.Any())
{
- ShowDeleted = { BindTarget = ShowDeleted },
- Sort = { BindTarget = Sort },
- Type = { BindTarget = type },
- CommentableId = { BindTarget = id }
- }, loaded =>
+ content.Add(new NoCommentsPlaceholder());
+ moreButton.Hide();
+ return;
+ }
+
+ AppendComments(response);
+ }
+
+ ///
+ /// Appends retrieved comments to the subtree rooted of comments in this page.
+ ///
+ /// The bundle of comments to add.
+ protected void AppendComments([NotNull] CommentBundle bundle)
+ {
+ var topLevelComments = new List();
+ var orphaned = new List();
+
+ foreach (var comment in bundle.Comments.Concat(bundle.IncludedComments))
{
- content.Add(loaded);
+ // Exclude possible duplicated comments.
+ if (CommentDictionary.ContainsKey(comment.Id))
+ continue;
- deletedCommentsCounter.Count.Value += response.Comments.Count(c => c.IsDeleted && c.IsTopLevel);
+ addNewComment(comment);
+ }
- if (response.HasMore)
+ // Comments whose parents were seen later than themselves can now be added.
+ foreach (var o in orphaned)
+ addNewComment(o);
+
+ if (topLevelComments.Any())
+ {
+ LoadComponentsAsync(topLevelComments, loaded =>
{
- int loadedTopLevelComments = 0;
- content.Children.OfType().ForEach(p => loadedTopLevelComments += p.Children.OfType().Count());
+ content.AddRange(loaded);
- moreButton.Current.Value = response.TopLevelCount - loadedTopLevelComments;
- moreButton.IsLoading = false;
+ deletedCommentsCounter.Count.Value += topLevelComments.Select(d => d.Comment).Count(c => c.IsDeleted && c.IsTopLevel);
+
+ if (bundle.HasMore)
+ {
+ int loadedTopLevelComments = 0;
+ content.Children.OfType().ForEach(p => loadedTopLevelComments++);
+
+ moreButton.Current.Value = bundle.TopLevelCount - loadedTopLevelComments;
+ moreButton.IsLoading = false;
+ }
+ else
+ {
+ moreButton.Hide();
+ }
+ }, (loadCancellation = new CancellationTokenSource()).Token);
+ }
+
+ void addNewComment(Comment comment)
+ {
+ var drawableComment = getDrawableComment(comment);
+
+ if (comment.ParentId == null)
+ {
+ // Comments that have no parent are added as top-level comments to the flow.
+ topLevelComments.Add(drawableComment);
+ }
+ else if (CommentDictionary.TryGetValue(comment.ParentId.Value, out var parentDrawable))
+ {
+ // The comment's parent has already been seen, so the parent<-> child links can be added.
+ comment.ParentComment = parentDrawable.Comment;
+ parentDrawable.Replies.Add(drawableComment);
}
else
{
- moreButton.Hide();
+ // The comment's parent has not been seen yet, so keep it orphaned for the time being. This can occur if the comments arrive out of order.
+ // Since this comment has now been seen, any further children can be added to it without being orphaned themselves.
+ orphaned.Add(comment);
}
+ }
+ }
- commentCounter.Current.Value = response.Total;
- }, loadCancellation.Token);
+ private DrawableComment getDrawableComment(Comment comment)
+ {
+ if (CommentDictionary.TryGetValue(comment.Id, out var existing))
+ return existing;
+
+ return CommentDictionary[comment.Id] = new DrawableComment(comment)
+ {
+ ShowDeleted = { BindTarget = ShowDeleted },
+ Sort = { BindTarget = Sort },
+ RepliesRequested = onCommentRepliesRequested
+ };
+ }
+
+ private void onCommentRepliesRequested(DrawableComment drawableComment, int page)
+ {
+ var req = new GetCommentsRequest(id.Value, type.Value, Sort.Value, page, drawableComment.Comment.Id);
+
+ req.Success += response => Schedule(() => AppendComments(response));
+
+ api.PerformAsync(req);
}
protected override void Dispose(bool isDisposing)
@@ -212,5 +290,30 @@ namespace osu.Game.Overlays.Comments
loadCancellation?.Cancel();
base.Dispose(isDisposing);
}
+
+ private class NoCommentsPlaceholder : CompositeDrawable
+ {
+ [BackgroundDependencyLoader]
+ private void load(OverlayColourProvider colourProvider)
+ {
+ Height = 80;
+ RelativeSizeAxes = Axes.X;
+ AddRangeInternal(new Drawable[]
+ {
+ new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Colour = colourProvider.Background4
+ },
+ new OsuSpriteText
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ Margin = new MarginPadding { Left = 50 },
+ Text = @"No comments yet."
+ }
+ });
+ }
+ }
}
}
diff --git a/osu.Game/Overlays/Comments/CommentsPage.cs b/osu.Game/Overlays/Comments/CommentsPage.cs
deleted file mode 100644
index 9b146b0a7d..0000000000
--- a/osu.Game/Overlays/Comments/CommentsPage.cs
+++ /dev/null
@@ -1,161 +0,0 @@
-// 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.Graphics.Containers;
-using osu.Framework.Graphics;
-using osu.Framework.Bindables;
-using osu.Game.Online.API.Requests.Responses;
-using osu.Framework.Graphics.Shapes;
-using osu.Game.Graphics.Sprites;
-using System.Linq;
-using osu.Game.Online.API.Requests;
-using osu.Game.Online.API;
-using System.Collections.Generic;
-using JetBrains.Annotations;
-
-namespace osu.Game.Overlays.Comments
-{
- public class CommentsPage : CompositeDrawable
- {
- public readonly BindableBool ShowDeleted = new BindableBool();
- public readonly Bindable Sort = new Bindable();
- public readonly Bindable Type = new Bindable();
- public readonly BindableLong CommentableId = new BindableLong();
-
- [Resolved]
- private IAPIProvider api { get; set; }
-
- private readonly CommentBundle commentBundle;
- private FillFlowContainer flow;
-
- public CommentsPage(CommentBundle commentBundle)
- {
- this.commentBundle = commentBundle;
- }
-
- [BackgroundDependencyLoader]
- private void load(OverlayColourProvider colourProvider)
- {
- RelativeSizeAxes = Axes.X;
- AutoSizeAxes = Axes.Y;
-
- AddRangeInternal(new Drawable[]
- {
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = colourProvider.Background5
- },
- flow = new FillFlowContainer
- {
- RelativeSizeAxes = Axes.X,
- AutoSizeAxes = Axes.Y,
- Direction = FillDirection.Vertical,
- }
- });
-
- if (!commentBundle.Comments.Any())
- {
- flow.Add(new NoCommentsPlaceholder());
- return;
- }
-
- AppendComments(commentBundle);
- }
-
- private DrawableComment getDrawableComment(Comment comment)
- {
- if (CommentDictionary.TryGetValue(comment.Id, out var existing))
- return existing;
-
- return CommentDictionary[comment.Id] = new DrawableComment(comment)
- {
- ShowDeleted = { BindTarget = ShowDeleted },
- Sort = { BindTarget = Sort },
- RepliesRequested = onCommentRepliesRequested
- };
- }
-
- private void onCommentRepliesRequested(DrawableComment drawableComment, int page)
- {
- var request = new GetCommentsRequest(CommentableId.Value, Type.Value, Sort.Value, page, drawableComment.Comment.Id);
-
- request.Success += response => Schedule(() => AppendComments(response));
-
- api.PerformAsync(request);
- }
-
- protected readonly Dictionary CommentDictionary = new Dictionary();
-
- ///
- /// Appends retrieved comments to the subtree rooted of comments in this page.
- ///
- /// The bundle of comments to add.
- protected void AppendComments([NotNull] CommentBundle bundle)
- {
- var orphaned = new List();
-
- foreach (var comment in bundle.Comments.Concat(bundle.IncludedComments))
- {
- // Exclude possible duplicated comments.
- if (CommentDictionary.ContainsKey(comment.Id))
- continue;
-
- addNewComment(comment);
- }
-
- // Comments whose parents were seen later than themselves can now be added.
- foreach (var o in orphaned)
- addNewComment(o);
-
- void addNewComment(Comment comment)
- {
- var drawableComment = getDrawableComment(comment);
-
- if (comment.ParentId == null)
- {
- // Comments that have no parent are added as top-level comments to the flow.
- flow.Add(drawableComment);
- }
- else if (CommentDictionary.TryGetValue(comment.ParentId.Value, out var parentDrawable))
- {
- // The comment's parent has already been seen, so the parent<-> child links can be added.
- comment.ParentComment = parentDrawable.Comment;
- parentDrawable.Replies.Add(drawableComment);
- }
- else
- {
- // The comment's parent has not been seen yet, so keep it orphaned for the time being. This can occur if the comments arrive out of order.
- // Since this comment has now been seen, any further children can be added to it without being orphaned themselves.
- orphaned.Add(comment);
- }
- }
- }
-
- private class NoCommentsPlaceholder : CompositeDrawable
- {
- [BackgroundDependencyLoader]
- private void load(OverlayColourProvider colourProvider)
- {
- Height = 80;
- RelativeSizeAxes = Axes.X;
- AddRangeInternal(new Drawable[]
- {
- new Box
- {
- RelativeSizeAxes = Axes.Both,
- Colour = colourProvider.Background4
- },
- new OsuSpriteText
- {
- Anchor = Anchor.CentreLeft,
- Origin = Anchor.CentreLeft,
- Margin = new MarginPadding { Left = 50 },
- Text = @"No comments yet."
- }
- });
- }
- }
- }
-}
diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs
index a53e253581..2616abf825 100644
--- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs
@@ -17,6 +17,7 @@ using osu.Game.Online.Rooms;
using osu.Game.Overlays;
using osu.Game.Overlays.Mods;
using osu.Game.Rulesets.Mods;
+using osu.Game.Screens.OnlinePlay.Match.Components;
namespace osu.Game.Screens.OnlinePlay.Match
{
@@ -250,5 +251,9 @@ namespace osu.Game.Screens.OnlinePlay.Match
private class UserModSelectOverlay : LocalPlayerModSelectOverlay
{
}
+
+ public class UserModSelectButton : PurpleTriangleButton
+ {
+ }
}
}
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs
index 9fa19aaf21..a8e44dd56c 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs
@@ -176,7 +176,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
Spacing = new Vector2(10, 0),
Children = new Drawable[]
{
- new PurpleTriangleButton
+ new UserModSelectButton
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs
index 45aca24ab2..953c687087 100644
--- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs
+++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs
@@ -163,7 +163,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
Spacing = new Vector2(10, 0),
Children = new Drawable[]
{
- new PurpleTriangleButton
+ new UserModSelectButton
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 6ccd34dd48..d4dba9330f 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -36,7 +36,7 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
diff --git a/osu.iOS.props b/osu.iOS.props
index b6623da540..7e514afe74 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -70,7 +70,7 @@
-
+
@@ -93,7 +93,7 @@
-
+