Skip to content

Commit

Permalink
Merge pull request #30467 from cdwcgt/friend-add
Browse files Browse the repository at this point in the history
Add the ability to add/remove friends in `UserProfileHeader`
  • Loading branch information
bdach authored Nov 5, 2024
2 parents f21d2de + c576fd8 commit 2bd12e1
Show file tree
Hide file tree
Showing 16 changed files with 488 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,13 @@ public void TestFriendScore()
var api = (DummyAPIAccess)API;

api.Friends.Clear();
api.Friends.Add(friend);
api.Friends.Add(new APIRelation
{
Mutual = true,
RelationType = RelationType.Friend,
TargetID = friend.OnlineID,
TargetUser = friend
});
});

int playerNumber = 1;
Expand Down
20 changes: 13 additions & 7 deletions osu.Game.Tests/Visual/Online/TestSceneDashboardOverlay.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,20 @@ private void load()
if (supportLevel > 3)
supportLevel = 0;

((DummyAPIAccess)API).Friends.Add(new APIUser
((DummyAPIAccess)API).Friends.Add(new APIRelation
{
Username = @"peppy",
Id = 2,
Colour = "99EB47",
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
IsSupporter = supportLevel > 0,
SupportLevel = supportLevel
TargetID = 2,
RelationType = RelationType.Friend,
Mutual = true,
TargetUser = new APIUser
{
Username = @"peppy",
Id = 2,
Colour = "99EB47",
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
IsSupporter = supportLevel > 0,
SupportLevel = supportLevel
}
});
}
}
Expand Down
101 changes: 101 additions & 0 deletions osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,20 @@

using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Configuration;
using osu.Game.Graphics.Containers;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays;
using osu.Game.Overlays.Profile;
using osu.Game.Overlays.Profile.Header.Components;
using osu.Game.Rulesets.Osu;
using osu.Game.Users;

Expand All @@ -22,6 +27,10 @@ public partial class TestSceneUserProfileHeader : OsuTestScene
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green);

private DummyAPIAccess dummyAPI => (DummyAPIAccess)API;

private readonly ManualResetEventSlim requestLock = new ManualResetEventSlim();

[Resolved]
private OsuConfigManager configManager { get; set; } = null!;

Expand Down Expand Up @@ -400,5 +409,97 @@ public void TestManyTournamentBanners()
}
}, new OsuRuleset().RulesetInfo));
}

private APIUser nonFriend => new APIUser
{
Id = 727,
Username = "Whatever",
};

[Test]
public void TestAddFriend()
{
AddStep("Setup request", () =>
{
requestLock.Reset();

dummyAPI.HandleRequest = request =>
{
if (request is not AddFriendRequest req)
return false;

if (req.TargetId != nonFriend.OnlineID)
return false;

var apiRelation = new APIRelation
{
TargetID = nonFriend.OnlineID,
Mutual = true,
RelationType = RelationType.Friend,
TargetUser = nonFriend
};

Task.Run(() =>
{
requestLock.Wait(3000);
dummyAPI.Friends.Add(apiRelation);
req.TriggerSuccess(new AddFriendResponse
{
UserRelation = apiRelation
});
});

return true;
};
});
AddStep("clear friend list", () => dummyAPI.Friends.Clear());
AddStep("Show non-friend user", () => header.User.Value = new UserProfileData(nonFriend, new OsuRuleset().RulesetInfo));
AddStep("Click followers button", () => this.ChildrenOfType<FollowersButton>().First().TriggerClick());
AddStep("Complete request", () => requestLock.Set());
AddUntilStep("Friend added", () => API.Friends.Any(f => f.TargetID == nonFriend.OnlineID));
}

