diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index 26d7402a5b..f1878d967d 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -26,7 +26,7 @@ namespace osu.Desktop [Resolved] private IBindable ruleset { get; set; } - private Bindable user; + private IBindable user; private readonly IBindable status = new Bindable(); private readonly IBindable activity = new Bindable(); diff --git a/osu.Game.Tests/Visual/Menus/TestSceneDisclaimer.cs b/osu.Game.Tests/Visual/Menus/TestSceneDisclaimer.cs index 49fab08ded..9cbdee3632 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneDisclaimer.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneDisclaimer.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Game.Online.API; using osu.Game.Screens.Menu; using osu.Game.Users; @@ -16,7 +17,7 @@ namespace osu.Game.Tests.Visual.Menus AddStep("toggle support", () => { - API.LocalUser.Value = new User + ((DummyAPIAccess)API).LocalUser.Value = new User { Username = API.LocalUser.Value.Username, Id = API.LocalUser.Value.Id + 1, diff --git a/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs index 6c8ec917ba..dcfe0432a8 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs @@ -14,7 +14,7 @@ namespace osu.Game.Tests.Visual.Online { private readonly Container userPanelArea; - private Bindable localUser; + private IBindable localUser; public TestSceneAccountCreationOverlay() { diff --git a/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs index 0cc6e9f358..9bece39ca0 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs @@ -20,7 +20,7 @@ namespace osu.Game.Tests.Visual.Online [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); - private TestFriendDisplay display; + private FriendDisplay display; [SetUp] public void Setup() => Schedule(() => @@ -28,7 +28,7 @@ namespace osu.Game.Tests.Visual.Online Child = new BasicScrollContainer { RelativeSizeAxes = Axes.Both, - Child = display = new TestFriendDisplay() + Child = display = new FriendDisplay() }; }); @@ -41,7 +41,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestOnline() { - AddStep("Fetch online", () => display?.Fetch()); + // No need to do anything, fetch is performed automatically. } private List getUsers() => new List @@ -76,10 +76,5 @@ namespace osu.Game.Tests.Visual.Online LastVisit = DateTimeOffset.Now } }; - - private class TestFriendDisplay : FriendDisplay - { - public void Fetch() => PerformFetch(); - } } } diff --git a/osu.Game.Tests/Visual/Online/TestSceneNowPlayingCommand.cs b/osu.Game.Tests/Visual/Online/TestSceneNowPlayingCommand.cs index 8e151a987a..0324da6cf5 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNowPlayingCommand.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNowPlayingCommand.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Online.API; using osu.Game.Online.Chat; using osu.Game.Rulesets; using osu.Game.Users; @@ -18,6 +19,8 @@ namespace osu.Game.Tests.Visual.Online [Cached(typeof(IChannelPostTarget))] private PostTarget postTarget { get; set; } + private DummyAPIAccess api => (DummyAPIAccess)API; + public TestSceneNowPlayingCommand() { Add(postTarget = new PostTarget()); @@ -26,7 +29,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestGenericActivity() { - AddStep("Set activity", () => API.Activity.Value = new UserActivity.InLobby(null)); + AddStep("Set activity", () => api.Activity.Value = new UserActivity.InLobby(null)); AddStep("Run command", () => Add(new NowPlayingCommand())); @@ -36,7 +39,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestEditActivity() { - AddStep("Set activity", () => API.Activity.Value = new UserActivity.Editing(new BeatmapInfo())); + AddStep("Set activity", () => api.Activity.Value = new UserActivity.Editing(new BeatmapInfo())); AddStep("Run command", () => Add(new NowPlayingCommand())); @@ -46,7 +49,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestPlayActivity() { - AddStep("Set activity", () => API.Activity.Value = new UserActivity.SoloGame(new BeatmapInfo(), new RulesetInfo())); + AddStep("Set activity", () => api.Activity.Value = new UserActivity.SoloGame(new BeatmapInfo(), new RulesetInfo())); AddStep("Run command", () => Add(new NowPlayingCommand())); @@ -57,7 +60,7 @@ namespace osu.Game.Tests.Visual.Online [TestCase(false)] public void TestLinkPresence(bool hasOnlineId) { - AddStep("Set activity", () => API.Activity.Value = new UserActivity.InLobby(null)); + AddStep("Set activity", () => api.Activity.Value = new UserActivity.InLobby(null)); AddStep("Set beatmap", () => Beatmap.Value = new DummyWorkingBeatmap(Audio, null) { diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index b916339a53..fe500b9548 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -39,9 +39,15 @@ namespace osu.Game.Online.API private string password; - public Bindable LocalUser { get; } = new Bindable(createGuestUser()); + public IBindable LocalUser => localUser; + public IBindableList Friends => friends; + public IBindable Activity => activity; - public Bindable Activity { get; } = new Bindable(); + private Bindable localUser { get; } = new Bindable(createGuestUser()); + + private BindableList friends { get; } = new BindableList(); + + private Bindable activity { get; } = new Bindable(); protected bool HasLogin => authentication.Token.Value != null || (!string.IsNullOrEmpty(ProvidedUsername) && !string.IsNullOrEmpty(password)); @@ -61,10 +67,10 @@ namespace osu.Game.Online.API authentication.TokenString = config.Get(OsuSetting.Token); authentication.Token.ValueChanged += onTokenChanged; - LocalUser.BindValueChanged(u => + localUser.BindValueChanged(u => { - u.OldValue?.Activity.UnbindFrom(Activity); - u.NewValue.Activity.BindTo(Activity); + u.OldValue?.Activity.UnbindFrom(activity); + u.NewValue.Activity.BindTo(activity); }, true); var thread = new Thread(run) @@ -134,23 +140,37 @@ namespace osu.Game.Online.API } var userReq = new GetUserRequest(); + userReq.Success += u => { - LocalUser.Value = u; + localUser.Value = u; // todo: save/pull from settings - LocalUser.Value.Status.Value = new UserStatusOnline(); + localUser.Value.Status.Value = new UserStatusOnline(); failureCount = 0; + }; + + if (!handleRequest(userReq)) + { + failConnectionProcess(); + continue; + } + + // getting user's friends is considered part of the connection process. + var friendsReq = new GetFriendsRequest(); + + friendsReq.Success += res => + { + friends.AddRange(res); //we're connected! state.Value = APIState.Online; }; - if (!handleRequest(userReq)) + if (!handleRequest(friendsReq)) { - if (State.Value == APIState.Connecting) - state.Value = APIState.Failing; + failConnectionProcess(); continue; } @@ -186,6 +206,13 @@ namespace osu.Game.Online.API Thread.Sleep(50); } + + void failConnectionProcess() + { + // if something went wrong during the connection process, we want to reset the state (but only if still connecting). + if (State.Value == APIState.Connecting) + state.Value = APIState.Failing; + } } public void Perform(APIRequest request) @@ -322,7 +349,7 @@ namespace osu.Game.Online.API return true; } - public bool IsLoggedIn => LocalUser.Value.Id > 1; + public bool IsLoggedIn => localUser.Value.Id > 1; public void Queue(APIRequest request) { @@ -352,8 +379,12 @@ namespace osu.Game.Online.API password = null; authentication.Clear(); - // Scheduled prior to state change such that the state changed event is invoked with the correct user present - Schedule(() => LocalUser.Value = createGuestUser()); + // Scheduled prior to state change such that the state changed event is invoked with the correct user and their friends present + Schedule(() => + { + localUser.Value = createGuestUser(); + friends.Clear(); + }); state.Value = APIState.Offline; } diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index e275676cea..265298270c 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -18,6 +18,8 @@ namespace osu.Game.Online.API Id = 1001, }); + public BindableList Friends { get; } = new BindableList(); + public Bindable Activity { get; } = new Bindable(); public string AccessToken => "token"; @@ -86,5 +88,9 @@ namespace osu.Game.Online.API } public void SetState(APIState newState) => state.Value = newState; + + IBindable IAPIProvider.LocalUser => LocalUser; + IBindableList IAPIProvider.Friends => Friends; + IBindable IAPIProvider.Activity => Activity; } } diff --git a/osu.Game/Online/API/IAPIProvider.cs b/osu.Game/Online/API/IAPIProvider.cs index cadc806f4f..3a444460f2 100644 --- a/osu.Game/Online/API/IAPIProvider.cs +++ b/osu.Game/Online/API/IAPIProvider.cs @@ -13,13 +13,19 @@ namespace osu.Game.Online.API /// The local user. /// This is not thread-safe and should be scheduled locally if consumed from a drawable component. /// - Bindable LocalUser { get; } + IBindable LocalUser { get; } + + /// + /// The user's friends. + /// This is not thread-safe and should be scheduled locally if consumed from a drawable component. + /// + IBindableList Friends { get; } /// /// The current user's activity. /// This is not thread-safe and should be scheduled locally if consumed from a drawable component. /// - Bindable Activity { get; } + IBindable Activity { get; } /// /// Retrieve the OAuth access token. diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index d67d790ce2..1e342fa464 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -51,7 +51,6 @@ using osu.Game.Screens.Select; using osu.Game.Updater; using osu.Game.Utils; using LogLevel = osu.Framework.Logging.LogLevel; -using osu.Game.Users; using System.IO; namespace osu.Game @@ -976,7 +975,7 @@ namespace osu.Game if (newScreen is IOsuScreen newOsuScreen) { OverlayActivationMode.BindTo(newOsuScreen.OverlayActivationMode); - ((IBindable)API.Activity).BindTo(newOsuScreen.Activity); + API.Activity.BindTo(newOsuScreen.Activity); MusicController.AllowRateAdjustments = newOsuScreen.AllowRateAdjustments; diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs index 742b1055b2..c983b337b5 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs @@ -26,7 +26,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons private PostBeatmapFavouriteRequest request; private LoadingLayer loading; - private readonly Bindable localUser = new Bindable(); + private readonly IBindable localUser = new Bindable(); public string TooltipText { diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index a58d662de7..9a2dcd014a 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -26,7 +26,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores public readonly Bindable Beatmap = new Bindable(); private readonly Bindable ruleset = new Bindable(); private readonly Bindable scope = new Bindable(BeatmapLeaderboardScope.Global); - private readonly Bindable user = new Bindable(); + private readonly IBindable user = new Bindable(); private readonly Box background; private readonly ScoreTable scoreTable; diff --git a/osu.Game/Overlays/Comments/CommentsContainer.cs b/osu.Game/Overlays/Comments/CommentsContainer.cs index 2a78748be6..513fabf52a 100644 --- a/osu.Game/Overlays/Comments/CommentsContainer.cs +++ b/osu.Game/Overlays/Comments/CommentsContainer.cs @@ -25,7 +25,7 @@ namespace osu.Game.Overlays.Comments public readonly Bindable Sort = new Bindable(); public readonly BindableBool ShowDeleted = new BindableBool(); - protected readonly Bindable User = new Bindable(); + protected readonly IBindable User = new Bindable(); [Resolved] private IAPIProvider api { get; set; } diff --git a/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs b/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs index 41b25ee1a5..cc26a11da1 100644 --- a/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs +++ b/osu.Game/Overlays/Dashboard/Friends/FriendDisplay.cs @@ -5,18 +5,18 @@ using System.Collections.Generic; using System.Linq; using System.Threading; 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.Graphics.UserInterface; using osu.Game.Online.API; -using osu.Game.Online.API.Requests; using osu.Game.Users; using osuTK; namespace osu.Game.Overlays.Dashboard.Friends { - public class FriendDisplay : OverlayView> + public class FriendDisplay : CompositeDrawable { private List users = new List(); @@ -41,8 +41,16 @@ namespace osu.Game.Overlays.Dashboard.Friends private Container itemsPlaceholder; private LoadingLayer loading; - [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) + private readonly IBindableList apiFriends = new BindableList(); + + public FriendDisplay() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + } + + [BackgroundDependencyLoader(true)] + private void load(OverlayColourProvider colourProvider, IAPIProvider api) { InternalChild = new FillFlowContainer { @@ -132,6 +140,9 @@ namespace osu.Game.Overlays.Dashboard.Friends background.Colour = colourProvider.Background4; controlBackground.Colour = colourProvider.Background5; + + apiFriends.BindTo(api.Friends); + apiFriends.BindCollectionChanged((_, __) => Schedule(() => Users = apiFriends.ToList()), true); } protected override void LoadComplete() @@ -143,13 +154,6 @@ namespace osu.Game.Overlays.Dashboard.Friends userListToolbar.SortCriteria.BindValueChanged(_ => recreatePanels()); } - protected override APIRequest> CreateRequest() => new GetFriendsRequest(); - - protected override void OnSuccess(List response) - { - Users = response; - } - private void recreatePanels() { if (!users.Any()) diff --git a/osu.Game/Overlays/Profile/Header/Components/AddFriendButton.cs b/osu.Game/Overlays/Profile/Header/Components/AddFriendButton.cs index 6e1b6e2c7d..6c2b2dc16a 100644 --- a/osu.Game/Overlays/Profile/Header/Components/AddFriendButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/AddFriendButton.cs @@ -50,6 +50,8 @@ namespace osu.Game.Overlays.Profile.Header.Components } }; + // todo: when friending/unfriending is implemented, the APIAccess.Friends list should be updated accordingly. + User.BindValueChanged(user => updateFollowers(user.NewValue), true); } diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index 8beb955824..bd4577fd57 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -22,7 +22,7 @@ namespace osu.Game.Screens.Backgrounds private int currentDisplay; private const int background_count = 7; - private Bindable user; + private IBindable user; private Bindable skin; private Bindable mode; private Bindable introSequence; diff --git a/osu.Game/Screens/Menu/Disclaimer.cs b/osu.Game/Screens/Menu/Disclaimer.cs index 8368047d5a..ceec12c967 100644 --- a/osu.Game/Screens/Menu/Disclaimer.cs +++ b/osu.Game/Screens/Menu/Disclaimer.cs @@ -147,7 +147,7 @@ namespace osu.Game.Screens.Menu if (nextScreen != null) LoadComponentAsync(nextScreen); - currentUser.BindTo(api.LocalUser); + ((IBindable)currentUser).BindTo(api.LocalUser); } public override void OnEntering(IScreen last) diff --git a/osu.Game/Screens/Menu/MenuLogoVisualisation.cs b/osu.Game/Screens/Menu/MenuLogoVisualisation.cs index 92add458f9..c44beeffa5 100644 --- a/osu.Game/Screens/Menu/MenuLogoVisualisation.cs +++ b/osu.Game/Screens/Menu/MenuLogoVisualisation.cs @@ -12,7 +12,7 @@ namespace osu.Game.Screens.Menu { internal class MenuLogoVisualisation : LogoVisualisation { - private Bindable user; + private IBindable user; private Bindable skin; [BackgroundDependencyLoader] diff --git a/osu.Game/Screens/Menu/MenuSideFlashes.cs b/osu.Game/Screens/Menu/MenuSideFlashes.cs index 2ff8132d47..a1ae4555ed 100644 --- a/osu.Game/Screens/Menu/MenuSideFlashes.cs +++ b/osu.Game/Screens/Menu/MenuSideFlashes.cs @@ -35,7 +35,7 @@ namespace osu.Game.Screens.Menu private const double box_fade_in_time = 65; private const int box_width = 200; - private Bindable user; + private IBindable user; private Bindable skin; [Resolved]