Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,26 +1,39 @@
// Copyright 2025 The Nakama Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using System;
using System.Collections.Generic;
using System.Net.WebSockets;
using System.Text;
using System.Threading.Tasks;
using Nakama;
using UnityEditor;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UIElements;
using UnityNakamaFriends.Scripts;

namespace SampleProjects.NakamaFriends.Editor
namespace UnityNakamaFriends.Editor
{
public class AccountSwitcherEditor : EditorWindow
{
[SerializeField] private VisualTreeAsset tree;

private DropdownField accountDropdown;
private Label usernamesLabel;
private DropdownField _accountDropdown;
private Label _usernamesLabel;

private readonly SortedDictionary<string, string> accountUsernames = new();
private readonly SortedDictionary<string, string> _accountUsernames = new();

private const string ACCOUNT_USERNAMES_KEY = "AccountSwitcher_Usernames";
private const string AccountUsernamesKey = "AccountSwitcher_Usernames";

[MenuItem("Tools/Nakama/Account Switcher")]
public static void ShowWindow()
Expand All @@ -30,18 +43,18 @@ public static void ShowWindow()

window.Focus();
}

[MenuItem("Tools/Nakama/Clear Test Accounts")]
public static void ClearSavedAccounts()
{
EditorPrefs.DeleteKey(ACCOUNT_USERNAMES_KEY);
EditorPrefs.DeleteKey(AccountUsernamesKey);
Debug.Log("Cleared all saved account usernames");

// Refresh any open Account Switcher windows
var windows = Resources.FindObjectsOfTypeAll<AccountSwitcherEditor>();
foreach (var window in windows)
{
window.accountUsernames.Clear();
window._accountUsernames.Clear();
window.UpdateUsernameLabels();
}
}
Expand All @@ -50,11 +63,11 @@ private void CreateGUI()
{
tree.CloneTree(rootVisualElement);

accountDropdown = rootVisualElement.Q<DropdownField>("account-dropdown");
accountDropdown.RegisterValueChangedCallback(SwitchAccount);
_accountDropdown = rootVisualElement.Q<DropdownField>("account-dropdown");
_accountDropdown.RegisterValueChangedCallback(SwitchAccount);

_usernamesLabel = rootVisualElement.Q<Label>("usernames");

usernamesLabel = rootVisualElement.Q<Label>("usernames");

// Load saved usernames on startup
LoadAccountUsernames();
UpdateUsernameLabels();
Expand All @@ -66,9 +79,10 @@ private void CreateGUI()
{
if (!rootGameObject.TryGetComponent<NakamaFriendsController>(out var friendsController)) continue;

if (friendsController.Session != null)
var session = NakamaSingleton.Instance.Session;
if (session != null)
{
OnControllerInitialized(friendsController.Session);
OnControllerInitialized(session);
}
else
{
Expand All @@ -79,7 +93,7 @@ private void CreateGUI()

private void OnControllerInitialized(ISession session, NakamaFriendsController controller = null)
{
accountUsernames[accountDropdown.choices[0]] = session.Username;
_accountUsernames[_accountDropdown.choices[0]] = session.Username;
UpdateUsernameLabels();

if (controller != null)
Expand All @@ -90,17 +104,17 @@ private void OnControllerInitialized(ISession session, NakamaFriendsController c

private void LoadAccountUsernames()
{
var savedUsernames = EditorPrefs.GetString(ACCOUNT_USERNAMES_KEY, "");
var savedUsernames = EditorPrefs.GetString(AccountUsernamesKey, "");
if (string.IsNullOrEmpty(savedUsernames)) return;

try
{
var usernameData = JsonUtility.FromJson<SerializableStringDictionary>(savedUsernames);
accountUsernames.Clear();
_accountUsernames.Clear();

foreach (var item in usernameData.items)
{
accountUsernames[item.key] = item.value;
_accountUsernames[item.key] = item.value;
}
}
catch (Exception ex)
Expand All @@ -114,13 +128,13 @@ private void SaveAccountUsernames()
try
{
var usernameData = new SerializableStringDictionary();
foreach (var kvp in accountUsernames)
foreach (var kvp in _accountUsernames)
{
usernameData.items.Add(new SerializableKeyValuePair { key = kvp.Key, value = kvp.Value });
}

var json = JsonUtility.ToJson(usernameData);
EditorPrefs.SetString(ACCOUNT_USERNAMES_KEY, json);
EditorPrefs.SetString(AccountUsernamesKey, json);
}
catch (Exception ex)
{
Expand All @@ -140,6 +154,7 @@ private async void SwitchAccount(ChangeEvent<string> changeEvt)
{
deviceId = Guid.NewGuid().ToString();
}

PlayerPrefs.SetString("deviceId", deviceId);

var rootGameObjects = SceneManager.GetActiveScene().GetRootGameObjects();
Expand All @@ -148,32 +163,34 @@ private async void SwitchAccount(ChangeEvent<string> changeEvt)
if (!rootGameObject.TryGetComponent<NakamaFriendsController>(out var friendsController)) continue;

// Save username before switching
if (!string.IsNullOrEmpty(previousValue) && friendsController.Session != null)
var session = NakamaSingleton.Instance.Session;
if (!string.IsNullOrEmpty(previousValue) && session != null)
{
accountUsernames[previousValue] = friendsController.Session.Username;
_accountUsernames[previousValue] = session.Username;
}

await friendsController.MainSocket.CloseAsync();
if (friendsController.Session != null)
await NakamaSingleton.Instance.Socket.CloseAsync();

if (session != null)
{
await friendsController.Client.SessionLogoutAsync(friendsController.Session);
await NakamaSingleton.Instance.Client.SessionLogoutAsync(session);
}

try
{
var newSession = await friendsController.Client.AuthenticateDeviceAsync($"{deviceId}_{accountDropdown.index}");
accountUsernames[newValue] = newSession.Username;
await friendsController.MainSocket.ConnectAsync(newSession, true);
var newSession =
await NakamaSingleton.Instance.Client.AuthenticateDeviceAsync($"{deviceId}_{_accountDropdown.index}");
_accountUsernames[newValue] = newSession.Username;
await NakamaSingleton.Instance.Socket.ConnectAsync(newSession, true);
friendsController.SwitchComplete(newSession);

// Save usernames after successful authentication
SaveAccountUsernames();
break;
}
catch (ApiResponseException ex)
catch (ApiResponseException e)
{
Debug.LogWarning($"Error authenticating with Device ID: {ex.Message}");
Debug.LogWarning($"Error authenticating with Device ID: {e.Message}");
return;
}
}
Expand All @@ -185,8 +202,8 @@ private void UpdateUsernameLabels()
{
var sb = new StringBuilder();
var index = 1;
foreach (var kvp in accountUsernames)

foreach (var kvp in _accountUsernames)
{
sb.Append(index);
sb.Append(": ");
Expand All @@ -195,7 +212,7 @@ private void UpdateUsernameLabels()
index++;
}

usernamesLabel.text = sb.ToString();
_usernamesLabel.text = sb.ToString();
}

[Serializable]
Expand All @@ -211,4 +228,4 @@ private class SerializableKeyValuePair
public string value;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,89 +1,94 @@
// Copyright 2025 The Nakama Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using Nakama;
using UnityEngine;
using UnityEngine.UIElements;

namespace SampleProjects.NakamaFriends
namespace UnityNakamaFriends.Scripts
{
public class FriendsRecordView
{
private Label usernameLabel;
private Button addButton;
private Button removeButton;
private Button blockButton;
private Button unblockButton;
private Label _usernameLabel;
private Button _addButton;
private Button _removeButton;
private Button _blockButton;
private Button _unblockButton;

private NakamaFriendsController friendsController;
private NakamaFriendsController _friendsController;

public void SetVisualElement(VisualElement visualElement, NakamaFriendsController controller)
{
friendsController = controller;
_friendsController = controller;

usernameLabel = visualElement.Q<Label>("username");
_usernameLabel = visualElement.Q<Label>("username");

addButton = visualElement.Q<Button>("add");
addButton.RegisterCallback<ClickEvent>(AddFriend);
_addButton = visualElement.Q<Button>("add");
_addButton.RegisterCallback<ClickEvent>(AddFriend);

removeButton = visualElement.Q<Button>("remove");
removeButton.RegisterCallback<ClickEvent>(DeleteFriend);
_removeButton = visualElement.Q<Button>("remove");
_removeButton.RegisterCallback<ClickEvent>(DeleteFriend);

blockButton = visualElement.Q<Button>("block");
blockButton.RegisterCallback<ClickEvent>(BlockFriend);
_blockButton = visualElement.Q<Button>("block");
_blockButton.RegisterCallback<ClickEvent>(BlockFriend);

// To unblock a user, we just need to reset their friend state, so we can just unfriend them, which will remove the block.
unblockButton = visualElement.Q<Button>("unblock");
unblockButton.RegisterCallback<ClickEvent>(DeleteFriend);
_unblockButton = visualElement.Q<Button>("unblock");
_unblockButton.RegisterCallback<ClickEvent>(DeleteFriend);
}

public void SetFriend(IApiFriend record)
{
usernameLabel.text = $"{record.User.Username}";
_usernameLabel.text = $"{record.User.Username}";
var state = (NakamaFriendsController.FriendState)record.State;

switch (state)
{
case NakamaFriendsController.FriendState.FRIEND:
addButton.style.display = DisplayStyle.None;
removeButton.style.display = DisplayStyle.Flex;
blockButton.style.display = DisplayStyle.Flex;
unblockButton.style.display = DisplayStyle.None;
case NakamaFriendsController.FriendState.Friend:
_addButton.style.display = DisplayStyle.None;
_removeButton.style.display = DisplayStyle.Flex;
_blockButton.style.display = DisplayStyle.Flex;
_unblockButton.style.display = DisplayStyle.None;
break;
case NakamaFriendsController.FriendState.INVITE_SENT:
addButton.style.display = DisplayStyle.None;
removeButton.style.display = DisplayStyle.Flex;
blockButton.style.display = DisplayStyle.None;
unblockButton.style.display = DisplayStyle.None;
case NakamaFriendsController.FriendState.InviteSent:
_addButton.style.display = DisplayStyle.None;
_removeButton.style.display = DisplayStyle.Flex;
_blockButton.style.display = DisplayStyle.None;
_unblockButton.style.display = DisplayStyle.None;
break;
case NakamaFriendsController.FriendState.INVITE_RECEIVED:
addButton.style.display = DisplayStyle.Flex;
removeButton.style.display = DisplayStyle.Flex;
blockButton.style.display = DisplayStyle.Flex;
unblockButton.style.display = DisplayStyle.None;
case NakamaFriendsController.FriendState.InviteReceived:
_addButton.style.display = DisplayStyle.Flex;
_removeButton.style.display = DisplayStyle.Flex;
_blockButton.style.display = DisplayStyle.Flex;
_unblockButton.style.display = DisplayStyle.None;
break;
case NakamaFriendsController.FriendState.BLOCKED:
addButton.style.display = DisplayStyle.None;
removeButton.style.display = DisplayStyle.None;
blockButton.style.display = DisplayStyle.None;
unblockButton.style.display = DisplayStyle.Flex;
case NakamaFriendsController.FriendState.Blocked:
_addButton.style.display = DisplayStyle.None;
_removeButton.style.display = DisplayStyle.None;
_blockButton.style.display = DisplayStyle.None;
_unblockButton.style.display = DisplayStyle.Flex;
break;
default:
Debug.LogError("Invalid friend state!");
break;
}
}

private void AddFriend(ClickEvent _)
{
friendsController.AddFriend(usernameLabel.text);
}
private void AddFriend(ClickEvent ce) => _ = _friendsController.AddFriend(_usernameLabel.text);

private void DeleteFriend(ClickEvent _)
{
friendsController.DeleteFriend(usernameLabel.text);
}
private void DeleteFriend(ClickEvent ce) => _ = _friendsController.DeleteFriend(_usernameLabel.text);

private void BlockFriend(ClickEvent _)
{
friendsController.BlockFriend(usernameLabel.text);
}
private void BlockFriend(ClickEvent ce) => _ = _friendsController.BlockFriend(_usernameLabel.text);
}
}
Loading