1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-22 20:47:25 +08:00

Merge pull request #22193 from Feodor0090/comment-editor-3

Integrate comment editor into comments view
This commit is contained in:
Bartłomiej Dach 2023-01-17 22:21:41 +01:00 committed by GitHub
commit c38f3b461c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 218 additions and 8 deletions

View File

@ -1,8 +1,6 @@
// 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.
#nullable disable
using System;
using System.Collections.Generic;
using System.Linq;
@ -11,7 +9,10 @@ using osu.Game.Overlays;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Testing;
using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
@ -27,15 +28,20 @@ namespace osu.Game.Tests.Visual.Online
private DummyAPIAccess dummyAPI => (DummyAPIAccess)API;
private CommentsContainer commentsContainer;
private CommentsContainer commentsContainer = null!;
private TextBox editorTextBox = null!;
[SetUp]
public void SetUp() => Schedule(() =>
{
Child = new BasicScrollContainer
{
RelativeSizeAxes = Axes.Both,
Child = commentsContainer = new CommentsContainer()
});
};
editorTextBox = commentsContainer.ChildrenOfType<TextBox>().First();
});
[Test]
public void TestIdleState()
@ -126,6 +132,44 @@ namespace osu.Game.Tests.Visual.Online
commentsContainer.ChildrenOfType<DrawableComment>().Count(d => d.Comment.Pinned == withPinned) == 1);
}
[Test]
public void TestPost()
{
setUpCommentsResponse(new CommentBundle { Comments = new List<Comment>() });
AddStep("show comments", () => commentsContainer.ShowComments(CommentableType.Beatmapset, 123));
AddAssert("no comments placeholder shown", () => commentsContainer.ChildrenOfType<CommentsContainer.NoCommentsPlaceholder>().Any());
setUpPostResponse();
AddStep("enter text", () => editorTextBox.Current.Value = "comm");
AddStep("submit", () => commentsContainer.ChildrenOfType<RoundedButton>().First().TriggerClick());
AddUntilStep("comment sent", () =>
{
string writtenText = editorTextBox.Current.Value;
var comment = commentsContainer.ChildrenOfType<DrawableComment>().LastOrDefault();
return comment != null && comment.ChildrenOfType<SpriteText>().Any(y => y.Text == writtenText);
});
AddAssert("no comments placeholder removed", () => !commentsContainer.ChildrenOfType<CommentsContainer.NoCommentsPlaceholder>().Any());
}
[Test]
public void TestPostWithExistingComments()
{
setUpCommentsResponse(getExampleComments());
AddStep("show comments", () => commentsContainer.ShowComments(CommentableType.Beatmapset, 123));
setUpPostResponse();
AddStep("enter text", () => editorTextBox.Current.Value = "comm");
AddStep("submit", () => commentsContainer.ChildrenOfType<CommentEditor>().Single().ChildrenOfType<RoundedButton>().First().TriggerClick());
AddUntilStep("comment sent", () =>
{
string writtenText = editorTextBox.Current.Value;
var comment = commentsContainer.ChildrenOfType<DrawableComment>().LastOrDefault();
return comment != null && comment.ChildrenOfType<SpriteText>().Any(y => y.Text == writtenText);
});
}
private void setUpCommentsResponse(CommentBundle commentBundle)
=> AddStep("set up response", () =>
{
@ -139,7 +183,33 @@ namespace osu.Game.Tests.Visual.Online
};
});
private CommentBundle getExampleComments(bool withPinned = false)
private void setUpPostResponse()
=> AddStep("set up response", () =>
{
dummyAPI.HandleRequest = request =>
{
if (!(request is CommentPostRequest req))
return false;
req.TriggerSuccess(new CommentBundle
{
Comments = new List<Comment>
{
new Comment
{
Id = 98,
Message = req.Message,
LegacyName = "FirstUser",
CreatedAt = DateTimeOffset.Now,
VotesCount = 98,
}
}
});
return true;
};
});
private static CommentBundle getExampleComments(bool withPinned = false)
{
var bundle = new CommentBundle
{

View File

@ -0,0 +1,41 @@
// 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;
using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Online.API.Requests
{
public class CommentPostRequest : APIRequest<CommentBundle>
{
public readonly CommentableType Commentable;
public readonly long CommentableId;
public readonly string Message;
public readonly long? ParentCommentId;
public CommentPostRequest(CommentableType commentable, long commentableId, string message, long? parentCommentId = null)
{
Commentable = commentable;
CommentableId = commentableId;
Message = message;
ParentCommentId = parentCommentId;
}
protected override WebRequest CreateWebRequest()
{
var req = base.CreateWebRequest();
req.Method = HttpMethod.Post;
req.AddParameter(@"comment[commentable_type]", Commentable.ToString().ToLowerInvariant());
req.AddParameter(@"comment[commentable_id]", $"{CommentableId}");
req.AddParameter(@"comment[message]", Message);
if (ParentCommentId.HasValue)
req.AddParameter(@"comment[parent_id]", $"{ParentCommentId}");
return req;
}
protected override string Target => "comments";
}
}

View File

@ -3,6 +3,7 @@
#nullable disable
using System;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers;
using osu.Game.Online.API;
@ -17,16 +18,22 @@ using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Threading;
using System.Collections.Generic;
using JetBrains.Annotations;
using osu.Framework.Localisation;
using osu.Framework.Logging;
using osu.Game.Graphics.Sprites;
using osu.Game.Resources.Localisation.Web;
using APIUser = osu.Game.Online.API.Requests.Responses.APIUser;
using osu.Game.Users.Drawables;
using osuTK;
namespace osu.Game.Overlays.Comments
{
[Cached]
public partial class CommentsContainer : CompositeDrawable
{
private readonly Bindable<CommentableType> type = new Bindable<CommentableType>();
private readonly BindableLong id = new BindableLong();
public IBindable<CommentableType> Type => type;
public IBindable<long> Id => id;
public readonly Bindable<CommentsSortCriteria> Sort = new Bindable<CommentsSortCriteria>();
public readonly BindableBool ShowDeleted = new BindableBool();
@ -46,12 +53,14 @@ namespace osu.Game.Overlays.Comments
private DeletedCommentsCounter deletedCommentsCounter;
private CommentsShowMoreButton moreButton;
private TotalCommentsCounter commentCounter;
private UpdateableAvatar avatar;
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider)
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
AddRangeInternal(new Drawable[]
{
new Box
@ -86,6 +95,32 @@ namespace osu.Game.Overlays.Comments
},
},
},
new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Padding = new MarginPadding { Horizontal = 50, Vertical = 20 },
Children = new Drawable[]
{
avatar = new UpdateableAvatar(api.LocalUser.Value)
{
Size = new Vector2(50),
CornerExponent = 2,
CornerRadius = 25,
Masking = true,
},
new Container
{
Padding = new MarginPadding { Left = 60 },
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Child = new NewCommentEditor
{
OnPost = prependPostedComments
}
}
}
},
new CommentsHeader
{
Sort = { BindTarget = Sort },
@ -151,6 +186,7 @@ namespace osu.Game.Overlays.Comments
protected override void LoadComplete()
{
User.BindValueChanged(_ => refetchComments());
User.BindValueChanged(e => avatar.User = e.NewValue);
Sort.BindValueChanged(_ => refetchComments(), true);
base.LoadComplete();
}
@ -245,7 +281,6 @@ namespace osu.Game.Overlays.Comments
{
pinnedContent.AddRange(loaded.Where(d => d.Comment.Pinned));
content.AddRange(loaded.Where(d => !d.Comment.Pinned));
deletedCommentsCounter.Count.Value += topLevelComments.Select(d => d.Comment).Count(c => c.IsDeleted && c.IsTopLevel);
if (bundle.HasMore)
@ -288,6 +323,34 @@ namespace osu.Game.Overlays.Comments
}
}
private void prependPostedComments(CommentBundle bundle)
{
var topLevelComments = new List<DrawableComment>();
foreach (var comment in bundle.Comments)
{
// Exclude possible duplicated comments.
if (CommentDictionary.ContainsKey(comment.Id))
continue;
topLevelComments.Add(getDrawableComment(comment));
}
if (topLevelComments.Any())
{
LoadComponentsAsync(topLevelComments, loaded =>
{
if (content.Count > 0 && content[0] is NoCommentsPlaceholder placeholder)
content.Remove(placeholder, true);
foreach (var comment in loaded)
{
content.Insert((int)-Clock.CurrentTime, comment);
}
}, (loadCancellation = new CancellationTokenSource()).Token);
}
}
private DrawableComment getDrawableComment(Comment comment)
{
if (CommentDictionary.TryGetValue(comment.Id, out var existing))
@ -317,7 +380,7 @@ namespace osu.Game.Overlays.Comments
base.Dispose(isDisposing);
}
private partial class NoCommentsPlaceholder : CompositeDrawable
internal partial class NoCommentsPlaceholder : CompositeDrawable
{
[BackgroundDependencyLoader]
private void load()
@ -336,5 +399,41 @@ namespace osu.Game.Overlays.Comments
});
}
}
private partial class NewCommentEditor : CommentEditor
{
[Resolved]
private CommentsContainer commentsContainer { get; set; }
[Resolved]
private IAPIProvider api { get; set; }
public Action<CommentBundle> OnPost;
//TODO should match web, left empty due to no multiline support
protected override LocalisableString FooterText => default;
protected override LocalisableString CommitButtonText => CommonStrings.ButtonsPost;
protected override LocalisableString TextBoxPlaceholder => CommentsStrings.PlaceholderNew;
protected override void OnCommit(string text)
{
ShowLoadingSpinner = true;
CommentPostRequest req = new CommentPostRequest(commentsContainer.Type.Value, commentsContainer.Id.Value, text);
req.Failure += e => Schedule(() =>
{
ShowLoadingSpinner = false;
Logger.Error(e, "Posting comment failed.");
});
req.Success += cb => Schedule(() =>
{
ShowLoadingSpinner = false;
Current.Value = string.Empty;
OnPost?.Invoke(cb);
});
api.Queue(req);
}
}
}
}