[Test]
public void TestAddFriendNonMutual()
{
AddStep("Setup request", () =>
{
requestLock.Reset();

dummyAPI.HandleRequest = request =>
{
if (request is not AddFriendRequest req)
return false;

if (req.TargetId != nonFriend.OnlineID)
return false;

var apiRelation = new APIRelation
{
TargetID = nonFriend.OnlineID,
Mutual = false,
RelationType = RelationType.Friend,
TargetUser = nonFriend
};

Task.Run(() =>
{
requestLock.Wait(3000);
dummyAPI.Friends.Add(apiRelation);
req.TriggerSuccess(new AddFriendResponse
{
UserRelation = apiRelation
});
});

return true;
};
});
AddStep("clear friend list", () => dummyAPI.Friends.Clear());
AddStep("Show non-friend user", () => header.User.Value = new UserProfileData(nonFriend, new OsuRuleset().RulesetInfo));
AddStep("Click followers button", () => this.ChildrenOfType<FollowersButton>().First().TriggerClick());
AddStep("Complete request", () => requestLock.Set());
AddUntilStep("Friend added", () => API.Friends.Any(f => f.TargetID == nonFriend.OnlineID));
}
}
}
34 changes: 19 additions & 15 deletions osu.Game/Online/API/APIAccess.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public partial class APIAccess : Component, IAPIProvider
private string password;

public IBindable<APIUser> LocalUser => localUser;
public IBindableList<APIUser> Friends => friends;
public IBindableList<APIRelation> Friends => friends;
public IBindable<UserActivity> Activity => activity;
public IBindable<UserStatistics> Statistics => statistics;

Expand All @@ -67,7 +67,7 @@ public partial class APIAccess : Component, IAPIProvider

private Bindable<APIUser> localUser { get; } = new Bindable<APIUser>(createGuestUser());

private BindableList<APIUser> friends { get; } = new BindableList<APIUser>();
private BindableList<APIRelation> friends { get; } = new BindableList<APIRelation>();

private Bindable<UserActivity> activity { get; } = new Bindable<UserActivity>();

Expand Down Expand Up @@ -360,19 +360,7 @@ private void attemptConnect()
}
}

var friendsReq = new GetFriendsRequest();
friendsReq.Failure += _ => state.Value = APIState.Failing;
friendsReq.Success += res =>
{
friends.Clear();
friends.AddRange(res);
};

if (!handleRequest(friendsReq))
{
state.Value = APIState.Failing;
return;
}
UpdateLocalFriends();

// The Success callback event is fired on the main thread, so we should wait for that to run before proceeding.
// Without this, we will end up circulating this Connecting loop multiple times and queueing up many web requests
Expand Down Expand Up @@ -624,6 +612,22 @@ public void UpdateStatistics(UserStatistics newStatistics)
localUser.Value.Statistics = newStatistics;
}

public void UpdateLocalFriends()
{
if (!IsLoggedIn)
return;

var friendsReq = new GetFriendsRequest();
friendsReq.Failure += _ => state.Value = APIState.Failing;
friendsReq.Success += res =>
{
friends.Clear();
friends.AddRange(res);
};

Queue(friendsReq);
}

private static APIUser createGuestUser() => new GuestUser();

private void setLocalUser(APIUser user) => Scheduler.Add(() =>
Expand Down
8 changes: 6 additions & 2 deletions osu.Game/Online/API/DummyAPIAccess.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public partial class DummyAPIAccess : Component, IAPIProvider
Id = DUMMY_USER_ID,
});

public BindableList<APIUser> Friends { get; } = new BindableList<APIUser>();
public BindableList<APIRelation> Friends { get; } = new BindableList<APIRelation>();

public Bindable<UserActivity> Activity { get; } = new Bindable<UserActivity>();

Expand Down Expand Up @@ -201,6 +201,10 @@ public void UpdateStatistics(UserStatistics newStatistics)
LocalUser.Value.Statistics = newStatistics;
}

public void UpdateLocalFriends()
{
}

public IHubClientConnector? GetHubConnector(string clientName, string endpoint, bool preferMessagePack) => null;

