diff --git a/osu.Game/Misskey/Users/Drawables/ClickableAvatar.cs b/osu.Game/Misskey/Users/Drawables/ClickableAvatar.cs index c123b6d986..db0fccd88e 100644 --- a/osu.Game/Misskey/Users/Drawables/ClickableAvatar.cs +++ b/osu.Game/Misskey/Users/Drawables/ClickableAvatar.cs @@ -11,6 +11,7 @@ using osu.Framework.Localisation; using osu.Game.Graphics.Containers; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.MisskeyAPI.Requests.Responses; +using osu.Game.Online.MisskeyAPI.Responses.Types; namespace osu.Game.Misskey.Users.Drawables { @@ -35,7 +36,7 @@ namespace osu.Game.Misskey.Users.Drawables set => clickableArea.TooltipText = value ? (user?.Username ?? string.Empty) : default_tooltip_text; } - private readonly I user; + private readonly User user; [Resolved(CanBeNull = true)] private OsuGame game { get; set; } @@ -47,7 +48,7 @@ namespace osu.Game.Misskey.Users.Drawables /// If is true, clicking will open the user's profile. /// /// The user. A null value will get a placeholder avatar. - public ClickableAvatar(I user = null) + public ClickableAvatar(User user = null) { this.user = user; @@ -66,6 +67,11 @@ namespace osu.Game.Misskey.Users.Drawables LoadComponentAsync(new DrawableAvatar(user), clickableArea.Add); } + protected override void LoadComplete() + { + CornerRadius = DrawHeight / 2; + } + private void openProfile() { // if (!string.IsNullOrEmpty(user?.Username)) diff --git a/osu.Game/Misskey/Users/Drawables/DrawableAvatar.cs b/osu.Game/Misskey/Users/Drawables/DrawableAvatar.cs index f58cd927ec..598c9e5dea 100644 --- a/osu.Game/Misskey/Users/Drawables/DrawableAvatar.cs +++ b/osu.Game/Misskey/Users/Drawables/DrawableAvatar.cs @@ -9,19 +9,20 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.MisskeyAPI.Requests.Responses; +using osu.Game.Online.MisskeyAPI.Responses.Types; namespace osu.Game.Misskey.Users.Drawables { [LongRunningLoad] public partial class DrawableAvatar : Sprite { - private readonly I user; + private readonly User user; /// /// A simple, non-interactable avatar sprite for the specified user. /// /// The user. A null value will get a placeholder avatar. - public DrawableAvatar(I user = null) + public DrawableAvatar(User user = null) { this.user = user; diff --git a/osu.Game/Misskey/Users/Drawables/UpdateableAvatar.cs b/osu.Game/Misskey/Users/Drawables/UpdateableAvatar.cs index 849f894a9b..1af7b99957 100644 --- a/osu.Game/Misskey/Users/Drawables/UpdateableAvatar.cs +++ b/osu.Game/Misskey/Users/Drawables/UpdateableAvatar.cs @@ -8,15 +8,16 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.MisskeyAPI.Requests.Responses; +using osu.Game.Online.MisskeyAPI.Responses.Types; namespace osu.Game.Misskey.Users.Drawables { /// /// An avatar which can update to a new user when needed. /// - public partial class UpdateableAvatar : ModelBackedDrawable + public partial class UpdateableAvatar : ModelBackedDrawable { - public I User + public User User { get => Model; set => Model = value; @@ -59,7 +60,7 @@ namespace osu.Game.Misskey.Users.Drawables /// If set to true, hover/click sounds will play and clicking the avatar will open the user's profile. /// Whether to show the username rather than "view profile" on the tooltip. (note: this only applies if is also true) /// Whether to show a default guest representation on null user (as opposed to nothing). - public UpdateableAvatar(I user = null, bool isInteractive = true, bool showUsernameTooltip = false, bool showGuestOnNull = true) + public UpdateableAvatar(User user = null, bool isInteractive = true, bool showUsernameTooltip = false, bool showGuestOnNull = true) { this.isInteractive = isInteractive; this.showUsernameTooltip = showUsernameTooltip; @@ -68,7 +69,7 @@ namespace osu.Game.Misskey.Users.Drawables User = user; } - protected override Drawable CreateDrawable(I user) + protected override Drawable CreateDrawable(User user) { if (user == null && !showGuestOnNull) return null; diff --git a/osu.Game/Misskey/Users/ExtendedUserPanel.cs b/osu.Game/Misskey/Users/ExtendedUserPanel.cs index 36dec24412..0fd61411be 100644 --- a/osu.Game/Misskey/Users/ExtendedUserPanel.cs +++ b/osu.Game/Misskey/Users/ExtendedUserPanel.cs @@ -15,6 +15,7 @@ using osu.Game.Misskey.Users.Drawables; using osu.Framework.Input.Events; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.MisskeyAPI.Requests.Responses; +using osu.Game.Online.MisskeyAPI.Responses.Types; namespace osu.Game.Misskey.Users { @@ -29,7 +30,7 @@ namespace osu.Game.Misskey.Users private SpriteIcon statusIcon; private OsuSpriteText statusMessage; - protected ExtendedUserPanel(I user) + protected ExtendedUserPanel(User user) : base(user) { } diff --git a/osu.Game/Misskey/Users/UserCoverBackground.cs b/osu.Game/Misskey/Users/UserCoverBackground.cs index 62dabb1d2f..827eef252d 100644 --- a/osu.Game/Misskey/Users/UserCoverBackground.cs +++ b/osu.Game/Misskey/Users/UserCoverBackground.cs @@ -14,19 +14,20 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.MisskeyAPI.Requests.Responses; +using osu.Game.Online.MisskeyAPI.Responses.Types; using osuTK.Graphics; namespace osu.Game.Misskey.Users { - public partial class UserCoverBackground : ModelBackedDrawable + public partial class UserCoverBackground : ModelBackedDrawable { - public I User + public User User { get => Model; set => Model = value; } - protected override Drawable CreateDrawable(I user) => new Cover(user); + protected override Drawable CreateDrawable(User user) => new Cover(user); protected override double LoadDelay => 300; @@ -41,9 +42,9 @@ namespace osu.Game.Misskey.Users [LongRunningLoad] private partial class Cover : CompositeDrawable { - private readonly I user; + private readonly User user; - public Cover(I user) + public Cover(User user) { this.user = user; diff --git a/osu.Game/Misskey/Users/UserGridPanel.cs b/osu.Game/Misskey/Users/UserGridPanel.cs index 742212532f..2b38d2ceb3 100644 --- a/osu.Game/Misskey/Users/UserGridPanel.cs +++ b/osu.Game/Misskey/Users/UserGridPanel.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.MisskeyAPI.Requests.Responses; +using osu.Game.Online.MisskeyAPI.Responses.Types; using osu.Game.Overlays.Profile.Header.Components; using osuTK; @@ -17,7 +18,7 @@ namespace osu.Game.Misskey.Users { private const int margin = 10; - public UserGridPanel(I user) + public UserGridPanel(User user) : base(user) { Height = 120; diff --git a/osu.Game/Misskey/Users/UserListPanel.cs b/osu.Game/Misskey/Users/UserListPanel.cs index 57b751a5b5..55da87f9c2 100644 --- a/osu.Game/Misskey/Users/UserListPanel.cs +++ b/osu.Game/Misskey/Users/UserListPanel.cs @@ -11,6 +11,7 @@ using osuTK.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.MisskeyAPI.Requests.Responses; +using osu.Game.Online.MisskeyAPI.Responses.Types; using osuTK; using osu.Game.Overlays.Profile.Header.Components; @@ -18,7 +19,7 @@ namespace osu.Game.Misskey.Users { public partial class UserListPanel : ExtendedUserPanel { - public UserListPanel(I user) + public UserListPanel(User user) : base(user) { RelativeSizeAxes = Axes.X; diff --git a/osu.Game/Misskey/Users/UserPanel.cs b/osu.Game/Misskey/Users/UserPanel.cs index 0d073471a9..594d06f8d7 100644 --- a/osu.Game/Misskey/Users/UserPanel.cs +++ b/osu.Game/Misskey/Users/UserPanel.cs @@ -17,12 +17,13 @@ using osu.Game.Graphics.Containers; using JetBrains.Annotations; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.MisskeyAPI.Requests.Responses; +using osu.Game.Online.MisskeyAPI.Responses.Types; namespace osu.Game.Misskey.Users { public abstract partial class UserPanel : OsuClickableContainer, IHasContextMenu { - public readonly I User; + public readonly User User; /// /// Perform an action in addition to showing the user's profile. @@ -34,7 +35,7 @@ namespace osu.Game.Misskey.Users protected Drawable Background { get; private set; } - protected UserPanel(I user) + protected UserPanel(User user) : base(HoverSampleSet.Button) { if (user == null) diff --git a/osu.Game/Online/MisskeyAPI/APIAccess.cs b/osu.Game/Online/MisskeyAPI/APIAccess.cs index d2cd2e7d93..070e83f321 100644 --- a/osu.Game/Online/MisskeyAPI/APIAccess.cs +++ b/osu.Game/Online/MisskeyAPI/APIAccess.cs @@ -18,9 +18,12 @@ using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Logging; using osu.Game.Configuration; +using osu.Game.Misskey.Users; using osu.Game.Online.MisskeyAPI.Requests; using osu.Game.Online.MisskeyAPI.Requests.Responses; -using osu.Game.Users; +using osu.Game.Online.MisskeyAPI.Responses.Types; +using User = osu.Game.Online.MisskeyAPI.Responses.Types.User; +using UserActivity = osu.Game.Users.UserActivity; namespace osu.Game.Online.MisskeyAPI { @@ -44,10 +47,10 @@ namespace osu.Game.Online.MisskeyAPI private string password; - public IBindable LocalUser => localUser; + public IBindable LocalUser => localUser; public IBindable Activity => activity; - private Bindable localUser { get; } = new Bindable(createGuestUser()); + private Bindable localUser { get; } = new Bindable(createGuestUser()); private Bindable activity { get; } = new Bindable(); protected bool HasLogin => authentication.Token.Value != null || (!string.IsNullOrEmpty(ProvidedUsername) && !string.IsNullOrEmpty(password)); @@ -108,7 +111,7 @@ namespace osu.Game.Online.MisskeyAPI if (queue.Count == 0) { log.Add(@"Queueing a ping request"); - Queue(new Requests.I(AccessToken)); + Queue(new Requests.User(AccessToken)); } break; @@ -149,7 +152,7 @@ namespace osu.Game.Online.MisskeyAPI } } - var userReq = new Requests.I(AccessToken); + var userReq = new Requests.User(AccessToken); userReq.Failure += ex => { @@ -428,7 +431,7 @@ namespace osu.Game.Online.MisskeyAPI flushQueue(); } - private static Requests.Responses.I createGuestUser() => new GuestUser(); + private static User createGuestUser() => new GuestUser(); protected override void Dispose(bool isDisposing) { @@ -439,7 +442,7 @@ namespace osu.Game.Online.MisskeyAPI } } - internal class GuestUser : Requests.Responses.I + internal class GuestUser : User { public GuestUser() { diff --git a/osu.Game/Online/MisskeyAPI/APIRequest.cs b/osu.Game/Online/MisskeyAPI/APIRequest.cs index 1b0704f384..a963ebf9ef 100644 --- a/osu.Game/Online/MisskeyAPI/APIRequest.cs +++ b/osu.Game/Online/MisskeyAPI/APIRequest.cs @@ -12,6 +12,7 @@ using Newtonsoft.Json; using osu.Framework.IO.Network; using osu.Framework.Logging; using osu.Game.Online.MisskeyAPI.Requests.Responses; +using osu.Game.Online.MisskeyAPI.Responses.Types; namespace osu.Game.Online.MisskeyAPI { @@ -85,7 +86,7 @@ namespace osu.Game.Online.MisskeyAPI //// //// The currently logged in user. Note that this will only be populated during . //// - protected I User { get; private set; } + protected User User { get; private set; } /// /// Invoked on successful completion of an API request. diff --git a/osu.Game/Online/MisskeyAPI/IAPIProvider.cs b/osu.Game/Online/MisskeyAPI/IAPIProvider.cs index 3d1e8cbedb..862f6f6760 100644 --- a/osu.Game/Online/MisskeyAPI/IAPIProvider.cs +++ b/osu.Game/Online/MisskeyAPI/IAPIProvider.cs @@ -7,6 +7,7 @@ using System; using System.Threading.Tasks; using osu.Framework.Bindables; using osu.Game.Online.MisskeyAPI.Requests.Responses; +using osu.Game.Online.MisskeyAPI.Responses.Types; using osu.Game.Users; namespace osu.Game.Online.MisskeyAPI @@ -17,7 +18,7 @@ namespace osu.Game.Online.MisskeyAPI /// The local user. /// This is not thread-safe and should be scheduled locally if consumed from a drawable component. /// - IBindable LocalUser { get; } + IBindable LocalUser { get; } /// /// The current user's activity. diff --git a/osu.Game/Online/MisskeyAPI/Requests/I/I.cs b/osu.Game/Online/MisskeyAPI/Requests/I/I.cs index 18b830c9c3..e1389f5f69 100644 --- a/osu.Game/Online/MisskeyAPI/Requests/I/I.cs +++ b/osu.Game/Online/MisskeyAPI/Requests/I/I.cs @@ -8,12 +8,12 @@ using osu.Framework.IO.Network; namespace osu.Game.Online.MisskeyAPI.Requests { - public class I : APIRequest + public class User : APIRequest { private string i; - public I(string i) + public User(string i) { this.i = i; } diff --git a/osu.Game/Online/MisskeyAPI/Requests/Notes/Create.cs b/osu.Game/Online/MisskeyAPI/Requests/Notes/Create.cs index 45b83423d4..a053415a06 100644 --- a/osu.Game/Online/MisskeyAPI/Requests/Notes/Create.cs +++ b/osu.Game/Online/MisskeyAPI/Requests/Notes/Create.cs @@ -13,17 +13,25 @@ namespace osu.Game.Online.MisskeyAPI.Requests.Notes { private string text; private string i; + private string? cw; public Create(string i, string Text) { - this.text = Text; this.i = i; + this.text = Text; + } + public Create(string i, string Text, string cw) + { + this.i = i; + this.text = Text; + this.cw = cw; } private class ReqBody { public string? text; public string? i; + public string? cw; }; protected override WebRequest CreateWebRequest() { @@ -34,6 +42,10 @@ namespace osu.Game.Online.MisskeyAPI.Requests.Notes text = text, i = i }; + if (cw != null) + { + body.cw = cw; + } var json = JsonConvert.SerializeObject(body); Logger.Log(json, LoggingTarget.Network, LogLevel.Debug); req.AddRaw(json); diff --git a/osu.Game/Online/MisskeyAPI/Requests/Notes/HybridTimeline.cs b/osu.Game/Online/MisskeyAPI/Requests/Notes/HybridTimeline.cs index 13e5a4d48e..054b80b41a 100644 --- a/osu.Game/Online/MisskeyAPI/Requests/Notes/HybridTimeline.cs +++ b/osu.Game/Online/MisskeyAPI/Requests/Notes/HybridTimeline.cs @@ -6,41 +6,46 @@ using System.Net.Http; using JetBrains.Annotations; using Newtonsoft.Json; using osu.Framework.IO.Network; +using osu.Game.Online.MisskeyAPI.Responses.Types; using Realms; namespace osu.Game.Online.MisskeyAPI.Requests.Notes { - public class HybridTimeline : APIRequest + public class HybridTimeline : APIRequest { private readonly string i; - private readonly string sinceId; - private readonly string untilId; + // private readonly string? sinceId; + private readonly string? untilId; public HybridTimeline( string i, - string sinceId = "", string untilId = "" ) { this.i = i; - this.sinceId = sinceId; this.untilId = untilId; } + public HybridTimeline(string i) + { + this.i = i; + } + protected override WebRequest CreateWebRequest() { var req = base.CreateWebRequest(); req.Method = HttpMethod.Post; - var body = new Dictionary + var body = new Dictionary { { "i", i }, + { "limit", 30 }, }; - if (sinceId != "") - body.Add("sinceId", sinceId); - if (untilId != "") + // if (sinceId != null) + // body.Add("sinceId", sinceId); + if (untilId != null) body.Add("untilId", untilId); req.AddRaw(JsonConvert.SerializeObject(body)); diff --git a/osu.Game/Online/MisskeyAPI/Responses/Notes/HybridTimeline.cs b/osu.Game/Online/MisskeyAPI/Responses/Notes/HybridTimeline.cs deleted file mode 100644 index cf165811fe..0000000000 --- a/osu.Game/Online/MisskeyAPI/Responses/Notes/HybridTimeline.cs +++ /dev/null @@ -1,267 +0,0 @@ -// Copyright (c) sim1222 . 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 Newtonsoft.Json; - -namespace osu.Game.Online.MisskeyAPI.Requests.Responses.Notes -{ - public class Channel - { - } - - public class Emoji - { - [JsonProperty("name")] - public string Name { get; set; } - - [JsonProperty("url")] - public string Url { get; set; } - } - - public class File - { - [JsonProperty("id")] - public string Id { get; set; } - - [JsonProperty("createdAt")] - public DateTime? CreatedAt { get; set; } - - [JsonProperty("name")] - public string Name { get; set; } - - [JsonProperty("type")] - public string Type { get; set; } - - [JsonProperty("md5")] - public string Md5 { get; set; } - - [JsonProperty("size")] - public int? Size { get; set; } - - [JsonProperty("isSensitive")] - public bool? IsSensitive { get; set; } - - [JsonProperty("blurhash")] - public string Blurhash { get; set; } - - [JsonProperty("properties")] - public Properties Properties { get; set; } - - [JsonProperty("url")] - public string Url { get; set; } - - [JsonProperty("thumbnailUrl")] - public string ThumbnailUrl { get; set; } - - [JsonProperty("comment")] - public string Comment { get; set; } - - [JsonProperty("folderId")] - public string FolderId { get; set; } - - [JsonProperty("folder")] - public Folder Folder { get; set; } - - [JsonProperty("userId")] - public string UserId { get; set; } - - [JsonProperty("user")] - public User User { get; set; } - } - - public class Folder - { - [JsonProperty("id")] - public string Id { get; set; } - - [JsonProperty("createdAt")] - public DateTime? CreatedAt { get; set; } - - [JsonProperty("name")] - public string Name { get; set; } - - [JsonProperty("foldersCount")] - public int? FoldersCount { get; set; } - - [JsonProperty("filesCount")] - public int? FilesCount { get; set; } - - [JsonProperty("parentId")] - public string ParentId { get; set; } - - [JsonProperty("parent")] - public Parent Parent { get; set; } - } - - public class MyReaction - { - } - - public class Parent - { - } - - public class Poll - { - } - - public class Properties - { - [JsonProperty("width")] - public int? Width { get; set; } - - [JsonProperty("height")] - public int? Height { get; set; } - - [JsonProperty("orientation")] - public int? Orientation { get; set; } - - [JsonProperty("avgColor")] - public string AvgColor { get; set; } - } - - public class Reactions - { - } - - public class Renote - { - } - - public class Reply - { - } - - public class HybridTimeline - { - [JsonProperty("id")] - public string Id { get; set; } - - [JsonProperty("createdAt")] - public DateTime? CreatedAt { get; set; } - - [JsonProperty("text")] - public string Text { get; set; } - - [JsonProperty("cw")] - public string Cw { get; set; } - - [JsonProperty("userId")] - public string UserId { get; set; } - - [JsonProperty("user")] - public User User { get; set; } - - [JsonProperty("replyId")] - public string ReplyId { get; set; } - - [JsonProperty("renoteId")] - public string RenoteId { get; set; } - - [JsonProperty("reply")] - public Reply Reply { get; set; } - - [JsonProperty("renote")] - public Renote Renote { get; set; } - - [JsonProperty("isHidden")] - public bool? IsHidden { get; set; } - - [JsonProperty("visibility")] - public string Visibility { get; set; } - - [JsonProperty("mentions")] - public List Mentions { get; set; } - - [JsonProperty("visibleUserIds")] - public List VisibleUserIds { get; set; } - - [JsonProperty("fileIds")] - public List FileIds { get; set; } - - [JsonProperty("files")] - public List Files { get; set; } - - [JsonProperty("tags")] - public List Tags { get; set; } - - [JsonProperty("poll")] - public Poll Poll { get; set; } - - [JsonProperty("channelId")] - public string ChannelId { get; set; } - - [JsonProperty("channel")] - public Channel Channel { get; set; } - - [JsonProperty("localOnly")] - public bool? LocalOnly { get; set; } - - [JsonProperty("emojis")] - public List Emojis { get; set; } - - [JsonProperty("reactions")] - public Reactions Reactions { get; set; } - - [JsonProperty("renoteCount")] - public int? RenoteCount { get; set; } - - [JsonProperty("repliesCount")] - public int? RepliesCount { get; set; } - - [JsonProperty("uri")] - public string Uri { get; set; } - - [JsonProperty("url")] - public string Url { get; set; } - - [JsonProperty("myReaction")] - public MyReaction MyReaction { get; set; } - } - - public class User - { - [JsonProperty("id")] - public string Id { get; set; } - - [JsonProperty("name")] - public string Name { get; set; } - - [JsonProperty("username")] - public string Username { get; set; } - - [JsonProperty("host")] - public string Host { get; set; } - - [JsonProperty("avatarUrl")] - public string AvatarUrl { get; set; } - - [JsonProperty("avatarBlurhash")] - public object AvatarBlurhash { get; set; } - - [JsonProperty("avatarColor")] - public object AvatarColor { get; set; } - - [JsonProperty("isAdmin")] - public bool? IsAdmin { get; set; } - - [JsonProperty("isModerator")] - public bool? IsModerator { get; set; } - - [JsonProperty("isBot")] - public bool? IsBot { get; set; } - - [JsonProperty("isCat")] - public bool? IsCat { get; set; } - - [JsonProperty("emojis")] - public List Emojis { get; set; } - - [JsonProperty("onlineStatus")] - public string OnlineStatus { get; set; } - } -} diff --git a/osu.Game/Online/MisskeyAPI/Types.cs b/osu.Game/Online/MisskeyAPI/Types.cs index 2f4bca603f..16570b5dd9 100644 --- a/osu.Game/Online/MisskeyAPI/Types.cs +++ b/osu.Game/Online/MisskeyAPI/Types.cs @@ -6,6 +6,9 @@ using System.Collections.Generic; using JetBrains.Annotations; using Newtonsoft.Json; +using osu.Framework.Bindables; +using osu.Game.Misskey.Users; +using UserStatusOnline = osu.Game.Users.UserStatusOnline; namespace osu.Game.Online.MisskeyAPI.Responses.Types { @@ -79,7 +82,7 @@ namespace osu.Game.Online.MisskeyAPI.Responses.Types public class Choice { [JsonProperty("isVoted")] - public bool Id { get; set; } + public bool IsVoted { get; set; } [JsonProperty("text")] public string Text { get; set; } @@ -96,7 +99,7 @@ namespace osu.Game.Online.MisskeyAPI.Responses.Types public bool Multiple { get; set; } [JsonProperty("choices")] - public Choice Choices { get; set; } + public Choice[] Choices { get; set; } } public class Note // https://github.com/misskey-dev/misskey.js/blob/c89374c321aeb1cca2582922d4a9a9be059c691e/src/entities.ts#L128 @@ -715,6 +718,13 @@ namespace osu.Game.Online.MisskeyAPI.Responses.Types public class UserLite // https://github.com/misskey-dev/misskey.js/blob/c89374c321aeb1cca2582922d4a9a9be059c691e/src/entities.ts#L9 { + /// + /// A user ID which can be used to represent any system user which is not attached to a user profile. + /// + public const string SYSTEM_USER_ID = "system"; + + public readonly Bindable Status = new Bindable(); + [JsonProperty("id")] public string Id { get; set; } @@ -725,6 +735,9 @@ namespace osu.Game.Online.MisskeyAPI.Responses.Types [CanBeNull] public string Host { get; set; } + [JsonProperty("name")] + public string Name { get; set; } + [JsonProperty("onlineStatus")] // 'online' | 'active' | 'offline' | 'unknown' public string OnlineStatus { get; set; } diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index d6d4f1a67b..8b3f259d53 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -28,6 +28,7 @@ using osu.Game.Overlays.BeatmapListing; using osu.Game.Resources.Localisation.Web; using osuTK; using osuTK.Graphics; +using ExpandedContentScrollContainer = osu.Game.Screens.Misskey.Components.Note.Cards.ExpandedContentScrollContainer; namespace osu.Game.Overlays { diff --git a/osu.Game/Overlays/Rankings/SpotlightsLayout.cs b/osu.Game/Overlays/Rankings/SpotlightsLayout.cs index 8a1b929753..d87ef4099d 100644 --- a/osu.Game/Overlays/Rankings/SpotlightsLayout.cs +++ b/osu.Game/Overlays/Rankings/SpotlightsLayout.cs @@ -18,6 +18,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.Rankings.Tables; using osu.Game.Rulesets; using osuTK; +using ExpandedContentScrollContainer = osu.Game.Screens.Misskey.Components.Note.Cards.ExpandedContentScrollContainer; namespace osu.Game.Overlays.Rankings { diff --git a/osu.Game/Screens/Misskey/Components/Avatar.cs b/osu.Game/Screens/Misskey/Components/Avatar.cs index bad4d8cc3c..7c1cdfee78 100644 --- a/osu.Game/Screens/Misskey/Components/Avatar.cs +++ b/osu.Game/Screens/Misskey/Components/Avatar.cs @@ -13,26 +13,30 @@ using osu.Game.Graphics.Containers; using osu.Game.Online.API.Requests.Responses; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osuTK; namespace osu.Game.Screens.Misskey.Components { + [LongRunningLoad] public partial class Avatar : Sprite { // private readonly APIUser user; + private Online.MisskeyAPI.Responses.Types.Note note; /// /// A simple, non-interactable avatar sprite for the specified user. /// ///// The user. A null value will get a placeholder avatar. - public Avatar() + public Avatar(Online.MisskeyAPI.Responses.Types.Note note) { // this.user = user; - - RelativeSizeAxes = Axes.Both; + // RelativeSizeAxes = Axes.Both; + Size = new Vector2(10f); FillMode = FillMode.Fit; Anchor = Anchor.Centre; Origin = Anchor.Centre; + this.note = note; } [BackgroundDependencyLoader] @@ -43,7 +47,7 @@ namespace osu.Game.Screens.Misskey.Components // // in remaining cases where this is required (chat tabs, local leaderboard), at which point this should be removed. // Texture = textures.Get(user.AvatarUrl ?? $@"https://a.ppy.sh/{user.Id}"); - Texture ??= textures.Get(@"https://simkey.net/files/thumbnail-328eb27f-f06f-4454-ad52-a79d5f780a6b"); + Texture ??= textures.Get(note.User.AvatarUrl); } protected override void LoadComplete() diff --git a/osu.Game/Screens/Misskey/Components/Note/Cards/BeatmapCardDownloadProgressBar.cs b/osu.Game/Screens/Misskey/Components/Note/Cards/BeatmapCardDownloadProgressBar.cs new file mode 100644 index 0000000000..386ea1ca57 --- /dev/null +++ b/osu.Game/Screens/Misskey/Components/Note/Cards/BeatmapCardDownloadProgressBar.cs @@ -0,0 +1,98 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps.Drawables.Cards; +using osu.Game.Graphics; +using osu.Game.Online; +using osu.Game.Overlays; + +namespace osu.Game.Screens.Misskey.Components.Note.Cards +{ + public partial class BeatmapCardDownloadProgressBar : CompositeDrawable + { + public IBindable State => state; + private readonly Bindable state = new Bindable(); + + public IBindable Progress => progress; + private readonly BindableDouble progress = new BindableDouble(); + + public override bool IsPresent => true; + + private readonly CircularContainer foreground; + + private readonly Box backgroundFill; + private readonly Box foregroundFill; + + [Resolved] + private OsuColour colours { get; set; } + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } + + public BeatmapCardDownloadProgressBar() + { + InternalChildren = new Drawable[] + { + new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Child = backgroundFill = new Box + { + RelativeSizeAxes = Axes.Both, + } + }, + foreground = new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Child = foregroundFill = new Box + { + RelativeSizeAxes = Axes.Both, + } + } + }; + } + + [BackgroundDependencyLoader] + private void load() + { + backgroundFill.Colour = colourProvider.Background6; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + state.BindValueChanged(_ => stateChanged(), true); + progress.BindValueChanged(_ => progressChanged(), true); + } + + private void stateChanged() + { + switch (state.Value) + { + case DownloadState.Downloading: + FinishTransforms(true); + foregroundFill.Colour = colourProvider.Highlight1; + break; + + case DownloadState.Importing: + foregroundFill.FadeColour(colours.Yellow, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + break; + } + } + + private void progressChanged() + { + foreground.ResizeWidthTo((float)progress.Value, progress.Value > 0 ? BeatmapCard.TRANSITION_DURATION : 0, Easing.OutQuint); + } + } +} diff --git a/osu.Game/Screens/Misskey/Components/Note/Cards/BeatmapSetFavouriteState.cs b/osu.Game/Screens/Misskey/Components/Note/Cards/BeatmapSetFavouriteState.cs new file mode 100644 index 0000000000..0c0d1bac06 --- /dev/null +++ b/osu.Game/Screens/Misskey/Components/Note/Cards/BeatmapSetFavouriteState.cs @@ -0,0 +1,33 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using osu.Game.Screens.Misskey.Components.Note.Cards.Buttons; +using osu.Game.Screens.Misskey.Components.Note.Cards.Statistics; + +namespace osu.Game.Screens.Misskey.Components.Note.Cards +{ + /// + /// Stores the current favourite state of a beatmap set. + /// Used to coordinate between and . + /// + public readonly struct BeatmapSetFavouriteState + { + /// + /// Whether the currently logged-in user has favourited this beatmap. + /// + public bool Favourited { get; } + + /// + /// The number of favourites that the beatmap set has received, including the currently logged-in user. + /// + public int FavouriteCount { get; } + + public BeatmapSetFavouriteState(bool favourited, int favouriteCount) + { + Favourited = favourited; + FavouriteCount = favouriteCount; + } + } +} diff --git a/osu.Game/Screens/Misskey/Components/Note/Cards/Buttons/BeatmapCardIconButton.cs b/osu.Game/Screens/Misskey/Components/Note/Cards/Buttons/BeatmapCardIconButton.cs new file mode 100644 index 0000000000..5eda213ee7 --- /dev/null +++ b/osu.Game/Screens/Misskey/Components/Note/Cards/Buttons/BeatmapCardIconButton.cs @@ -0,0 +1,137 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; +using osu.Game.Beatmaps.Drawables.Cards; +using osu.Game.Graphics.Containers; +using osu.Game.Overlays; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Misskey.Components.Note.Cards.Buttons +{ + public abstract partial class BeatmapCardIconButton : OsuClickableContainer + { + private Colour4 idleColour; + + public Colour4 IdleColour + { + get => idleColour; + set + { + idleColour = value; + if (IsLoaded) + updateState(); + } + } + + private Colour4 hoverColour; + + public Colour4 HoverColour + { + get => hoverColour; + set + { + hoverColour = value; + if (IsLoaded) + updateState(); + } + } + + private float iconSize; + + public float IconSize + { + get => iconSize; + set + { + iconSize = value; + Icon.Size = new Vector2(iconSize); + } + } + + protected readonly SpriteIcon Icon; + + protected override Container Content => content; + + private readonly Container content; + private readonly Box hover; + + protected BeatmapCardIconButton() + { + Origin = Anchor.Centre; + Anchor = Anchor.Centre; + + base.Content.Add(content = new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = BeatmapCard.CORNER_RADIUS, + Scale = new Vector2(0.8f), + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Children = new Drawable[] + { + hover = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.White.Opacity(0.1f), + Blending = BlendingParameters.Additive, + }, + Icon = new SpriteIcon + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Scale = new Vector2(1.2f), + }, + } + }); + + IconSize = 12; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + IdleColour = colourProvider.Light1; + HoverColour = colourProvider.Content1; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Enabled.BindValueChanged(_ => updateState(), true); + FinishTransforms(true); + } + + protected override bool OnHover(HoverEvent e) + { + updateState(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + updateState(); + } + + private void updateState() + { + bool isHovered = IsHovered && Enabled.Value; + + hover.FadeTo(isHovered ? 1f : 0f, 500, Easing.OutQuint); + content.ScaleTo(isHovered ? 1 : 0.8f, 500, Easing.OutQuint); + Icon.FadeColour(isHovered ? HoverColour : IdleColour, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + } + } +} diff --git a/osu.Game/Screens/Misskey/Components/Note/Cards/Buttons/DownloadButton.cs b/osu.Game/Screens/Misskey/Components/Note/Cards/Buttons/DownloadButton.cs new file mode 100644 index 0000000000..3243523685 --- /dev/null +++ b/osu.Game/Screens/Misskey/Components/Note/Cards/Buttons/DownloadButton.cs @@ -0,0 +1,103 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables.Cards; +using osu.Game.Configuration; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Resources.Localisation.Web; +using osuTK; + +namespace osu.Game.Screens.Misskey.Components.Note.Cards.Buttons +{ + public partial class DownloadButton : BeatmapCardIconButton + { + public Bindable State { get; } = new Bindable(); + + private readonly APIBeatmapSet beatmapSet; + + private Bindable preferNoVideo = null!; + + private readonly LoadingSpinner spinner; + + [Resolved] + private BeatmapModelDownloader beatmaps { get; set; } = null!; + + public DownloadButton(APIBeatmapSet beatmapSet) + { + Icon.Icon = FontAwesome.Solid.Download; + + Content.Add(spinner = new LoadingSpinner { Size = new Vector2(IconSize) }); + + this.beatmapSet = beatmapSet; + } + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + preferNoVideo = config.GetBindable(OsuSetting.PreferNoVideo); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + preferNoVideo.BindValueChanged(_ => updateState()); + State.BindValueChanged(_ => updateState(), true); + FinishTransforms(true); + } + + private void updateState() + { + switch (State.Value) + { + case DownloadState.Unknown: + Action = null; + TooltipText = string.Empty; + break; + + case DownloadState.Downloading: + case DownloadState.Importing: + Action = null; + TooltipText = string.Empty; + spinner.Show(); + Icon.Hide(); + break; + + case DownloadState.LocallyAvailable: + Action = null; + TooltipText = string.Empty; + this.FadeOut(BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + break; + + case DownloadState.NotDownloaded: + if (beatmapSet.Availability.DownloadDisabled) + { + Enabled.Value = false; + TooltipText = BeatmapsetsStrings.AvailabilityDisabled; + return; + } + + Action = () => beatmaps.Download(beatmapSet, preferNoVideo.Value); + this.FadeIn(BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + spinner.Hide(); + Icon.Show(); + + if (!beatmapSet.HasVideo) + TooltipText = BeatmapsetsStrings.PanelDownloadAll; + else + TooltipText = preferNoVideo.Value ? BeatmapsetsStrings.PanelDownloadNoVideo : BeatmapsetsStrings.PanelDownloadVideo; + break; + + default: + throw new InvalidOperationException($"Unknown {nameof(DownloadState)} specified."); + } + } + } +} diff --git a/osu.Game/Screens/Misskey/Components/Note/Cards/Buttons/FavouriteButton.cs b/osu.Game/Screens/Misskey/Components/Note/Cards/Buttons/FavouriteButton.cs new file mode 100644 index 0000000000..5470f47015 --- /dev/null +++ b/osu.Game/Screens/Misskey/Components/Note/Cards/Buttons/FavouriteButton.cs @@ -0,0 +1,88 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Logging; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Resources.Localisation.Web; + +namespace osu.Game.Screens.Misskey.Components.Note.Cards.Buttons +{ + public partial class FavouriteButton : BeatmapCardIconButton, IHasCurrentValue + { + private readonly BindableWithCurrent current; + + public Bindable Current + { + get => current.Current; + set => current.Current = value; + } + + private readonly APIBeatmapSet beatmapSet; + + private PostBeatmapFavouriteRequest favouriteRequest; + + [Resolved] + private IAPIProvider api { get; set; } + + public FavouriteButton(APIBeatmapSet beatmapSet) + { + current = new BindableWithCurrent(new BeatmapSetFavouriteState(beatmapSet.HasFavourited, beatmapSet.FavouriteCount)); + this.beatmapSet = beatmapSet; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Action = toggleFavouriteStatus; + current.BindValueChanged(_ => updateState(), true); + } + + private void toggleFavouriteStatus() + { + var actionType = current.Value.Favourited ? BeatmapFavouriteAction.UnFavourite : BeatmapFavouriteAction.Favourite; + + favouriteRequest?.Cancel(); + favouriteRequest = new PostBeatmapFavouriteRequest(beatmapSet.OnlineID, actionType); + + Enabled.Value = false; + favouriteRequest.Success += () => + { + bool favourited = actionType == BeatmapFavouriteAction.Favourite; + + current.Value = new BeatmapSetFavouriteState(favourited, current.Value.FavouriteCount + (favourited ? 1 : -1)); + + Enabled.Value = true; + }; + favouriteRequest.Failure += e => + { + Logger.Error(e, $"Failed to {actionType.ToString().ToLowerInvariant()} beatmap: {e.Message}"); + Enabled.Value = true; + }; + + api.Queue(favouriteRequest); + } + + private void updateState() + { + if (current.Value.Favourited) + { + Icon.Icon = FontAwesome.Solid.Heart; + TooltipText = BeatmapsetsStrings.ShowDetailsUnfavourite; + } + else + { + Icon.Icon = FontAwesome.Regular.Heart; + TooltipText = BeatmapsetsStrings.ShowDetailsFavourite; + } + } + } +} diff --git a/osu.Game/Screens/Misskey/Components/Note/Cards/Buttons/GoToBeatmapButton.cs b/osu.Game/Screens/Misskey/Components/Note/Cards/Buttons/GoToBeatmapButton.cs new file mode 100644 index 0000000000..6914cc56b8 --- /dev/null +++ b/osu.Game/Screens/Misskey/Components/Note/Cards/Buttons/GoToBeatmapButton.cs @@ -0,0 +1,48 @@ +// 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.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Game.Beatmaps.Drawables.Cards; +using osu.Game.Online; +using osu.Game.Online.API.Requests.Responses; + +namespace osu.Game.Screens.Misskey.Components.Note.Cards.Buttons +{ + public partial class GoToBeatmapButton : BeatmapCardIconButton + { + public IBindable State => state; + private readonly Bindable state = new Bindable(); + + private readonly APIBeatmapSet beatmapSet; + + public GoToBeatmapButton(APIBeatmapSet beatmapSet) + { + this.beatmapSet = beatmapSet; + + Icon.Icon = FontAwesome.Solid.AngleDoubleRight; + TooltipText = "Go to beatmap"; + } + + [BackgroundDependencyLoader(true)] + private void load(OsuGame? game) + { + Action = () => game?.PresentBeatmap(beatmapSet); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + state.BindValueChanged(_ => updateState(), true); + FinishTransforms(true); + } + + private void updateState() + { + this.FadeTo(state.Value == DownloadState.LocallyAvailable ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + } + } +} diff --git a/osu.Game/Screens/Misskey/Components/Note/Cards/Buttons/PlayButton.cs b/osu.Game/Screens/Misskey/Components/Note/Cards/Buttons/PlayButton.cs new file mode 100644 index 0000000000..6c1037f388 --- /dev/null +++ b/osu.Game/Screens/Misskey/Components/Note/Cards/Buttons/PlayButton.cs @@ -0,0 +1,154 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Audio; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables.Cards; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; +using osuTK; + +namespace osu.Game.Screens.Misskey.Components.Note.Cards.Buttons +{ + public partial class PlayButton : OsuHoverContainer + { + public IBindable Progress => progress; + private readonly BindableDouble progress = new BindableDouble(); + + public BindableBool Playing { get; } = new BindableBool(); + + private readonly IBeatmapSetInfo beatmapSetInfo; + + protected override IEnumerable EffectTargets => icon.Yield(); + + private readonly SpriteIcon icon; + private readonly LoadingSpinner loadingSpinner; + + [Resolved] + private PreviewTrackManager previewTrackManager { get; set; } = null!; + + private PreviewTrack? previewTrack; + + public PlayButton(IBeatmapSetInfo beatmapSetInfo) + { + this.beatmapSetInfo = beatmapSetInfo; + + Anchor = Origin = Anchor.Centre; + + // needed for touch input to work when card is not hovered/expanded + AlwaysPresent = true; + + Children = new Drawable[] + { + icon = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Icon = FontAwesome.Solid.Play, + Size = new Vector2(14) + }, + loadingSpinner = new LoadingSpinner + { + Size = new Vector2(14) + } + }; + + Action = () => Playing.Toggle(); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + HoverColour = colours.Yellow; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Playing.BindValueChanged(updateState, true); + } + + protected override void Update() + { + base.Update(); + + if (Playing.Value && previewTrack != null && previewTrack.TrackLoaded) + progress.Value = previewTrack.CurrentTime / previewTrack.Length; + else + progress.Value = 0; + } + + private void updateState(ValueChangedEvent playing) + { + icon.Icon = playing.NewValue ? FontAwesome.Solid.Stop : FontAwesome.Solid.Play; + + if (!playing.NewValue) + { + stopPreview(); + return; + } + + if (previewTrack == null) + { + toggleLoading(true); + + LoadComponentAsync(previewTrack = previewTrackManager.Get(beatmapSetInfo), onPreviewLoaded); + } + else + tryStartPreview(); + } + + private void stopPreview() + { + toggleLoading(false); + Playing.Value = false; + previewTrack?.Stop(); + } + + private void onPreviewLoaded(PreviewTrack loadedPreview) + { + // Make sure that we schedule to after the next audio frame to fix crashes in single-threaded execution. + // See: https://github.com/ppy/osu-framework/issues/4692 + Schedule(() => + { + // another async load might have completed before this one. + // if so, do not make any changes. + if (loadedPreview != previewTrack) + { + loadedPreview.Dispose(); + return; + } + + AddInternal(loadedPreview); + toggleLoading(false); + + loadedPreview.Stopped += () => Schedule(() => Playing.Value = false); + + if (Playing.Value) + tryStartPreview(); + }); + } + + private void tryStartPreview() + { + if (previewTrack?.Start() == false) + Playing.Value = false; + } + + private void toggleLoading(bool loading) + { + Enabled.Value = !loading; + icon.FadeTo(loading ? 0 : 1, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + loadingSpinner.State.Value = loading ? Visibility.Visible : Visibility.Hidden; + } + } +} diff --git a/osu.Game/Screens/Misskey/Components/DrawableNoteCard.cs b/osu.Game/Screens/Misskey/Components/Note/Cards/DrawableNoteCard.cs similarity index 54% rename from osu.Game/Screens/Misskey/Components/DrawableNoteCard.cs rename to osu.Game/Screens/Misskey/Components/Note/Cards/DrawableNoteCard.cs index 7a35d206e5..4170bd3350 100644 --- a/osu.Game/Screens/Misskey/Components/DrawableNoteCard.cs +++ b/osu.Game/Screens/Misskey/Components/Note/Cards/DrawableNoteCard.cs @@ -10,10 +10,9 @@ using osu.Game.Beatmaps.Drawables.Cards; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online; -using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; -namespace osu.Game.Screens.Misskey.Components +namespace osu.Game.Screens.Misskey.Components.Note.Cards { public abstract partial class DrawableNoteCard : OsuClickableContainer { @@ -24,38 +23,38 @@ namespace osu.Game.Screens.Misskey.Components public IBindable Expanded { get; } - public readonly APIBeatmapSet BeatmapSet; + public readonly Online.MisskeyAPI.Responses.Types.Note Note; - protected readonly Bindable FavouriteState; + // protected readonly Bindable FavouriteState; - protected abstract Drawable IdleContent { get; } - protected abstract Drawable DownloadInProgressContent { get; } + // protected abstract Drawable IdleContent { get; } + // protected abstract Drawable DownloadInProgressContent { get; } - protected readonly BeatmapDownloadTracker DownloadTracker; + // protected readonly BeatmapDownloadTracker DownloadTracker; - protected DrawableNoteCard(APIBeatmapSet beatmapSet, bool allowExpansion = true) + protected DrawableNoteCard(Online.MisskeyAPI.Responses.Types.Note note, bool allowExpansion = true) : base(HoverSampleSet.Button) { Expanded = new BindableBool { Disabled = !allowExpansion }; - BeatmapSet = beatmapSet; - FavouriteState = new Bindable(new BeatmapSetFavouriteState(beatmapSet.HasFavourited, beatmapSet.FavouriteCount)); - DownloadTracker = new BeatmapDownloadTracker(beatmapSet); + Note = note; + // FavouriteState = new Bindable(new BeatmapSetFavouriteState(note.HasFavourited, note.FavouriteCount)); + // DownloadTracker = new BeatmapDownloadTracker(note); } [BackgroundDependencyLoader(true)] private void load(BeatmapSetOverlay? beatmapSetOverlay) { - Action = () => beatmapSetOverlay?.FetchAndShowBeatmapSet(BeatmapSet.OnlineID); + // Action = () => beatmapSetOverlay?.FetchAndShowBeatmapSet(Note.OnlineID); - AddInternal(DownloadTracker); + // AddInternal(DownloadTracker); } protected override void LoadComplete() { base.LoadComplete(); - DownloadTracker.State.BindValueChanged(_ => UpdateState()); + // DownloadTracker.State.BindValueChanged(_ => UpdateState()); Expanded.BindValueChanged(_ => UpdateState(), true); FinishTransforms(true); } @@ -74,24 +73,24 @@ namespace osu.Game.Screens.Misskey.Components protected virtual void UpdateState() { - bool showProgress = DownloadTracker.State.Value == DownloadState.Downloading || DownloadTracker.State.Value == DownloadState.Importing; + // bool showProgress = DownloadTracker.State.Value == DownloadState.Downloading || DownloadTracker.State.Value == DownloadState.Importing; - IdleContent.FadeTo(showProgress ? 0 : 1, TRANSITION_DURATION, Easing.OutQuint); - DownloadInProgressContent.FadeTo(showProgress ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint); + // IdleContent.FadeTo(showProgress ? 0 : 1, TRANSITION_DURATION, Easing.OutQuint); + // DownloadInProgressContent.FadeTo(showProgress ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint); } /// - /// Creates a beatmap card of the given for the supplied . + /// Creates a beatmap card of the given for the supplied . /// - public static BeatmapCard Create(APIBeatmapSet beatmapSet, BeatmapCardSize size, bool allowExpansion = true) + public static NoteCard Create(Online.MisskeyAPI.Responses.Types.Note note, BeatmapCardSize size, bool allowExpansion = true) { switch (size) { case BeatmapCardSize.Normal: - return new BeatmapCardNormal(beatmapSet, allowExpansion); + return new NoteCardNormal(note, allowExpansion); case BeatmapCardSize.Extra: - return new BeatmapCardExtra(beatmapSet, allowExpansion); + return new NoteCardNormal(note, allowExpansion); //todo: extra default: throw new ArgumentOutOfRangeException(nameof(size), size, @"Unsupported card size"); diff --git a/osu.Game/Screens/Misskey/Components/Note/Cards/ExpandedContentScrollContainer.cs b/osu.Game/Screens/Misskey/Components/Note/Cards/ExpandedContentScrollContainer.cs new file mode 100644 index 0000000000..1ce3a6b5ee --- /dev/null +++ b/osu.Game/Screens/Misskey/Components/Note/Cards/ExpandedContentScrollContainer.cs @@ -0,0 +1,81 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using System; +using osu.Framework.Graphics; +using osu.Framework.Input.Events; +using osu.Framework.Utils; +using osu.Game.Graphics.Containers; + +namespace osu.Game.Screens.Misskey.Components.Note.Cards +{ + public partial class ExpandedContentScrollContainer : OsuScrollContainer + { + public const float HEIGHT = 200; + + protected override ScrollbarContainer CreateScrollbar(Direction direction) => new ExpandedContentScrollbar(direction); + + protected override void Update() + { + base.Update(); + + Height = Math.Min(Content.DrawHeight, HEIGHT); + ScrollbarVisible = allowScroll; + } + + private bool allowScroll => !Precision.AlmostEquals(DrawSize, Content.DrawSize); + + protected override bool OnDragStart(DragStartEvent e) + { + if (!allowScroll) + return false; + + return base.OnDragStart(e); + } + + protected override void OnDrag(DragEvent e) + { + if (!allowScroll) + return; + + base.OnDrag(e); + } + + protected override void OnDragEnd(DragEndEvent e) + { + if (!allowScroll) + return; + + base.OnDragEnd(e); + } + + protected override bool OnScroll(ScrollEvent e) + { + if (!allowScroll) + return false; + + return base.OnScroll(e); + } + + protected override bool OnClick(ClickEvent e) => true; + + private partial class ExpandedContentScrollbar : OsuScrollbar + { + public ExpandedContentScrollbar(Direction scrollDir) + : base(scrollDir) + { + } + + protected override bool OnHover(HoverEvent e) + { + base.OnHover(e); + // do not handle hover, as handling hover would make the beatmap card's expanded content not-hovered + // and therefore cause it to hide when trying to drag the scroll bar. + // see: `BeatmapCardContent.dropdownContent` and its `Unhovered` handler. + return false; + } + } + } +} diff --git a/osu.Game/Screens/Misskey/Components/Note/Cards/HoverHandlingContainer.cs b/osu.Game/Screens/Misskey/Components/Note/Cards/HoverHandlingContainer.cs new file mode 100644 index 0000000000..f15034c353 --- /dev/null +++ b/osu.Game/Screens/Misskey/Components/Note/Cards/HoverHandlingContainer.cs @@ -0,0 +1,27 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Events; + +namespace osu.Game.Screens.Misskey.Components.Note.Cards +{ + public partial class HoverHandlingContainer : Container + { + public Func? Hovered { get; set; } + public Action? Unhovered { get; set; } + + protected override bool OnHover(HoverEvent e) + { + bool handledByBase = base.OnHover(e); + return Hovered?.Invoke(e) ?? handledByBase; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + Unhovered?.Invoke(e); + } + } +} diff --git a/osu.Game/Screens/Misskey/Components/Note/Cards/IconPill.cs b/osu.Game/Screens/Misskey/Components/Note/Cards/IconPill.cs new file mode 100644 index 0000000000..5bb3478bf8 --- /dev/null +++ b/osu.Game/Screens/Misskey/Components/Note/Cards/IconPill.cs @@ -0,0 +1,57 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Misskey.Components.Note.Cards +{ + public abstract partial class IconPill : CircularContainer, IHasTooltip + { + public Vector2 IconSize + { + get => iconContainer.Size; + set => iconContainer.Size = value; + } + + private readonly Container iconContainer; + + protected IconPill(IconUsage icon) + { + AutoSizeAxes = Axes.Both; + Masking = true; + + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + Alpha = 0.5f, + }, + iconContainer = new Container + { + Size = new Vector2(22), + Padding = new MarginPadding(5), + Child = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Icon = icon, + }, + }, + }; + } + + public abstract LocalisableString TooltipText { get; } + } +} diff --git a/osu.Game/Screens/Misskey/Components/NoteCard.cs b/osu.Game/Screens/Misskey/Components/Note/Cards/NoteCard.cs similarity index 57% rename from osu.Game/Screens/Misskey/Components/NoteCard.cs rename to osu.Game/Screens/Misskey/Components/Note/Cards/NoteCard.cs index cd947be57f..8805be2939 100644 --- a/osu.Game/Screens/Misskey/Components/NoteCard.cs +++ b/osu.Game/Screens/Misskey/Components/Note/Cards/NoteCard.cs @@ -13,7 +13,7 @@ using osu.Game.Online; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; -namespace osu.Game.Screens.Misskey.Components +namespace osu.Game.Screens.Misskey.Components.Note.Cards { public abstract partial class NoteCard : OsuClickableContainer { @@ -24,38 +24,38 @@ namespace osu.Game.Screens.Misskey.Components public IBindable Expanded { get; } - public readonly APIBeatmapSet BeatmapSet; + public readonly Online.MisskeyAPI.Responses.Types.Note Note; - protected readonly Bindable FavouriteState; + // protected readonly Bindable FavouriteState; protected abstract Drawable IdleContent { get; } protected abstract Drawable DownloadInProgressContent { get; } - protected readonly BeatmapDownloadTracker DownloadTracker; + // protected readonly BeatmapDownloadTracker DownloadTracker; - protected NoteCard(APIBeatmapSet beatmapSet, bool allowExpansion = true) + protected NoteCard(Online.MisskeyAPI.Responses.Types.Note note, bool allowExpansion = true) : base(HoverSampleSet.Button) { Expanded = new BindableBool { Disabled = !allowExpansion }; - BeatmapSet = beatmapSet; - FavouriteState = new Bindable(new BeatmapSetFavouriteState(beatmapSet.HasFavourited, beatmapSet.FavouriteCount)); - DownloadTracker = new BeatmapDownloadTracker(beatmapSet); + Note = note; + // FavouriteState = new Bindable(new BeatmapSetFavouriteState(note.HasFavourited, note.FavouriteCount)); + // DownloadTracker = new BeatmapDownloadTracker(note); } [BackgroundDependencyLoader(true)] private void load(BeatmapSetOverlay? beatmapSetOverlay) { - Action = () => beatmapSetOverlay?.FetchAndShowBeatmapSet(BeatmapSet.OnlineID); + // Action = () => beatmapSetOverlay?.FetchAndShowBeatmapSet(Note.OnlineID); - AddInternal(DownloadTracker); + // AddInternal(DownloadTracker); } protected override void LoadComplete() { base.LoadComplete(); - DownloadTracker.State.BindValueChanged(_ => UpdateState()); + // DownloadTracker.State.BindValueChanged(_ => UpdateState()); Expanded.BindValueChanged(_ => UpdateState(), true); FinishTransforms(true); } @@ -74,24 +74,24 @@ namespace osu.Game.Screens.Misskey.Components protected virtual void UpdateState() { - bool showProgress = DownloadTracker.State.Value == DownloadState.Downloading || DownloadTracker.State.Value == DownloadState.Importing; - - IdleContent.FadeTo(showProgress ? 0 : 1, TRANSITION_DURATION, Easing.OutQuint); - DownloadInProgressContent.FadeTo(showProgress ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint); + // bool showProgress = DownloadTracker.State.Value == DownloadState.Downloading || DownloadTracker.State.Value == DownloadState.Importing; + // + // IdleContent.FadeTo(showProgress ? 0 : 1, TRANSITION_DURATION, Easing.OutQuint); + // DownloadInProgressContent.FadeTo(showProgress ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint); } /// - /// Creates a beatmap card of the given for the supplied . + /// Creates a beatmap card of the given for the supplied . /// - public static BeatmapCard Create(APIBeatmapSet beatmapSet, BeatmapCardSize size, bool allowExpansion = true) + public static NoteCard Create(Online.MisskeyAPI.Responses.Types.Note note, BeatmapCardSize size, bool allowExpansion = true) { switch (size) { case BeatmapCardSize.Normal: - return new BeatmapCardNormal(beatmapSet, allowExpansion); + return new NoteCardNormal(note, allowExpansion); - case BeatmapCardSize.Extra: - return new BeatmapCardExtra(beatmapSet, allowExpansion); + // case BeatmapCardSize.Extra: + // return new BeatmapCardExtra(note, allowExpansion); default: throw new ArgumentOutOfRangeException(nameof(size), size, @"Unsupported card size"); diff --git a/osu.Game/Screens/Misskey/Components/Note/Cards/NoteCardAvatar.cs b/osu.Game/Screens/Misskey/Components/Note/Cards/NoteCardAvatar.cs new file mode 100644 index 0000000000..c3062c3b45 --- /dev/null +++ b/osu.Game/Screens/Misskey/Components/Note/Cards/NoteCardAvatar.cs @@ -0,0 +1,75 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Beatmaps.Drawables; +using osu.Game.Beatmaps.Drawables.Cards; +using osu.Game.Beatmaps.Drawables.Cards.Buttons; +using osu.Game.Graphics; +using osu.Game.Misskey.Users.Drawables; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.MisskeyAPI.Responses.Types; +using osu.Game.Overlays; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Misskey.Components.Note.Cards +{ + public partial class NoteCardAvatar : Container + { + public BindableBool Dimmed { get; } = new BindableBool(); + + private Drawable avatar; + + private readonly User user; + + + public NoteCardAvatar(Online.MisskeyAPI.Responses.Types.Note note) + { + this.user = note.User; + } + + [BackgroundDependencyLoader] + private void load() + { + ClickableAvatar internalAvatar; + + Children = new[] + { + avatar = new DelayedLoadWrapper( + internalAvatar = new ClickableAvatar(user) + { + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 50, + // EdgeEffect = new EdgeEffectParameters + // { + // Type = EdgeEffectType.Shadow, + // Radius = 1, + // Colour = Color4.Black.Opacity(0.2f), + // }, + }) + { + RelativeSizeAxes = Axes.Both, + // Size = new Vector2(HEIGHT - edge_margin * 2, HEIGHT - edge_margin * 2), + } + }; + // internalAvatar.OnLoadComplete += d => d.FadeInFromZero(200); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + } + + private void updateState() + { + } + } +} diff --git a/osu.Game/Screens/Misskey/Components/Note/Cards/NoteCardContent.cs b/osu.Game/Screens/Misskey/Components/Note/Cards/NoteCardContent.cs new file mode 100644 index 0000000000..fc6db22592 --- /dev/null +++ b/osu.Game/Screens/Misskey/Components/Note/Cards/NoteCardContent.cs @@ -0,0 +1,156 @@ +// 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.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Threading; +using osu.Game.Beatmaps.Drawables.Cards; +using osu.Game.Graphics.Containers; +using osu.Game.Overlays; +using osuTK; + +namespace osu.Game.Screens.Misskey.Components.Note.Cards +{ + public partial class NoteCardContent : CompositeDrawable + { + public Drawable MainContent + { + set => bodyContent.Child = value; + } + + public Drawable ExpandedContent + { + set => dropdownScroll.Child = value; + } + + public IBindable Expanded => expanded; + + private readonly BindableBool expanded = new BindableBool(); + + private readonly Box background; + private readonly Container content; + private readonly Container bodyContent; + private readonly Container dropdownContent; + private readonly OsuScrollContainer dropdownScroll; + private readonly Container borderContainer; + + public NoteCardContent(float height) + { + RelativeSizeAxes = Axes.X; + Height = height; + + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + InternalChild = content = new HoverHandlingContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + CornerRadius = NoteCard.CORNER_RADIUS, + Masking = true, + Unhovered = _ => updateFromHoverChange(), + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both + }, + bodyContent = new Container + { + RelativeSizeAxes = Axes.X, + Height = height, + CornerRadius = NoteCard.CORNER_RADIUS, + Masking = true, + }, + dropdownContent = new HoverHandlingContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Margin = new MarginPadding { Top = height }, + Alpha = 0, + Hovered = _ => + { + updateFromHoverChange(); + return true; + }, + Unhovered = _ => updateFromHoverChange(), + Child = dropdownScroll = new ExpandedContentScrollContainer + { + RelativeSizeAxes = Axes.X, + ScrollbarVisible = false + } + }, + borderContainer = new Container + { + RelativeSizeAxes = Axes.Both, + CornerRadius = NoteCard.CORNER_RADIUS, + Masking = true, + BorderThickness = 3, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true + } + } + } + }; + } + + [BackgroundDependencyLoader] + private void load() + { + background.Colour = Colour4.Gray; + borderContainer.BorderColour = Colour4.White; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + Expanded.BindValueChanged(_ => updateState(), true); + FinishTransforms(true); + } + + private ScheduledDelegate? scheduledExpandedChange; + + public void ExpandAfterDelay() => queueExpandedStateChange(true, 100); + + public void CancelExpand() => scheduledExpandedChange?.Cancel(); + + private void updateFromHoverChange() => + queueExpandedStateChange(content.IsHovered || dropdownContent.IsHovered, 100); + + private void queueExpandedStateChange(bool newState, int delay = 0) + { + if (Expanded.Disabled) + return; + + scheduledExpandedChange?.Cancel(); + scheduledExpandedChange = Scheduler.AddDelayed(() => expanded.Value = newState, delay); + } + + private void updateState() + { + // Scale value is intentionally chosen to fit in the spacing of listing displays, as to not overlap horizontally with adjacent cards. + // This avoids depth issues where a hovered (scaled) card to the right of another card would be beneath the card to the left. + this.ScaleTo(Expanded.Value ? 1.03f : 1, 500, Easing.OutQuint); + + background.FadeTo(Expanded.Value ? 1 : 0, NoteCard.TRANSITION_DURATION, Easing.OutQuint); + dropdownContent.FadeTo(Expanded.Value ? 1 : 0, NoteCard.TRANSITION_DURATION, Easing.OutQuint); + borderContainer.FadeTo(Expanded.Value ? 1 : 0, NoteCard.TRANSITION_DURATION, Easing.OutQuint); + + content.TweenEdgeEffectTo(new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Offset = new Vector2(0, 2), + Radius = 10, + Colour = Colour4.Black.Opacity(Expanded.Value ? 0.3f : 0f), + Hollow = true, + }, NoteCard.TRANSITION_DURATION, Easing.OutQuint); + } + } +} diff --git a/osu.Game/Screens/Misskey/Components/NoteCardNormal.cs b/osu.Game/Screens/Misskey/Components/Note/Cards/NoteCardNormal.cs similarity index 52% rename from osu.Game/Screens/Misskey/Components/NoteCardNormal.cs rename to osu.Game/Screens/Misskey/Components/Note/Cards/NoteCardNormal.cs index fe252cc4d0..76b6ab8ac6 100644 --- a/osu.Game/Screens/Misskey/Components/NoteCardNormal.cs +++ b/osu.Game/Screens/Misskey/Components/Note/Cards/NoteCardNormal.cs @@ -7,19 +7,20 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Localisation; using osu.Game.Beatmaps.Drawables.Cards; -using osu.Game.Beatmaps.Drawables.Cards.Statistics; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; -using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.MisskeyAPI; using osu.Game.Overlays; using osu.Game.Overlays.BeatmapSet; -using osuTK; using osu.Game.Resources.Localisation.Web; +using osu.Game.Screens.Misskey.Components.Note.Cards.Statistics; +using osu.Game.Skinning.Components; +using osuTK; -namespace osu.Game.Screens.Misskey.Components +namespace osu.Game.Screens.Misskey.Components.Note.Cards { - public partial class NoteCardNormal : DrawableNoteCard + public partial class NoteCardNormal : NoteCard { protected override Drawable IdleContent => idleBottomContent; protected override Drawable DownloadInProgressContent => downloadProgressBar; @@ -27,23 +28,27 @@ namespace osu.Game.Screens.Misskey.Components private const float height = 100; [Cached] - private readonly BeatmapCardContent content; + private readonly NoteCardContent content; - private BeatmapCardThumbnail thumbnail = null!; - private CollapsibleButtonContainer buttonContainer = null!; + private NoteCardAvatar thumbnail = null!; + // private CollapsibleButtonContainer buttonContainer = null!; - private FillFlowContainer statisticsContainer = null!; + // private FillFlowContainer statisticsContainer = null!; + private TextFlowContainer noteText = null!; private FillFlowContainer idleBottomContent = null!; private BeatmapCardDownloadProgressBar downloadProgressBar = null!; [Resolved] - private OverlayColourProvider colourProvider { get; set; } = null!; + private IAPIProvider api { get; set; } = null!; - public NoteCardNormal(APIBeatmapSet beatmapSet, bool allowExpansion = true) - : base(beatmapSet, allowExpansion) + // [Resolved] + // private OverlayColourProvider colourProvider { get; set; } = null!; + + public NoteCardNormal(Online.MisskeyAPI.Responses.Types.Note note, bool allowExpansion = true) + : base(note, allowExpansion) { - content = new BeatmapCardContent(height); + content = new NoteCardContent(height); } [BackgroundDependencyLoader] @@ -56,6 +61,9 @@ namespace osu.Game.Screens.Misskey.Components FillFlowContainer titleBadgeArea = null!; GridContainer artistContainer = null!; + // LinkFlowContainer titleText = null!; + LinkFlowContainer artistText = null!; + Child = content.With(c => { c.MainContent = new Container @@ -63,11 +71,11 @@ namespace osu.Game.Screens.Misskey.Components RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - thumbnail = new BeatmapCardThumbnail(BeatmapSet) + thumbnail = new NoteCardAvatar(Note) { Name = @"Left (icon) area", - Size = new Vector2(height), - Padding = new MarginPadding { Right = CORNER_RADIUS }, + Size = new Vector2(height - 10), + // Padding = new MarginPadding { Right = CORNER_RADIUS }, Child = leftIconArea = new FillFlowContainer { Margin = new MarginPadding(5), @@ -76,17 +84,19 @@ namespace osu.Game.Screens.Misskey.Components Spacing = new Vector2(1) } }, - buttonContainer = new CollapsibleButtonContainer(BeatmapSet) - { - X = height - CORNER_RADIUS, - Width = WIDTH - height + CORNER_RADIUS, - FavouriteState = { BindTarget = FavouriteState }, - ButtonsCollapsedWidth = CORNER_RADIUS, - ButtonsExpandedWidth = 30, - Children = new Drawable[] - { + // buttonContainer = new CollapsibleButtonContainer(Note) + // { + // X = height - CORNER_RADIUS, + // Width = WIDTH - height + CORNER_RADIUS, + // // FavouriteState = { BindTarget = FavouriteState }, + // ButtonsCollapsedWidth = CORNER_RADIUS, + // ButtonsExpandedWidth = 30, + // Children = new Drawable[] + // { new FillFlowContainer { + // X = height - CORNER_RADIUS, + X = height, RelativeSizeAxes = Axes.Both, Direction = FillDirection.Vertical, Children = new Drawable[] @@ -108,12 +118,14 @@ namespace osu.Game.Screens.Misskey.Components { new Drawable[] { - new OsuSpriteText + new OsuSpriteText() { - Text = new RomanisableString(BeatmapSet.TitleUnicode, BeatmapSet.Title), + Text = new RomanisableString(Note.User.Name, Note.User.Username), Font = OsuFont.Default.With(size: 22.5f, weight: FontWeight.SemiBold), RelativeSizeAxes = Axes.X, Truncate = true + // Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, + // AutoSizeAxes = Axes.Y, }, titleBadgeArea = new FillFlowContainer { @@ -142,28 +154,28 @@ namespace osu.Game.Screens.Misskey.Components { new[] { - new OsuSpriteText + artistText = new LinkFlowContainer() { - Text = createArtistText(), - Font = OsuFont.Default.With(size: 17.5f, weight: FontWeight.SemiBold), + // Text = createArtistText(), + // Font = OsuFont.Default.With(size: 17.5f, weight: FontWeight.SemiBold), RelativeSizeAxes = Axes.X, - Truncate = true + // Truncate = true + AutoSizeAxes = Axes.Y, }, Empty() }, } }, - new LinkFlowContainer(s => + noteText = new TextFlowContainer { - s.Shadow = false; - s.Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold); - }).With(d => + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Alpha = 1, + AlwaysPresent = true, + }.With(flow => { - d.AutoSizeAxes = Axes.Both; - d.Margin = new MarginPadding { Top = 2 }; - d.AddText("mapped by ", t => t.Colour = colourProvider.Content2); - d.AddUserLink(BeatmapSet.Author); - }), + flow.AddText(Note.Text, t => t.Font = OsuFont.Default.With(size: 15)); + }) } }, new Container @@ -184,104 +196,97 @@ namespace osu.Game.Screens.Misskey.Components AlwaysPresent = true, Children = new Drawable[] { - statisticsContainer = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(10, 0), - Alpha = 0, - AlwaysPresent = true, - ChildrenEnumerable = createStatistics() - }, - new BeatmapCardExtraInfoRow(BeatmapSet) + // statisticsContainer = new FillFlowContainer + // { + // RelativeSizeAxes = Axes.X, + // AutoSizeAxes = Axes.Y, + // Direction = FillDirection.Horizontal, + // Spacing = new Vector2(10, 0), + // Alpha = 0, + // AlwaysPresent = true, + // ChildrenEnumerable = createStatistics() + // }, + } - }, - downloadProgressBar = new BeatmapCardDownloadProgressBar - { - RelativeSizeAxes = Axes.X, - Height = 6, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - State = { BindTarget = DownloadTracker.State }, - Progress = { BindTarget = DownloadTracker.Progress } } } } - } - } + // } + // } } }; - c.ExpandedContent = new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Horizontal = 10, Vertical = 13 }, - Child = new BeatmapCardDifficultyList(BeatmapSet) - }; + // c.ExpandedContent = new Container + // { + // RelativeSizeAxes = Axes.X, + // AutoSizeAxes = Axes.Y, + // Padding = new MarginPadding { Horizontal = 10, Vertical = 13 }, + // Child = new BeatmapCardDifficultyList(BeatmapSet) + // }; c.Expanded.BindTarget = Expanded; + // titleText.AddLink(Note.User.Name, $"{api.APIEndpointUrl}@{Note.User.Username}@{Note.User.Host}"); + artistText.AddLink($"@{Note.User.Username}@{Note.User.Host}", $"{api.APIEndpointUrl}/@{Note.User.Username}@{Note.User.Host}"); }); - if (BeatmapSet.HasVideo) - leftIconArea.Add(new VideoIconPill { IconSize = new Vector2(20) }); + // if (BeatmapSet.HasVideo) + // leftIconArea.Add(new VideoIconPill { IconSize = new Vector2(20) }); + // + // if (BeatmapSet.HasStoryboard) + // leftIconArea.Add(new StoryboardIconPill { IconSize = new Vector2(20) }); + // + // if (BeatmapSet.FeaturedInSpotlight) + // { + // titleBadgeArea.Add(new SpotlightBeatmapBadge + // { + // Anchor = Anchor.BottomRight, + // Origin = Anchor.BottomRight, + // Margin = new MarginPadding { Left = 5 } + // }); + // } - if (BeatmapSet.HasStoryboard) - leftIconArea.Add(new StoryboardIconPill { IconSize = new Vector2(20) }); + // if (BeatmapSet.HasExplicitContent) + // { + // titleBadgeArea.Add(new ExplicitContentBeatmapBadge + // { + // Anchor = Anchor.BottomRight, + // Origin = Anchor.BottomRight, + // Margin = new MarginPadding { Left = 5 } + // }); + // } - if (BeatmapSet.FeaturedInSpotlight) - { - titleBadgeArea.Add(new SpotlightBeatmapBadge - { - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - Margin = new MarginPadding { Left = 5 } - }); - } - - if (BeatmapSet.HasExplicitContent) - { - titleBadgeArea.Add(new ExplicitContentBeatmapBadge - { - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - Margin = new MarginPadding { Left = 5 } - }); - } - - if (BeatmapSet.TrackId != null) - { - artistContainer.Content[0][1] = new FeaturedArtistBeatmapBadge - { - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - Margin = new MarginPadding { Left = 5 } - }; - } + // if (BeatmapSet.TrackId != null) + // { + // artistContainer.Content[0][1] = new FeaturedArtistBeatmapBadge + // { + // Anchor = Anchor.BottomRight, + // Origin = Anchor.BottomRight, + // Margin = new MarginPadding { Left = 5 } + // }; + // } } private LocalisableString createArtistText() { - var romanisableArtist = new RomanisableString(BeatmapSet.ArtistUnicode, BeatmapSet.Artist); - return BeatmapsetsStrings.ShowDetailsByArtist(romanisableArtist); + var romanisableArtist = new RomanisableString(Note.User.Username + "@" + Note.User.Host, Note.User.Username + "@" + Note.User.Host); + return romanisableArtist; } - private IEnumerable createStatistics() - { - var hypesStatistic = HypesStatistic.CreateFor(BeatmapSet); - if (hypesStatistic != null) - yield return hypesStatistic; - - var nominationsStatistic = NominationsStatistic.CreateFor(BeatmapSet); - if (nominationsStatistic != null) - yield return nominationsStatistic; - - yield return new FavouritesStatistic(BeatmapSet) { Current = FavouriteState }; - yield return new PlayCountStatistic(BeatmapSet); - - var dateStatistic = BeatmapCardDateStatistic.CreateFor(BeatmapSet); - if (dateStatistic != null) - yield return dateStatistic; - } + // private IEnumerable createStatistics() + // { + // var hypesStatistic = HypesStatistic.CreateFor(BeatmapSet); + // if (hypesStatistic != null) + // yield return hypesStatistic; + // + // var nominationsStatistic = NominationsStatistic.CreateFor(BeatmapSet); + // if (nominationsStatistic != null) + // yield return nominationsStatistic; + // + // yield return new FavouritesStatistic(BeatmapSet) { Current = FavouriteState }; + // yield return new PlayCountStatistic(BeatmapSet); + // + // var dateStatistic = BeatmapCardDateStatistic.CreateFor(BeatmapSet); + // if (dateStatistic != null) + // yield return dateStatistic; + // } protected override void UpdateState() { @@ -289,10 +294,10 @@ namespace osu.Game.Screens.Misskey.Components bool showDetails = IsHovered || Expanded.Value; - buttonContainer.ShowDetails.Value = showDetails; + // buttonContainer.ShowDetails.Value = showDetails; thumbnail.Dimmed.Value = showDetails; - statisticsContainer.FadeTo(showDetails ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint); + // statisticsContainer.FadeTo(showDetails ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint); } } } diff --git a/osu.Game/Screens/Misskey/Components/Note/Cards/Statistics/BeatmapCardDateStatistic.cs b/osu.Game/Screens/Misskey/Components/Note/Cards/Statistics/BeatmapCardDateStatistic.cs new file mode 100644 index 0000000000..d706c3b6f1 --- /dev/null +++ b/osu.Game/Screens/Misskey/Components/Note/Cards/Statistics/BeatmapCardDateStatistic.cs @@ -0,0 +1,54 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Extensions.LocalisationExtensions; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Sprites; +using osu.Game.Beatmaps; +using osu.Game.Graphics; + +namespace osu.Game.Screens.Misskey.Components.Note.Cards.Statistics +{ + public partial class BeatmapCardDateStatistic : BeatmapCardStatistic + { + private readonly DateTimeOffset dateTime; + + private BeatmapCardDateStatistic(DateTimeOffset dateTime) + { + this.dateTime = dateTime; + + Icon = FontAwesome.Regular.CheckCircle; + Text = dateTime.ToLocalisableString(@"d MMM yyyy"); + } + + public override object TooltipContent => dateTime; + public override ITooltip GetCustomTooltip() => new DateTooltip(); + + public static BeatmapCardDateStatistic? CreateFor(IBeatmapSetOnlineInfo beatmapSetInfo) + { + var displayDate = displayDateFor(beatmapSetInfo); + + if (displayDate == null) + return null; + + return new BeatmapCardDateStatistic(displayDate.Value); + } + + private static DateTimeOffset? displayDateFor(IBeatmapSetOnlineInfo beatmapSetInfo) + { + // reference: https://github.com/ppy/osu-web/blob/ef432c11719fd1207bec5f9194b04f0033bdf02c/resources/assets/lib/beatmapset-panel.tsx#L36-L44 + switch (beatmapSetInfo.Status) + { + case BeatmapOnlineStatus.Ranked: + case BeatmapOnlineStatus.Approved: + case BeatmapOnlineStatus.Loved: + case BeatmapOnlineStatus.Qualified: + return beatmapSetInfo.Ranked; + + default: + return beatmapSetInfo.LastUpdated; + } + } + } +} diff --git a/osu.Game/Screens/Misskey/Components/Note/Cards/Statistics/BeatmapCardStatistic.cs b/osu.Game/Screens/Misskey/Components/Note/Cards/Statistics/BeatmapCardStatistic.cs new file mode 100644 index 0000000000..6b968c8435 --- /dev/null +++ b/osu.Game/Screens/Misskey/Components/Note/Cards/Statistics/BeatmapCardStatistic.cs @@ -0,0 +1,82 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Overlays; +using osuTK; + +namespace osu.Game.Screens.Misskey.Components.Note.Cards.Statistics +{ + /// + /// A single statistic shown on a beatmap card. + /// + public abstract partial class BeatmapCardStatistic : CompositeDrawable, IHasTooltip, IHasCustomTooltip + { + protected IconUsage Icon + { + get => spriteIcon.Icon; + set => spriteIcon.Icon = value; + } + + protected LocalisableString Text + { + get => spriteText.Text; + set => spriteText.Text = value; + } + + public LocalisableString TooltipText { get; protected set; } + + private readonly SpriteIcon spriteIcon; + private readonly OsuSpriteText spriteText; + + protected BeatmapCardStatistic() + { + AutoSizeAxes = Axes.Both; + + InternalChild = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5, 0), + Children = new Drawable[] + { + spriteIcon = new SpriteIcon + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Size = new Vector2(10), + Margin = new MarginPadding { Top = 1 } + }, + spriteText = new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Font = OsuFont.Default.With(size: 14) + } + } + }; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + spriteIcon.Colour = colourProvider.Content2; + } + + #region Tooltip implementation + + public virtual ITooltip GetCustomTooltip() => null; + public virtual object TooltipContent => null; + + #endregion + } +} diff --git a/osu.Game/Screens/Misskey/Components/Note/Cards/Statistics/FavouritesStatistic.cs b/osu.Game/Screens/Misskey/Components/Note/Cards/Statistics/FavouritesStatistic.cs new file mode 100644 index 0000000000..a9826d63ce --- /dev/null +++ b/osu.Game/Screens/Misskey/Components/Note/Cards/Statistics/FavouritesStatistic.cs @@ -0,0 +1,47 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using Humanizer; +using osu.Framework.Bindables; +using osu.Framework.Extensions.LocalisationExtensions; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Beatmaps; +using osu.Game.Resources.Localisation.Web; + +namespace osu.Game.Screens.Misskey.Components.Note.Cards.Statistics +{ + /// + /// Shows the number of favourites that a beatmap set has received. + /// + public partial class FavouritesStatistic : BeatmapCardStatistic, IHasCurrentValue + { + private readonly BindableWithCurrent current; + + public Bindable Current + { + get => current.Current; + set => current.Current = value; + } + + public FavouritesStatistic(IBeatmapSetOnlineInfo onlineInfo) + { + current = new BindableWithCurrent(new BeatmapSetFavouriteState(onlineInfo.HasFavourited, onlineInfo.FavouriteCount)); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + current.BindValueChanged(_ => updateState(), true); + } + + private void updateState() + { + Icon = current.Value.Favourited ? FontAwesome.Solid.Heart : FontAwesome.Regular.Heart; + Text = current.Value.FavouriteCount.ToMetric(decimals: 1); + TooltipText = BeatmapsStrings.PanelFavourites(current.Value.FavouriteCount.ToLocalisableString(@"N0")); + } + } +} diff --git a/osu.Game/Screens/Misskey/Components/Note/Cards/Statistics/HypesStatistic.cs b/osu.Game/Screens/Misskey/Components/Note/Cards/Statistics/HypesStatistic.cs new file mode 100644 index 0000000000..a9a0f53ef2 --- /dev/null +++ b/osu.Game/Screens/Misskey/Components/Note/Cards/Statistics/HypesStatistic.cs @@ -0,0 +1,26 @@ +// 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.Extensions.LocalisationExtensions; +using osu.Framework.Graphics.Sprites; +using osu.Game.Beatmaps; +using osu.Game.Resources.Localisation.Web; + +namespace osu.Game.Screens.Misskey.Components.Note.Cards.Statistics +{ + /// + /// Shows the number of current hypes that a map has received, as well as the number of hypes required for nomination. + /// + public partial class HypesStatistic : BeatmapCardStatistic + { + private HypesStatistic(BeatmapSetHypeStatus hypeStatus) + { + Icon = FontAwesome.Solid.Bullhorn; + Text = hypeStatus.Current.ToLocalisableString(); + TooltipText = BeatmapsStrings.HypeRequiredText(hypeStatus.Current.ToLocalisableString(), hypeStatus.Required.ToLocalisableString()); + } + + public static HypesStatistic? CreateFor(IBeatmapSetOnlineInfo beatmapSetOnlineInfo) + => beatmapSetOnlineInfo.HypeStatus == null ? null : new HypesStatistic(beatmapSetOnlineInfo.HypeStatus); + } +} diff --git a/osu.Game/Screens/Misskey/Components/Note/Cards/Statistics/NominationsStatistic.cs b/osu.Game/Screens/Misskey/Components/Note/Cards/Statistics/NominationsStatistic.cs new file mode 100644 index 0000000000..2255a5ab24 --- /dev/null +++ b/osu.Game/Screens/Misskey/Components/Note/Cards/Statistics/NominationsStatistic.cs @@ -0,0 +1,28 @@ +// 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.Extensions.LocalisationExtensions; +using osu.Framework.Graphics.Sprites; +using osu.Game.Beatmaps; +using osu.Game.Resources.Localisation.Web; + +namespace osu.Game.Screens.Misskey.Components.Note.Cards.Statistics +{ + /// + /// Shows the number of current nominations that a map has received, as well as the number of nominations required for qualification. + /// + public partial class NominationsStatistic : BeatmapCardStatistic + { + private NominationsStatistic(BeatmapSetNominationStatus nominationStatus) + { + Icon = FontAwesome.Solid.ThumbsUp; + Text = nominationStatus.Current.ToLocalisableString(); + TooltipText = BeatmapsStrings.NominationsRequiredText(nominationStatus.Current.ToLocalisableString(), nominationStatus.Required.ToLocalisableString()); + } + + public static NominationsStatistic? CreateFor(IBeatmapSetOnlineInfo beatmapSetOnlineInfo) + // web does not show nominations unless hypes are also present. + // see: https://github.com/ppy/osu-web/blob/8ed7d071fd1d3eaa7e43cf0e4ff55ca2fef9c07c/resources/assets/lib/beatmapset-panel.tsx#L443 + => beatmapSetOnlineInfo.HypeStatus == null || beatmapSetOnlineInfo.NominationStatus == null ? null : new NominationsStatistic(beatmapSetOnlineInfo.NominationStatus); + } +} diff --git a/osu.Game/Screens/Misskey/Components/Note/Cards/Statistics/PlayCountStatistic.cs b/osu.Game/Screens/Misskey/Components/Note/Cards/Statistics/PlayCountStatistic.cs new file mode 100644 index 0000000000..78addb53bc --- /dev/null +++ b/osu.Game/Screens/Misskey/Components/Note/Cards/Statistics/PlayCountStatistic.cs @@ -0,0 +1,26 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using Humanizer; +using osu.Framework.Extensions.LocalisationExtensions; +using osu.Framework.Graphics.Sprites; +using osu.Game.Beatmaps; +using osu.Game.Resources.Localisation.Web; + +namespace osu.Game.Screens.Misskey.Components.Note.Cards.Statistics +{ + /// + /// Shows the number of times the given beatmap set has been played. + /// + public partial class PlayCountStatistic : BeatmapCardStatistic + { + public PlayCountStatistic(IBeatmapSetOnlineInfo onlineInfo) + { + Icon = FontAwesome.Regular.PlayCircle; + Text = onlineInfo.PlayCount.ToMetric(decimals: 1); + TooltipText = BeatmapsStrings.PanelPlaycount(onlineInfo.PlayCount.ToLocalisableString(@"N0")); + } + } +} diff --git a/osu.Game/Screens/Misskey/Components/Note/Cards/StoryboardIconPill.cs b/osu.Game/Screens/Misskey/Components/Note/Cards/StoryboardIconPill.cs new file mode 100644 index 0000000000..97c94c1a45 --- /dev/null +++ b/osu.Game/Screens/Misskey/Components/Note/Cards/StoryboardIconPill.cs @@ -0,0 +1,21 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; + +namespace osu.Game.Screens.Misskey.Components.Note.Cards +{ + public partial class StoryboardIconPill : IconPill + { + public StoryboardIconPill() + : base(FontAwesome.Solid.Image) + { + } + + public override LocalisableString TooltipText => BeatmapsetsStrings.ShowInfoStoryboard; + } +} diff --git a/osu.Game/Screens/Misskey/Components/Note/Cards/VideoIconPill.cs b/osu.Game/Screens/Misskey/Components/Note/Cards/VideoIconPill.cs new file mode 100644 index 0000000000..2fb964c24c --- /dev/null +++ b/osu.Game/Screens/Misskey/Components/Note/Cards/VideoIconPill.cs @@ -0,0 +1,21 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; + +namespace osu.Game.Screens.Misskey.Components.Note.Cards +{ + public partial class VideoIconPill : IconPill + { + public VideoIconPill() + : base(FontAwesome.Solid.Film) + { + } + + public override LocalisableString TooltipText => BeatmapsetsStrings.ShowInfoVideo; + } +} diff --git a/osu.Game/Screens/Misskey/Components/PostForm.cs b/osu.Game/Screens/Misskey/Components/PostForm.cs index 1996f1b0bb..8bdb64aa0b 100644 --- a/osu.Game/Screens/Misskey/Components/PostForm.cs +++ b/osu.Game/Screens/Misskey/Components/PostForm.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Game.Configuration; @@ -16,6 +17,7 @@ using osu.Game.Online.MisskeyAPI; using osu.Game.Misskey.Overlays.Settings; using osu.Game.Online.MisskeyAPI.Requests.Notes; using osu.Game.Overlays; +using osu.Game.Overlays.Notifications; using osu.Game.Overlays.OSD; using osu.Game.Resources.Localisation.Web; using osu.Game.Screens.Misskey; @@ -26,6 +28,8 @@ namespace osu.Game.Screens.Misskey.Components public partial class PostForm : FillFlowContainer { private OnScreenDisplay? onScreenDisplay { get; set; } + [Resolved(CanBeNull = true)] + private INotificationOverlay? notifications { get; set; } private partial class ResToast : Toast { public ResToast(string value, string desc) @@ -57,11 +61,20 @@ namespace osu.Game.Screens.Misskey.Components { textBox.Text = string.Empty; cwBox.Text = string.Empty; - onScreenDisplay?.Display(new ResToast("投稿しました", createReq.CompletionState.ToString())); + notifications?.Post(new SimpleNotification + { + Text = "投稿しました", + Icon = FontAwesome.Solid.Check, + }); }; api.Queue(createReq); - onScreenDisplay?.Display(new ResToast("送信しています", "")); + notifications?.Post(new SimpleNotification + { + Text = "送信しています", + Icon = FontAwesome.Solid.PaperPlane, + }); + } [BackgroundDependencyLoader(permitNulls: true)] @@ -128,15 +141,14 @@ namespace osu.Game.Screens.Misskey.Components } } }, - // new SettingsButton - // { - // Text = "Register", - // Action = () => - // { - // RequestHide?.Invoke(); - // accountCreation.Show(); - // } - // } + new SettingsButton + { + Text = "test", + Action = () => + { + onScreenDisplay?.Display(new ResToast("送信しています", "")); + } + } }; // forgottenPaswordLink.AddLink(LayoutStrings.PopupLoginLoginForgot, $"https://simkey.net/about"); diff --git a/osu.Game/Screens/Misskey/MisskeyComponents.cs b/osu.Game/Screens/Misskey/MisskeyComponents.cs index 929609b436..bd5fa0c1da 100644 --- a/osu.Game/Screens/Misskey/MisskeyComponents.cs +++ b/osu.Game/Screens/Misskey/MisskeyComponents.cs @@ -20,7 +20,7 @@ namespace osu.Game.Screens.Misskey public partial class MisskeyComponents : OsuScreen { private Container contentContainer; - private Drawable avatar; + // private Drawable avatar; [Resolved] private OsuColour colours { get; set; } @@ -44,23 +44,23 @@ namespace osu.Game.Screens.Misskey Colour = Color4.Black, Alpha = 0.6f, }, - new Container - { - // RelativeSizeAxes = Axes.Both, - // AutoSizeAxes = Axes.Y, - Size = new Vector2(200), - Masking = true, - CornerRadius = 100, - AutoSizeEasing = Easing.OutQuint, - Children = new Drawable[] - { - avatar = new DelayedLoadWrapper( - new Avatar() - { - RelativeSizeAxes = Axes.Both, - }) - }, - } + // new Container + // { + // // RelativeSizeAxes = Axes.Both, + // // AutoSizeAxes = Axes.Y, + // Size = new Vector2(200), + // Masking = true, + // CornerRadius = 100, + // AutoSizeEasing = Easing.OutQuint, + // Children = new Drawable[] + // { + // avatar = new DelayedLoadWrapper( + // new Avatar() + // { + // RelativeSizeAxes = Axes.Both, + // }) + // }, + // } } }; } diff --git a/osu.Game/Screens/Misskey/MisskeyScreenSelector.cs b/osu.Game/Screens/Misskey/MisskeyScreenSelector.cs index 1bedb3509e..c1a9d5763c 100644 --- a/osu.Game/Screens/Misskey/MisskeyScreenSelector.cs +++ b/osu.Game/Screens/Misskey/MisskeyScreenSelector.cs @@ -98,14 +98,14 @@ namespace osu.Game.Screens.Misskey Text = "MisskeyLogin", Action = () => this.Push(new MisskeyLogin()) }, - // new OsuButton() - // { - // Anchor = Anchor.Centre, - // Origin = Anchor.Centre, - // Size = new Vector2(500f, 50f), - // Text = "Timeline", - // Action = () => this.Push(new Timeline()) - // }, + new OsuButton() + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(500f, 50f), + Text = "Timeline", + Action = () => this.Push(new Timeline()) + }, new OsuButton() { Anchor = Anchor.Centre, diff --git a/osu.Game/Screens/Misskey/Timeline.cs b/osu.Game/Screens/Misskey/Timeline.cs new file mode 100644 index 0000000000..a9310b7bc4 --- /dev/null +++ b/osu.Game/Screens/Misskey/Timeline.cs @@ -0,0 +1,303 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Framework.Input.Events; +using osu.Framework.Localisation; +using osu.Game.Audio; +using osu.Game.Beatmaps.Drawables.Cards; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.MisskeyAPI.Requests.Notes; +using osu.Game.Online.MisskeyAPI.Responses.Types; +using osu.Game.Overlays; +using osu.Game.Overlays.BeatmapListing; +using osu.Game.Resources.Localisation.Web; +using osu.Game.Screens.Misskey.Components; +using osuTK; +using osuTK.Graphics; +using ExpandedContentScrollContainer = osu.Game.Screens.Misskey.Components.Note.Cards.ExpandedContentScrollContainer; +using IAPIProvider = osu.Game.Online.MisskeyAPI.IAPIProvider; +using NoteCard = osu.Game.Screens.Misskey.Components.Note.Cards.NoteCard; + +namespace osu.Game.Screens.Misskey +{ + public partial class Timeline : OsuScreen + { + // [Cached] + // protected readonly OsuScrollContainer ScrollFlow; + + protected readonly LoadingLayer Loading; + + [Resolved] + private PreviewTrackManager previewTrackManager { get; set; } + + [Resolved] + private IAPIProvider api { get; set; } + + private OsuScrollContainer panelTarget; + private FillFlowContainer foundContent; + + private int page = 0; + + [BackgroundDependencyLoader] + private void load() + { + InternalChild = new FillFlowContainer + { + Margin = new MarginPadding(10f), + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new Container + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.Gray(0.1f) + }, + panelTarget = new OsuScrollContainer() + { + Height = 750f, + Width = 600f, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + // RelativeSizeAxes = Axes.X, + Masking = true, + Padding = new MarginPadding { Horizontal = 20 }, + } + }, + }, + } + }; + } + + private string lastNoteID; + + protected override void LoadComplete() + { + base.LoadComplete(); + var req = new HybridTimeline(api.AccessToken); + req.Success += res => + { + onSearchFinished(res); + lastNoteID = res.Last().Id; + }; + onSearchStarted(); + api.Queue(req); + } + + private CancellationTokenSource cancellationToken; + + private Task panelLoadTask; + + private void onSearchStarted() + { + cancellationToken?.Cancel(); + + // if (panelTarget.Any()) + // Loading.Show(); + } + + private void onSearchFinished(Note[] searchResult) + { + cancellationToken?.Cancel(); + + var newCards = createCardsFor(searchResult); + + if (page == 0) + { + //No matches case + if (!newCards.Any()) + { + replaceResultsAreaContent(new NotFoundDrawable()); + return; + } + + var content = createCardContainerFor(newCards); + + panelLoadTask = LoadComponentAsync(foundContent = content, replaceResultsAreaContent, (cancellationToken = new CancellationTokenSource()).Token); + } + else + { + // new results may contain beatmaps from a previous page, + // this is dodgy but matches web behaviour for now. + // see: https://github.com/ppy/osu-web/issues/9270 + // todo: replace custom equality compraer with ExceptBy in net6.0 + // newCards = newCards.ExceptBy(foundContent.Select(c => c.BeatmapSet.OnlineID), c => c.BeatmapSet.OnlineID); + // newCards = newCards.Except(foundContent, BeatmapCardEqualityComparer.Default); + + panelLoadTask = LoadComponentsAsync(newCards, loaded => + { + lastFetchDisplayedTime = Time.Current; + foundContent.AddRange(loaded); + loaded.ForEach(p => p.FadeIn(200, Easing.OutQuint)); + }, (cancellationToken = new CancellationTokenSource()).Token); + } + } + + private IEnumerable createCardsFor(Note[] notes) => + notes.Select(set => + NoteCard.Create(set, BeatmapCardSize.Normal).With(c => + { + c.Anchor = Anchor.TopCentre; + c.Origin = Anchor.TopCentre; + })).ToArray(); + + private static ReverseChildIDFillFlowContainer createCardContainerFor(IEnumerable newCards) + { + // spawn new children with the contained so we only clear old content at the last moment. + // reverse ID flow is required for correct Z-ordering of the cards' expandable content (last card should be front-most). + var content = new ReverseChildIDFillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(10), + Alpha = 0, + Margin = new MarginPadding + { + Top = 15, + // the + 20 adjustment is roughly eyeballed in order to fit all of the expanded content height after it's scaled + // as well as provide visual balance to the top margin. + Bottom = ExpandedContentScrollContainer.HEIGHT + 20 + }, + ChildrenEnumerable = newCards + }; + return content; + } + + private void replaceResultsAreaContent(Drawable content) + { + // Loading.Hide(); + lastFetchDisplayedTime = Time.Current; + + panelTarget.Child = content; + + content.FadeInFromZero(); + } + + protected override void Dispose(bool isDisposing) + { + cancellationToken?.Cancel(); + base.Dispose(isDisposing); + } + + public partial class NotFoundDrawable : CompositeDrawable + { + public NotFoundDrawable() + { + RelativeSizeAxes = Axes.X; + Height = 250; + Alpha = 0; + Margin = new MarginPadding { Top = 15 }; + } + + [BackgroundDependencyLoader] + private void load(LargeTextureStore textures) + { + AddInternal(new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(10, 0), + Children = new Drawable[] + { + new Sprite + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit, + Texture = textures.Get(@"Online/not-found") + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = BeatmapsStrings.ListingSearchNotFoundQuote, + } + } + }); + } + } + + // TODO: localisation requires Text/LinkFlowContainer support for localising strings with links inside + // (https://github.com/ppy/osu-framework/issues/4530) + + private const double time_between_fetches = 500; + + private double lastFetchDisplayedTime; + + protected override void Update() + { + base.Update(); + + const int pagination_scroll_distance = 200; + + bool shouldShowMore = panelLoadTask?.IsCompleted != false + && Time.Current - lastFetchDisplayedTime > time_between_fetches + && (panelTarget.ScrollableExtent > 0 && panelTarget.IsScrolledToEnd(pagination_scroll_distance)); + + if (shouldShowMore) + FetchNextPage(); + } + + private bool locked; + private void FetchNextPage() + { + if (locked) + return; + var req = new HybridTimeline(api.AccessToken, lastNoteID); + req.Success += res => + { + lastNoteID = res.Last().Id; + page++; + onSearchFinished(res); + locked = false; + }; + api.Queue(req); + locked = true; + } + + private class BeatmapCardEqualityComparer : IEqualityComparer + { + public static BeatmapCardEqualityComparer Default { get; } = new BeatmapCardEqualityComparer(); + + public bool Equals(BeatmapCard x, BeatmapCard y) + { + if (ReferenceEquals(x, y)) return true; + if (ReferenceEquals(x, null)) return false; + if (ReferenceEquals(y, null)) return false; + + return x.BeatmapSet.Equals(y.BeatmapSet); + } + + public int GetHashCode(BeatmapCard obj) => obj.BeatmapSet.GetHashCode(); + } + } +} diff --git a/osu.Game/Screens/Misskey/Welcome.cs b/osu.Game/Screens/Misskey/Welcome.cs index 235bf48033..1ef9426717 100644 --- a/osu.Game/Screens/Misskey/Welcome.cs +++ b/osu.Game/Screens/Misskey/Welcome.cs @@ -147,7 +147,7 @@ namespace osu.Game.Screens.Misskey Origin = Anchor.TopCentre, Margin = new MarginPadding(100), Width = 400f, - Text = "Misskey.io は、地球で生まれた分散マイクロブログSNSです。Fediverse(様々なSNSで構成される宇宙)の中に存在するため、他のSNSと相互に繋がっています。\n暫し都会の喧騒から離れて、新しいインターネットにダイブしてみませんか。", + Text = "Misskey.。Fediverse(様々なSNSで構成される宇宙)の中に存在するため、他のSNSと相互に繋がっています。\n暫し都会の喧騒から離れて、新しいインターネットにダイブしてみませんか。", Font = new FontUsage(size: 20), Colour = Colour4.White, }, diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index b1c85f1e0b..536f206fc1 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -45,4 +45,7 @@ + + +