From 3b5d573db1365dd07b335a2f8be5da60e7b9f67a Mon Sep 17 00:00:00 2001
From: Joseph Madamba <madamba.joehu@outlook.com>
Date: Tue, 31 Jan 2023 15:02:59 -0800
Subject: [PATCH] Display tournament banner on user profile

---
 .../Online/TestSceneUserProfileOverlay.cs     |  6 ++
 .../Online/API/Requests/Responses/APIUser.cs  |  4 ++
 .../Profile/Header/BannerHeaderContainer.cs   | 63 +++++++++++++++++++
 .../Components/DrawableTournamentBanner.cs    | 46 ++++++++++++++
 osu.Game/Overlays/Profile/ProfileHeader.cs    |  4 ++
 osu.Game/Users/TournamentBanner.cs            | 23 +++++++
 6 files changed, 146 insertions(+)
 create mode 100644 osu.Game/Overlays/Profile/Header/BannerHeaderContainer.cs
 create mode 100644 osu.Game/Overlays/Profile/Header/Components/DrawableTournamentBanner.cs
 create mode 100644 osu.Game/Users/TournamentBanner.cs

diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs
index 91928c1fd2..a97c8aff66 100644
--- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs
+++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs
@@ -121,6 +121,12 @@ namespace osu.Game.Tests.Visual.Online
                     Data = Enumerable.Range(2345, 45).Concat(Enumerable.Range(2109, 40)).ToArray()
                 },
             },
+            TournamentBanner = new TournamentBanner
+            {
+                Id = 13926,
+                TournamentId = 35,
+                ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2022/profile/winner_US.jpg",
+            },
             Badges = new[]
             {
                 new Badge
diff --git a/osu.Game/Online/API/Requests/Responses/APIUser.cs b/osu.Game/Online/API/Requests/Responses/APIUser.cs
index 0e62b03f1a..e63395fe26 100644
--- a/osu.Game/Online/API/Requests/Responses/APIUser.cs
+++ b/osu.Game/Online/API/Requests/Responses/APIUser.cs
@@ -234,6 +234,10 @@ namespace osu.Game.Online.API.Requests.Responses
             set => Statistics.RankHistory = value;
         }
 
+        [JsonProperty(@"active_tournament_banner")]
+        [CanBeNull]
+        public TournamentBanner TournamentBanner;
+
         [JsonProperty("badges")]
         public Badge[] Badges;
 
diff --git a/osu.Game/Overlays/Profile/Header/BannerHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/BannerHeaderContainer.cs
new file mode 100644
index 0000000000..8e6648dc4b
--- /dev/null
+++ b/osu.Game/Overlays/Profile/Header/BannerHeaderContainer.cs
@@ -0,0 +1,63 @@
+// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Threading;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Game.Online.API.Requests.Responses;
+using osu.Game.Overlays.Profile.Header.Components;
+
+namespace osu.Game.Overlays.Profile.Header
+{
+    public partial class BannerHeaderContainer : CompositeDrawable
+    {
+        public readonly Bindable<UserProfileData?> User = new Bindable<UserProfileData?>();
+
+        [BackgroundDependencyLoader]
+        private void load()
+        {
+            Alpha = 0;
+            RelativeSizeAxes = Axes.Both;
+            FillMode = FillMode.Fit;
+            FillAspectRatio = 1000 / 60f;
+        }
+
+        protected override void LoadComplete()
+        {
+            base.LoadComplete();
+
+            User.BindValueChanged(u => updateDisplay(u.NewValue?.User), true);
+        }
+
+        private CancellationTokenSource? cancellationTokenSource;
+
+        private void updateDisplay(APIUser? user)
+        {
+            cancellationTokenSource?.Cancel();
+            cancellationTokenSource = new CancellationTokenSource();
+
+            ClearInternal();
+
+            var banner = user?.TournamentBanner;
+
+            if (banner != null)
+            {
+                Show();
+
+                LoadComponentAsync(new DrawableTournamentBanner(banner), AddInternal, cancellationTokenSource.Token);
+            }
+            else
+            {
+                Hide();
+            }
+        }
+
+        protected override void Dispose(bool isDisposing)
+        {
+            cancellationTokenSource?.Cancel();
+            base.Dispose(isDisposing);
+        }
+    }
+}
diff --git a/osu.Game/Overlays/Profile/Header/Components/DrawableTournamentBanner.cs b/osu.Game/Overlays/Profile/Header/Components/DrawableTournamentBanner.cs
new file mode 100644
index 0000000000..26d333ff95
--- /dev/null
+++ b/osu.Game/Overlays/Profile/Header/Components/DrawableTournamentBanner.cs
@@ -0,0 +1,46 @@
+// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Textures;
+using osu.Framework.Localisation;
+using osu.Game.Graphics.Containers;
+using osu.Game.Online.API;
+using osu.Game.Users;
+
+namespace osu.Game.Overlays.Profile.Header.Components
+{
+    [LongRunningLoad]
+    public partial class DrawableTournamentBanner : OsuClickableContainer
+    {
+        private readonly TournamentBanner banner;
+
+        public DrawableTournamentBanner(TournamentBanner banner)
+        {
+            this.banner = banner;
+            RelativeSizeAxes = Axes.Both;
+        }
+
+        [BackgroundDependencyLoader]
+        private void load(LargeTextureStore textures, OsuGame? game, IAPIProvider api)
+        {
+            Child = new Sprite
+            {
+                RelativeSizeAxes = Axes.Both,
+                Texture = textures.Get(banner.Image),
+            };
+
+            Action = () => game?.OpenUrlExternally($@"{api.WebsiteRootUrl}/community/tournaments/{banner.TournamentId}");
+        }
+
+        protected override void LoadComplete()
+        {
+            base.LoadComplete();
+            this.FadeInFromZero(200);
+        }
+
+        public override LocalisableString TooltipText => "view in browser";
+    }
+}
diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs
index 5d753dea7d..363eb5d58e 100644
--- a/osu.Game/Overlays/Profile/ProfileHeader.cs
+++ b/osu.Game/Overlays/Profile/ProfileHeader.cs
@@ -47,6 +47,10 @@ namespace osu.Game.Overlays.Profile
                     RelativeSizeAxes = Axes.X,
                     User = { BindTarget = User },
                 },
+                new BannerHeaderContainer
+                {
+                    User = { BindTarget = User },
+                },
                 new BadgeHeaderContainer
                 {
                     RelativeSizeAxes = Axes.X,
diff --git a/osu.Game/Users/TournamentBanner.cs b/osu.Game/Users/TournamentBanner.cs
new file mode 100644
index 0000000000..62e1913412
--- /dev/null
+++ b/osu.Game/Users/TournamentBanner.cs
@@ -0,0 +1,23 @@
+// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.IO;
+using Newtonsoft.Json;
+
+namespace osu.Game.Users
+{
+    public class TournamentBanner
+    {
+        [JsonProperty("id")]
+        public int Id;
+
+        [JsonProperty("tournament_id")]
+        public int TournamentId;
+
+        [JsonProperty("image")]
+        public string ImageLowRes = null!;
+
+        // TODO: remove when api returns @2x image link: https://github.com/ppy/osu-web/issues/9816
+        public string Image => $@"{Path.ChangeExtension(ImageLowRes, null)}@2x{Path.GetExtension(ImageLowRes)}";
+    }
+}