public IChatClient GetChatClient() => new TestChatClientConnector(this);
Expand All @@ -214,7 +218,7 @@ public void UpdateStatistics(UserStatistics newStatistics)
public void SetState(APIState newState) => state.Value = newState;

IBindable<APIUser> IAPIProvider.LocalUser => LocalUser;
IBindableList<APIUser> IAPIProvider.Friends => Friends;
IBindableList<APIRelation> IAPIProvider.Friends => Friends;
IBindable<UserActivity> IAPIProvider.Activity => Activity;
IBindable<UserStatistics?> IAPIProvider.Statistics => Statistics;

Expand Down
7 changes: 6 additions & 1 deletion osu.Game/Online/API/IAPIProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public interface IAPIProvider
/// <summary>
/// The user's friends.
/// </summary>
IBindableList<APIUser> Friends { get; }
IBindableList<APIRelation> Friends { get; }

/// <summary>
/// The current user's activity.
Expand Down Expand Up @@ -134,6 +134,11 @@ public interface IAPIProvider
/// </summary>
void UpdateStatistics(UserStatistics newStatistics);

/// <summary>
/// Update the friends status of the current user.
/// </summary>
void UpdateLocalFriends();

/// <summary>
/// Schedule a callback to run on the update thread.
/// </summary>
Expand Down
30 changes: 30 additions & 0 deletions osu.Game/Online/API/Requests/AddFriendRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System.Net.Http;
using osu.Framework.IO.Network;

namespace osu.Game.Online.API.Requests
{
public class AddFriendRequest : APIRequest<AddFriendResponse>
{
public readonly int TargetId;

public AddFriendRequest(int targetId)
{
TargetId = targetId;
}

protected override WebRequest CreateWebRequest()
{
var req = base.CreateWebRequest();

req.Method = HttpMethod.Post;
req.AddParameter("target", TargetId.ToString(), RequestParameterType.Query);

return req;
}

protected override string Target => @"friends";
}
}
14 changes: 14 additions & 0 deletions osu.Game/Online/API/Requests/AddFriendResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using Newtonsoft.Json;
using osu.Game.Online.API.Requests.Responses;

namespace osu.Game.Online.API.Requests
{
public class AddFriendResponse
{
[JsonProperty("user_relation")]
public APIRelation UserRelation = null!;
}
}
27 changes: 27 additions & 0 deletions osu.Game/Online/API/Requests/DeleteFriendRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System.Net.Http;
using osu.Framework.IO.Network;

namespace osu.Game.Online.API.Requests
{
public class DeleteFriendRequest : APIRequest
{
public readonly int TargetId;

public DeleteFriendRequest(int targetId)
{
TargetId = targetId;
}

protected override WebRequest CreateWebRequest()
{
var req = base.CreateWebRequest();
req.Method = HttpMethod.Delete;
return req;
}

protected override string Target => $@"friends/{TargetId}";
}
}
2 changes: 1 addition & 1 deletion osu.Game/Online/API/Requests/GetFriendsRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace osu.Game.Online.API.Requests
{
public class GetFriendsRequest : APIRequest<List<APIUser>>
public class GetFriendsRequest : APIRequest<List<APIRelation>>
{
protected override string Target => @"friends";
}
Expand Down
30 changes: 30 additions & 0 deletions osu.Game/Online/API/Requests/Responses/APIRelation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using Newtonsoft.Json;
using Newtonsoft.Json.Converters;

namespace osu.Game.Online.API.Requests.Responses
{
public class APIRelation
{
[JsonProperty("target_id")]
public int TargetID { get; set; }

[JsonProperty("relation_type")]
public RelationType RelationType { get; set; }

[JsonProperty("mutual")]
public bool Mutual { get; set; }

[JsonProperty("target")]
public APIUser? TargetUser { get; set; }
}

[JsonConverter(typeof(StringEnumConverter))]
public enum RelationType
{
Friend,
Block,
}
}
Loading

0 comments on commit 2bd12e1

Please sign in to comment.