using System; using System.Collections.Generic; using System.Threading.Tasks; using Unity.Services.Authentication; using Unity.Services.Friends; using Unity.Services.Friends.Exceptions; using Unity.Services.Friends.Models; using UnityEngine; namespace Phantom.XRMOD.GameServices.Runtime { /// /// Manages relationships between players, including friends, friend requests, and blocking. /// Wraps Unity's Friends Service. /// public class RelationshipsManager : IDisposable { private static RelationshipsManager _RELATIONSHIPS_MANAGER; /// /// Singleton instance of RelationshipsManager. /// public static RelationshipsManager GetInstance => _RELATIONSHIPS_MANAGER ??= new(); public IRelationshipsView relationshipsView; readonly List friendsEntryData = new List(); private readonly List requestsEntryData = new List(); readonly List blockEntryData = new List(); ILocalPlayerView localPlayerView; IAddFriendView addFriendView; IFriendsListView friendsListView; IRequestListView requestListView; IBlockedListView blockListView; PlayerProfile signInedPlayerProfile; private UserInfoModel userDataModel; private RelationshipsManager() { } // Fired after Friends service initializes and the local player presence is set. public event Action FriendSignedIn; public async void Dispose() { await SetPresence(Availability.Offline); } /// /// Initializes the Friend provider with user data. /// Sets up the service, binds views, and signs in the local player. /// /// The user data model for the local player. public async void InitFriendProvider(UserInfoModel _data) { try { await FriendsService.Instance.InitializeAsync(); userDataModel = _data; relationshipsView ??= new RelationshipsViewDataOnly(); if (relationshipsView == null) return; relationshipsView.Init(); ViewInit(); await SignInAsync(); // Notify listeners that Friends service sign-in is complete FriendSignedIn?.Invoke(); SubscribeToFriendsEventCallbacks(); RefreshAll(); Debug.Log("Friend System Initialized"); } catch (Exception tmp_Exception) { Debug.LogError(tmp_Exception); } } void ViewInit() { localPlayerView = relationshipsView.LocalPlayerView; addFriendView = relationshipsView.AddFriendView; //Bind Lists friendsListView = relationshipsView.FriendsListView; friendsListView.BindList(friendsEntryData); requestListView = relationshipsView.RequestListView; requestListView.BindList(requestsEntryData); blockListView = relationshipsView.BlockListView; blockListView.BindList(blockEntryData); //Bind Friends SDK Callbacks addFriendView.onFriendRequestSent += AddFriendAsync; friendsListView.onRemove += RemoveFriendAsync; friendsListView.onBlock += BlockFriendAsync; requestListView.onAccept += AcceptRequestAsync; requestListView.onDecline += DeclineRequestAsync; requestListView.onBlock += BlockFriendAsync; blockListView.onUnblock += UnblockFriendAsync; localPlayerView.OnPresenceChanged += SetPresenceAsync; } async Task SignInAsync() { var tmp_PlayerID = AuthenticationService.Instance.PlayerId; var tmp_PlayerName = AuthenticationService.Instance.PlayerName; signInedPlayerProfile = new PlayerProfile(GetPlayerNameFirstWord(tmp_PlayerName), tmp_PlayerName, tmp_PlayerID); await SetPresence(Availability.Online, $"In Home|{userDataModel.user_avatar}"); localPlayerView.Refresh(signInedPlayerProfile.ProfileImg, signInedPlayerProfile.Name, "In Home", Availability.Online); RefreshAll(); } string GetPlayerNameFirstWord(string _playerName) { if (string.IsNullOrEmpty(_playerName)) return "N"; return _playerName[0].ToString(); } public void RefreshAll() { RefreshFriends(); RefreshRequests(); RefreshBlocks(); } public async void BlockFriendAsync(string _id) { await BlockFriend(_id); RefreshAll(); } public async void UnblockFriendAsync(string _id) { await UnblockFriend(_id); RefreshBlocks(); RefreshFriends(); } public async void RemoveFriendAsync(string _id) { await RemoveFriend(_id); RefreshFriends(); } public async void AcceptRequestAsync(string _name) { await AcceptRequest(_name); RefreshRequests(); RefreshFriends(); } public async void DeclineRequestAsync(string _id) { await DeclineRequest(_id); RefreshRequests(); } public async void SetPresenceAsync((Availability presence, string activity) _status) { await SetPresence(_status.presence, _status.activity); // localPlayerView.Refresh(signInedPlayerProfile.ProfileImg, signInedPlayerProfile.Name, _status.activity, // _status.presence); } public async void AddFriendAsync(string _name) { var tmp_Success = await SendFriendRequest(_name); if (tmp_Success) { addFriendView.FriendRequestSuccess(); //If the added friend has also requested friendship, he is already a friend, just refresh the views. if (requestsEntryData.Find(_entry => _entry.Name == _name) != null) RefreshAll(); } else { addFriendView.FriendRequestFailed(); } } public void RefreshFriends() { friendsEntryData.Clear(); var tmp_Friends = GetFriends(); foreach (var tmp_Friend in tmp_Friends) { string tmp_ActivityText; var tmp_Info = new FriendsEntryData { Name = tmp_Friend.Profile.Name, Id = tmp_Friend.Id, Availability = tmp_Friend.Presence.Availability }; if (tmp_Friend.Presence.Availability == Availability.Offline || tmp_Friend.Presence.Availability == Availability.Invisible) { tmp_ActivityText = $"{tmp_Friend.Presence.LastSeen.ToShortDateString()} {tmp_Friend.Presence.LastSeen.ToLongTimeString()}"; } else { var tmp_Status = tmp_Friend.Presence.GetActivity().Status; var tmp_UserData = tmp_Status.Split("|"); tmp_ActivityText = tmp_UserData.Length > 1 ? tmp_UserData[0] : tmp_Status; tmp_Info.Avatar = tmp_UserData.Length >= 2 ? tmp_UserData[1] : ""; } tmp_Info.Activity = tmp_ActivityText; friendsEntryData.Add(tmp_Info); } relationshipsView.RelationshipBarView.Refresh(); relationshipsView.FriendsListView.Refresh(); } public void RefreshRequests() { requestsEntryData.Clear(); var tmp_Requests = GetRequests(); foreach (var tmp_Request in tmp_Requests) { requestsEntryData.Add(new PlayerProfile("", tmp_Request.Profile.Name, tmp_Request.Id)); } relationshipsView.RequestListView.Refresh(); relationshipsView.RelationshipBarView.Refresh(); } public void RefreshBlocks() { blockEntryData.Clear(); foreach (var tmp_Block in FriendsService.Instance.Blocks) { blockEntryData.Add(new PlayerProfile("", tmp_Block.Member.Profile.Name, tmp_Block.Member.Id)); } relationshipsView.BlockListView.Refresh(); relationshipsView.RelationshipBarView.Refresh(); } async Task SendFriendRequest(string _playerName) { try { // We add the friend by name in this sample but you can also add a friend by ID using AddFriendAsync var tmp_Relationship = await FriendsService.Instance.AddFriendByNameAsync(_playerName); Debug.Log($"Friend request sent to {_playerName}."); // If both players send friend request to each other, their relationship is changed to Friend. return tmp_Relationship.Type is RelationshipType.FriendRequest or RelationshipType.Friend; } catch (FriendsServiceException tmp_E) { Debug.Log($"Failed to Request {_playerName} - {tmp_E}."); return false; } } async Task RemoveFriend(string _playerId) { try { await FriendsService.Instance.DeleteFriendAsync(_playerId); Debug.Log($"{_playerId} was removed from the friends list."); } catch (FriendsServiceException e) { Debug.Log($"Failed to remove {_playerId}. - {e}"); } } async Task BlockFriend(string _playerId) { try { await FriendsService.Instance.AddBlockAsync(_playerId); Debug.Log($"{_playerId} was blocked."); } catch (FriendsServiceException tmp_E) { Debug.Log($"Failed to block {_playerId}. - {tmp_E}"); } } async Task UnblockFriend(string _playerId) { try { await FriendsService.Instance.DeleteBlockAsync(_playerId); Debug.Log($"{_playerId} was unblocked."); } catch (FriendsServiceException e) { Debug.Log($"Failed to unblock {_playerId} - {e}."); } } async Task AcceptRequest(string _playerName) { try { await SendFriendRequest(_playerName); Debug.Log($"Friend request from {_playerName} was accepted."); } catch (FriendsServiceException e) { Debug.Log($"Failed to accept request from {_playerName}. - {e}"); } } async Task DeclineRequest(string _playerId) { try { await FriendsService.Instance.DeleteIncomingFriendRequestAsync(_playerId); Debug.Log($"Friend request from {_playerId} was declined."); } catch (FriendsServiceException tmp_E) { Debug.Log($"Failed to decline request from {_playerId}. - {tmp_E}"); } } /// /// Get an amount of friends (including presence data). /// /// List of friends. public List GetFriends() { return Utilities.GetNonBlockedMembers(FriendsService.Instance.Friends); } /// /// Get an amount of Requests. The friends SDK maintains relationships unless explicitly deleted, even those /// towards blocked players. We don't want to show blocked players' requests, so we filter them out. /// /// List of players. public List GetRequests() { return Utilities.GetNonBlockedMembers(FriendsService.Instance.IncomingFriendRequests); } public void ChangeAvailability(Availability _availability, string _status) { SetPresenceAsync((_availability, _status)); } private async Task SetPresence(Availability _presenceAvailabilityOptions, string _activityStatus = "") { var tmp_Activity = new Activity {Status = _activityStatus}; try { await FriendsService.Instance.SetPresenceAsync(_presenceAvailabilityOptions, tmp_Activity); Debug.Log($"Availability changed to {_presenceAvailabilityOptions}."); } catch (FriendsServiceException e) { Debug.Log($"Failed to set the presence to {_presenceAvailabilityOptions} - {e}"); } } void SubscribeToFriendsEventCallbacks() { try { FriendsService.Instance.RelationshipAdded += e => { RefreshRequests(); RefreshFriends(); Debug.Log($"create {e.Relationship} EventReceived"); }; FriendsService.Instance.MessageReceived += _e => { RefreshRequests(); Debug.Log("MessageReceived EventReceived"); }; FriendsService.Instance.PresenceUpdated += _e => { RefreshFriends(); Debug.Log("PresenceUpdated EventReceived"); }; FriendsService.Instance.RelationshipDeleted += _e => { RefreshFriends(); Debug.Log($"Delete {_e.Relationship} EventReceived"); }; } catch (FriendsServiceException tmp_E) { Debug.Log( "An error occurred while performing the action. HttpCode: " + tmp_E.StatusCode + ", FriendsErrorCode: " + tmp_E.ErrorCode + ", Message: " + tmp_E.Message); } } } }