Skip to main content

Implement Friends using the Unreal Engine SDK

Last updated on October 24, 2024

Overview

AGS Shared Cloud

This topic is specific to the AccelByte Gaming Services (AGS) Shared Cloud tier.

The friends service is the part of AGS that allows players to connect socially with other players. For more information, refer to the Friends feature of the Social service.

Prerequisites

The examples in this document use AGS Shared Cloud UE SDK version 15.0.0. Any other version may not be compatible or may need different steps to implement.

This guide assumes that you have already implemented the Lobby service. Please make sure the player can successfully connect to the Lobby before continuing.

Quick Reference

References
```cpp #include "Api/AccelByteLobbyApi.h" #include "Api/AccelByteUserApi.h" #include "Core/AccelByteRegistry.h" #include "Models/AccelByteLobbyModels.h" ```
Friend Notification Events
FRegistry::Lobby.SetOnFriendRequestAcceptedNotifDelegate(Api::Lobby::FAcceptFriendsNotif::CreateWeakLambda(this, [](const FAccelByteModelsAcceptFriendsNotif& Result)
{
// On friend request accepted
}));

FRegistry::Lobby.SetOnUnfriendNotifDelegate(Api::Lobby::FUnfriendNotif::CreateWeakLambda(this, [](const FAccelByteModelsUnfriendNotif& Result)
{
// On player is unfriended
}));

FRegistry::Lobby.SetOnCancelFriendsNotifDelegate(Api::Lobby::FCancelFriendsNotif::CreateWeakLambda(this, [](const FAccelByteModelsCancelFriendsNotif& Result)
{
// On friend request is canceled
}));

FRegistry::Lobby.SetOnRejectFriendsNotifDelegate(Api::Lobby::FRejectFriendsNotif::CreateWeakLambda(this, [](const FAccelByteModelsRejectFriendsNotif& Result)
{
// On friend request is rejected
}));

FRegistry::Lobby.SetOnIncomingRequestFriendsNotifDelegate(Api::Lobby::FRequestFriendsNotif::CreateWeakLambda(this, [](const FAccelByteModelsRequestFriendsNotif& Result)
{
// On player receive friend request
}));
Request Friend
// Response delegate
FRegistry::Lobby.SetRequestFriendsResponseDelegate(
Api::Lobby::FRequestFriendsResponse::CreateWeakLambda(this, [](const FAccelByteModelsRequestFriendsResponse& Result)
{
// On send friend Request success
}),
FErrorHandler::CreateWeakLambda(this, [](int32 ErrorCode, const FString& ErrorMessage)
{
// On send friend Request failed
}));
// Request
FRegistry::Lobby.RequestFriend("DesiredUserId");
Accept Friend Request
FRegistry::Lobby.SetAcceptFriendsResponseDelegate(
Api::Lobby::FAcceptFriendsResponse::CreateWeakLambda(this, [](const FAccelByteModelsAcceptFriendsResponse& Result)
{
// On success accepting the friend request
}),
FErrorHandler::CreateWeakLambda(this, [](int32 ErrorCode, const FString& ErrorMessage)
{
// On failed accepting the friend request
}));
FRegistry::Lobby.AcceptFriend("DesiredUserId");
Reject Friend Request
FRegistry::Lobby.SetRejectFriendsResponseDelegate(
Api::Lobby::FRejectFriendsResponse::CreateWeakLambda(this, [](const FAccelByteModelsRejectFriendsResponse& Result)
{
// On success rejecting the friend request
}),
FErrorHandler::CreateWeakLambda(this, [](int32 ErrorCode, const FString& ErrorMessage)
{
// On failed rejecting the friend request
}));
FRegistry::Lobby.RejectFriend("DesiredUserId");
Cancel Friend Request
FRegistry::Lobby.SetCancelFriendsResponseDelegate(
Api::Lobby::FCancelFriendsResponse::CreateWeakLambda(this, [](const FAccelByteModelsCancelFriendsResponse& Result)
{
// On success canceling the friend request
}),
FErrorHandler::CreateWeakLambda(this, [](int32 ErrorCode, const FString& ErrorMessage)
{
// On failed canceling the friend request
}));
FRegistry::Lobby.CancelFriendRequest("DesiredUserId");
Unfriend
// Response delegate
FRegistry::Lobby.SetUnfriendResponseDelegate(
Api::Lobby::FUnfriendResponse::CreateWeakLambda(this, [](const FAccelByteModelsUnfriendResponse& Result)
{
// On unfriend success
}),
FErrorHandler::CreateWeakLambda(this, [](int32 ErrorCode, const FString& ErrorMessage)
{
// On unfriend failed
}));
// Request
FRegistry::Lobby.Unfriend(UserData.UserId);
Load Friend List
// Response delegate
FRegistry::Lobby.SetLoadFriendListResponseDelegate(
Api::Lobby::FLoadFriendListResponse::CreateWeakLambda(this, [](const FAccelByteModelsLoadFriendListResponse& Result)
{
// On load friend list success
}),
FErrorHandler::CreateWeakLambda(this, [](int32 ErrorCode, const FString& ErrorMessage)
{
// On load friend list failed
}));
// Request
FRegistry::Lobby.LoadFriendsList();
List Incoming Friend Requests
// Response delegate
FRegistry::Lobby.SetListIncomingFriendsResponseDelegate(
Api::Lobby::FListIncomingFriendsResponse::CreateWeakLambda(this, [](const FAccelByteModelsListIncomingFriendsResponse& Result)
{
// On load incoming friend list invitation success
},
FErrorHandler::CreateWeakLambda(this, [](int32 ErrorCode, const FString& ErrorMessage)
{
// On load incoming friend list invitation failed
})));
// Request
FRegistry::Lobby.ListIncomingFriends();
List Outgoing Friend Requests
// Response delegate
FRegistry::Lobby.SetListOutgoingFriendsResponseDelegate(
Api::Lobby::FListOutgoingFriendsResponse::CreateWeakLambda(this, [](const FAccelByteModelsListOutgoingFriendsResponse& Result)
{
// On load outgoing friend list invitation success
},
FErrorHandler::CreateWeakLambda(this, [](int32 ErrorCode, const FString& ErrorMessage)
{
// On load outgoing friend list invitation failed
})));
// Request
FRegistry::Lobby.ListOutgoingFriends();
Search Users
FRegistry::User.SearchUsers(
"FriendName",
THandler<FPagedPublicUsersInfo>::CreateWeakLambda(this, [](const FPagedPublicUsersInfo& Result)
{
// On success search users
},
FErrorHandler::CreateWeakLambda(this, [](int32 ErrorCode, const FString& ErrorMessage)
{
// On search users failed
})));
Get User data by ID
FRegistry::User.GetUserByUserId(
"UserId",
THandler<FSimpleUserData>::CreateWeakLambda(this, [](const FSimpleUserData& Result)
{
// On Get user data by id success
},
FErrorHandler::CreateWeakLambda(this, [](int32 ErrorCode, const FString& ErrorMessage)
{
// On Get user data by id failed
})));
Get bulk User data
FRegistry::User.BulkGetUserInfo(
ArrayOfUsersId,
THandler<FListBulkUserInfo>::CreateWeakLambda(this, [](const FListBulkUserInfo& Result)
{
// On Get bulk user info success
},
FErrorHandler::CreateWeakLambda(this, [](int32 ErrorCode, const FString& ErrorMessage)
{
// On Get bulk user info failed
})));

Quickstart

In this section, you will learn how to use AGS Shared Cloud Friends service.

This tutorial is more abstract than some others as the specifics of how each game implements the Friends service can vary dramatically.

There are a handful of data models and structs to familiarize yourself with. (You can find most of these in the models folder inside the SDK plugin. In this case, the default path is .\Plugins\AccelByteUe4Sdk\Source\AccelByteUe4Sdk\Public\Models.)

AccelByte's Game SDK uses a mixture of Websocket and HTTP requests, generally presented to you through a wrapper layer that uses callbacks and actions to return your data. Because of this, you must design your UI to accommodate delayed callbacks and, sometimes, several layers of callbacks (e.g., requesting a list of Friends > requesting specific data about friends > requesting the avatar of a friend).

info

The Lobby service Websocket API has two type functions for each functionality: Response delegate

  • The delegate accesses the resulting response from the SDK's function
  • Not necessary, but must be defined before you call the Request function Request function
  • This function sends a request for services to the Websocket API
  • Can be called separately from the Response delegate in a different function

Friend Logic

You will start by adding simple friend logic to your game.

Find Friends

You will focus on the Find Friends functionality first. Since this feature is usually separate from the main Friends Menu widget, you will need to:

  1. Create a user widget C++ class called AccelByteFindFriend. In the AccelByteFindFriend class, include the AccelByte header to ensure the SDK functions work correctly.

    ...
    #include "Api/AccelByteLobbyApi.h"
    #include "Api/AccelByteUserApi.h"
  2. For the Search a Friend feature, you can use the User SDK's SearchUser() function to return a list of search results with the desired player's display name as the parameter for the search query.

    Create a new function in the AccelByteFindFriend class, such as in the following:

    void UAccelByteFindFriend::FindUsers(const FString& FriendName)
    {
    FRegistry::User.SearchUsers(
    FriendName,
    THandler<FPagedPublicUsersInfo>::CreateWeakLambda(this, [this](const FPagedPublicUsersInfo& Result)
    {
    UE_LOG(LogTemp, Log, TEXT("Success Find Friends"));
    }),
    FErrorHandler::CreateWeakLambda(this, [](int32 ErrorCode, const FString& ErrorMessage)
    {
    UE_LOG(LogTemp, Error, TEXT("Error SearchUsers, Error Code: %d Error Message: %s"), ErrorCode, *ErrorMessage);
    }));
    }
  3. To send a friend request, you will need the desired player's User ID as the parameter. Use the SearchUsers() function to find the User ID.

  4. Since you will also need the response data from SearchUsers(), call RequestFriend() right after the SearchUsers() query in the FindFriends() function of the AccelByteFindFriend class.

    ...
    // SearchUser() OnSuccessDelegate
    THandler<FPagedPublicUsersInfo>::CreateWeakLambda(this, [](const FPagedPublicUsersInfo& Result)
    {
    ...
    for (const FPublicUserInfo& UserData : Result.Data)
    {
    // 1. Set Response Delegate
    FRegistry::Lobby.SetRequestFriendsResponseDelegate(
    Api::Lobby::FRequestFriendsResponse::CreateWeakLambda(this, [](const FAccelByteModelsRequestFriendsResponse& Result)
    {
    UE_LOG(LogTemp, Log, TEXT("Send Friend Request success"));
    }),
    FErrorHandler::CreateWeakLambda(this, [](int32 ErrorCode, const FString& ErrorMessage)
    {
    UE_LOG(LogTemp, Error, TEXT("Error RequestFriendsResponseDelegate, Error Code: %d Error Message: %s"), ErrorCode, *ErrorMessage);
    }));

    // 2. Send Request
    FRegistry::Lobby.RequestFriend("DesiredUserId");
    }
    }),
    ...
  5. For functionality related to Friend Request, create a new user widget C++ class called AccelByteFriendEntry and include the following files at the top of the class and header files:

    ...
    #include "Api/AccelByteLobbyApi.h"
    #include "Api/AccelByteUserApi.h"
    #include "Core/AccelByteRegistry.h"
    ...
  6. In the AccelByteFriendEntry class, create some new functions for Friend Request functionality:

Accept an incoming friend request

void UAccelByteFriendEntry::OnClickedAccept()
{
FRegistry::Lobby.SetAcceptFriendsResponseDelegate(
Api::Lobby::FAcceptFriendsResponse::CreateWeakLambda(this, [](const FAccelByteModelsAcceptFriendsResponse & Result)
{
UE_LOG(LogTemp, Log, TEXT("Successfully accept a friend request!"));
}),
FErrorHandler::CreateWeakLambda(this, [](int32 ErrorCode, const FString& ErrorMessage)
{
UE_LOG(LogTemp, Error, TEXT("Failed to accept a friend request! Code: %d, Reason: %s"), ErrorCode, *ErrorMessage);
}));
FRegistry::Lobby.AcceptFriend("DesiredUserId");
}

Reject an incoming friend request

void UAccelByteFriendEntry::OnClickedDecline()
{
FRegistry::Lobby.SetRejectFriendsResponseDelegate(
Api::Lobby::FRejectFriendsResponse::CreateWeakLambda(this, [](const FAccelByteModelsRejectFriendsResponse& Result)
{
UE_LOG(LogTemp, Log, TEXT("Successfully reject the friend request!"));
}),
FErrorHandler::CreateWeakLambda(this, [](int32 ErrorCode, const FString& ErrorMessage)
{
UE_LOG(LogTemp, Error, TEXT("Failed to reject the friend request! Code: %d, Reason: %s"), ErrorCode, *ErrorMessage);
}));
FRegistry::Lobby.RejectFriend("DesiredUserId");
}

Cancel an outgoing friend request

void UAccelByteFriendEntry::OnClickedCancelRequest()
{
FRegistry::Lobby.SetCancelFriendsResponseDelegate(
Api::Lobby::FCancelFriendsResponse::CreateWeakLambda(this, [](const FAccelByteModelsCancelFriendsResponse& Result)
{
UE_LOG(LogTemp, Log, TEXT("Successfully cancel the friend request!"));
}),
FErrorHandler::CreateWeakLambda(this, [](int32 ErrorCode, const FString& ErrorMessage)
{
UE_LOG(LogTemp, Error, TEXT("Failed to cancel the friend request! Code: %d, Reason: %s"), ErrorCode, *ErrorMessage);
}));
FRegistry::Lobby.CancelFriendRequest("DesiredUserId");
}

Unfriend

You can unfriend a player by using the SetUnfriendResponseDelegate() function.

void UAccelByteFriendEntry::OnClickedUnfriend()
{
FRegistry::Lobby.SetUnfriendResponseDelegate(
Api::Lobby::FUnfriendResponse::CreateWeakLambda(this, [](const FAccelByteModelsUnfriendResponse& Result)
{
UE_LOG(LogTemp, Log, TEXT("Successfully unfriend a friend!"));
}),
FErrorHandler::CreateWeakLambda(this, [](int32 ErrorCode, const FString& ErrorMessage)
{
UE_LOG(LogTemp, Error, TEXT("Failed to unfriend a friend! %d: %s"), ErrorCode, *ErrorMessage);
}));

FRegistry::Lobby.Unfriend("DesiredUserId");
}

Friend Menu Functionalities

The Friends menu functionality includes:

  • Current friends list
  • Outgoing friend list
  • Incoming friend list
  • Display the friend list
  • Send notifications

Follow the steps below to use the Friends menu functionality:

Current friends list

  1. Create a new user widget C++ class called AccelByteFriends to specify the flow between the functions when creating a Friend system.

  2. Add the AccelByte header in the AccelByteFriends class to ensure the functions work correctly for the Lobby services (as the Friends service is a part of the Lobby).

    .cpp

    ...
    #include "Api/AccelByteLobbyApi.h"
    #include "Core/AccelByteRegistry.h"
    ...
  3. You can display the friends list by using the SetLoadFriendListResponseDelegate(). To do this, create a new function in the AccelByteFriends class.

    void UAccelByteFriends::LoadFriendList()
    {
    FRegistry::Lobby.SetLoadFriendListResponseDelegate(
    Api::Lobby::FLoadFriendListResponse::CreateWeakLambda(this, [](const FAccelByteModelsLoadFriendListResponse& Result)
    {
    UE_LOG(LogTemp, Log, TEXT("Success Retrieve Load Friend List Response!"));
    }),
    FErrorHandler::CreateWeakLambda(this, [](int32 ErrorCode, const FString& ErrorMessage)
    {
    UE_LOG(LogTemp, Error, TEXT("Error LoadFriendListResponseDelegate, Error Code: %d Error Message: %s"), ErrorCode, *ErrorMessage);
    }));
    FRegistry::Lobby.LoadFriendsList();
    }

Display incoming and outgoing friend requests

  1. To display the User IDs for pending Incoming and Outgoing requests, use either SetListIncomingFriendsResponseDelegate or SetListOutgoingFriendsResponseDelegate.

  2. To do this, create a new function for both the Incoming and Outgoing pending request lists in the AccelByteFriends class.

    void UAccelByteFriends::LoadPendingIncomingList()
    {
    FRegistry::Lobby.SetListIncomingFriendsResponseDelegate(
    Api::Lobby::FListIncomingFriendsResponse::CreateWeakLambda(this, [](const FAccelByteModelsListIncomingFriendsResponse& Result)
    {
    UE_LOG(LogTemp, Log, TEXT("Success Retrieve List Incoming Friend Response!"));
    },
    FErrorHandler::CreateWeakLambda(this, [](int32 ErrorCode, const FString& ErrorMessage)
    {
    UE_LOG(LogTemp, Error, TEXT("Unable to retrieve the list of incoming friend requests!"));
    })));

    FRegistry::Lobby.ListIncomingFriends();
    }

    void UAccelByteFriends::LoadPendingOutgoingList()
    {
    FRegistry::Lobby.SetListOutgoingFriendsResponseDelegate(
    Api::Lobby::FListOutgoingFriendsResponse::CreateWeakLambda(this, [](const FAccelByteModelsListOutgoingFriendsResponse& Result)
    {
    UE_LOG(LogTemp, Log, TEXT("Successfully Retrieved List Outgoing Friend Response"));
    },
    FErrorHandler::CreateWeakLambda(this, [](int32 ErrorCode, const FString& ErrorMessage)
    {
    UE_LOG(LogTemp, Error, TEXT("Unable to retrieve the list of outgoing friend requests"));
    })));

    FRegistry::Lobby.ListOutgoingFriends();
    }

Notifications

  1. Create a new function in the AccelByteFriends class called SetNotificationDelegate() to notify the player of the Friends Notification event and make your Friends List more responsive. The notification events are explained below:

    DelegatesUsage
    `SetOnFriendRequestAcceptedNotifDelegate()`Receive notifications when a friend request from the sender is accepted by the receiver.
    `SetOnUnfriendNotifDelegate()`Receive notifications when the receiver is unfriended by the sender.
    `SetOnCancelFriendsNotifDelegate()`Receive notifications when a friend request from the sender is canceled by the sender.
    `SetOnRejectFriendsNotifDelegate()`Receive notifications when a friend request from the sender is rejected by the receiver.
    `SetOnIncomingRequestFriendsNotifDelegate()`Receive notifications when a player has an incoming friend request.
    void UAccelByteFriends::SetNotificationDelegate()
    {
    FRegistry::Lobby.SetOnFriendRequestAcceptedNotifDelegate(Api::Lobby::FAcceptFriendsNotif::CreateWeakLambda(this, [](const FAccelByteModelsAcceptFriendsNotif& Result)
    {
    UE_LOG(LogTemp, Log, TEXT("On accepted a friend request from a player"));
    }));

    FRegistry::Lobby.SetOnUnfriendNotifDelegate(Api::Lobby::FUnfriendNotif::CreateWeakLambda(this, [](const FAccelByteModelsUnfriendNotif& Result)
    {
    UE_LOG(LogTemp, Log, TEXT("On unfriend by a friend"));
    }));

    FRegistry::Lobby.SetOnCancelFriendsNotifDelegate(Api::Lobby::FCancelFriendsNotif::CreateWeakLambda(this, [](const FAccelByteModelsCancelFriendsNotif& Result)
    {
    UE_LOG(LogTemp, Log, TEXT("On a friend request canceled by the sender"));
    }));

    FRegistry::Lobby.SetOnRejectFriendsNotifDelegate(Api::Lobby::FRejectFriendsNotif::CreateWeakLambda(this, [](const FAccelByteModelsRejectFriendsNotif& Result)
    {
    UE_LOG(LogTemp, Log, TEXT("On the friend request is rejected by another player"));
    }));

    FRegistry::Lobby.SetOnIncomingRequestFriendsNotifDelegate(Api::Lobby::FRequestFriendsNotif::CreateWeakLambda(this, [](const FAccelByteModelsRequestFriendsNotif& Result)
    {
    UE_LOG(LogTemp, Log, TEXT("On received a friend request"));
    }));
    }

Additional friend functionalities

Get user data

To get user data, you need the user's Friend ID. To find this:

  1. Modify SetOnFriendRequestAcceptedNotifDelegate.

  2. Add the call GetUserData after the friend request is accepted.

    ...
    FRegistry::Lobby.SetOnFriendRequestAcceptedNotifDelegate(Api::Lobby::FAcceptFriendsNotif::CreateWeakLambda(this, [this](const FAccelByteModelsAcceptFriendsNotif& Result)
    {
    ...
    FRegistry::User.GetUserByUserId(
    Result.friendId,
    THandler<FSimpleUserData>::CreateWeakLambda(this, [](const FSimpleUserData& Data)
    {
    UE_LOG(LogTemp, Log, TEXT("Get User By User Id Success. DisplayName: %s"), *Data.DisplayName);
    },
    FErrorHandler::CreateWeakLambda(this, [](int32 ErrorCode, const FString& ErrorMessage)
    {
    UE_LOG(LogTemp, Error, TEXT("Get User by user id failed. Code: %d, Message: %s"), ErrorCode, *ErrorMessage);
    })));
    }));
    ...

Bulk get User data

To get user data in bulk, you will first need to load the Friends list.

The easiest way to do this is to add the call GetBulkUserInfo into the LoadFriendList() success response delegate in the SetLoadFriendListResponseDelegate function.

<Tabs groupId="platforms">
<TabItem value="cpp" label=".cpp">

```cpp
...
void UAccelByteFriends::LoadFriendList()
{
FRegistry::Lobby.SetLoadFriendListResponseDelegate(
Api::Lobby::FLoadFriendListResponse::CreateWeakLambda(this, [](const FAccelByteModelsLoadFriendListResponse& Result)
{
...
FRegistry::User.BulkGetUserInfo(
Result.friendsId,
THandler<FListBulkUserInfo>::CreateWeakLambda(this, [](const FListBulkUserInfo& Data)
{
// On Get bulk user info success
for (FBaseUserInfo Info : Data.Data)
{
UE_LOG(LogTemp, Log, TEXT("Get Bulk User Info Success. DisplayName: %s"), *Info.DisplayName);
}
},
FErrorHandler::CreateWeakLambda(this, [](int32 ErrorCode, const FString& ErrorMessage)
{
UE_LOG(LogTemp, Error, TEXT("Get Bulk User info failed. Code: %d, Message: %s"), ErrorCode, *ErrorMessage);
})));
}),
FErrorHandler::CreateWeakLambda(this, [](int32 ErrorCode, const FString& ErrorMessage)
{
...
}));
...
}
...
```

</TabItem>
</Tabs>

Unbind events

Use the UnbindFriendNotifEvents and UnbindFriendResponseEvents delegates to unbind the Friends service.

<Tabs groupId="platforms">
<TabItem value="cpp" label=".cpp">

```cpp
...
FRegistry::Lobby.UnbindFriendNotifEvents();

FRegistry::Lobby.UnbindFriendResponseEvents();
...
```

</TabItem>
</Tabs>
danger

After you have called this code remember to resubscribe all the notification and response delegates before you continue.

Congratulations! You have successfully implemented the basics of the Friends service.

Continue on for a step-by-step example of the UI and code implementation. Otherwise, you are now ready to move on to Party services.

Step-by-step guide

In this guide you will learn how to implement all the main Friends services, except the Block, Unblock, and Presence functionalities. Although you may mention these functions in the following sections, they are not required for the following implementation. You will explain them in detail later.

Implement the UI

Follow the steps below to implement the UI:

  1. Create a widget blueprint class called WB_FriendMenu.

  2. Set its parent class to refer to the AccelByteFriends class.

  3. Add the following to the Friends menu:

    • Text box for the header text.
    • Text boxfor the total number of online friends.
    • Button to go back to the previous page.
    • Button to open the Find Friends widget.
    • Four buttons: one each for Friends, Incoming Requests, Sent Requests, and Blocked tabs.
    • Size box for the content list.
  4. Create a widget blueprint class called WB_FriendEntry.

  5. Set the AccelByteFriendEntry class as WB_FriendEntry's parent class.

  6. Include the following objects in the widget blueprint:

    • Image for a friend's avatar image.
    • Text box for a friend's display name.
    • Text box for a friend's online status.
    • Four buttons: one each for Chat, Unfriend, Send party invitation, and Block.
  7. Create a widget blueprint class called WB_FindFriend.

  8. Set the AccelByteFindFriend class as the WB_FindFriend parent class.

  9. Make sure the widget blueprint has the following objects:

    • Editable text box to input the desired player's display name.
    • Button to submit the search input.
  10. (Optional) In this example, the Friends service is part of the Lobby, so you will need to open the WB_LobbyMenu.

  11. You will need to redirect the Friends service's widgets usage from the Lobby menu, so make sure it has the following objects:

    • Text box for the header text.
    • Button to go back to the Main menu.
    • Button to open the Friends menu widget.
  12. (Optional) To make it easier to move between pages (on the game side), create a new widget blueprint class called WB_MainMenu and make sure it has the following components:

    • Button for the Lobby menu.
    • Button for logging out.

Implement the main menu code

Follow the steps below to implement the code:

  1. Create a user widget C++ class called AccelByteMainMenu and set the class as the parent class of your Main menu widget blueprint (in this case, WB_MainMenu).

  2. Add the following code to the top of the AccelByteMenu class:

    ...
    #include "Authentication/AccelByteAuth.h"
    #include "Components/Button.h"
    #include "TutorialProject/TutorialMenuHUD.h"
  3. Add the NativeConstruct() override function to set up the Lobby button in the Main Menu.

    void UAccelByteMainMenu::NativeConstruct()
    {
    Super::NativeConstruct();

    TutorialMenuHUD = Cast<ATutorialMenuHUD>(GetOwningPlayer()->GetHUD());

    check(TutorialMenuHUD);

    Btn_Lobby->OnClicked.AddUniqueDynamic(TutorialMenuHUD, &ATutorialMenuHUD::OpenLobbyMenu);
    }
  4. Create a function in the AccelByteMainMenu class to bind the OnLogoutButtonClicked() function from the AccelByteAuth class with the Log Out button in the Main Menu.

    void UAccelByteMainMenu::LogOut()
    {
    TutorialMenuHUD->GetLoginMenu()->OnLogoutButtonClicked();
    }
  5. Inside the NativeConstruct() function, set the LogOut() function as the Logout button's callback function.

    void UAccelByteMainMenu::NativeConstruct()
    {
    ...
    Btn_Logout->OnClicked.AddUniqueDynamic(this, &UAccelByteMainMenu::LogOut);
    }

Tutorial menu HUD

Prepare all of the Main Menu and Friends-related widgets so they are accessible to other classes via the TutorialMenuHUDclass.

To do this:

  1. Add the related classes at the top of the class.

    ...
    #include "AccelByte/AccelByteMainMenu.h"
    #include "AccelByte/Friends/AccelbyteFindFriend.h"
    #include "AccelByte/Friends/AccelByteFriends.h"
  2. In the BeginPlay() function, initialize pointers for the Main Menu, Friends Menu and Find Friends widget classes.

    void ATutorialMenuHUD::BeginPlay()
    {
    ...
    check(MainMenuClass != nullptr);
    check(FriendsMenuClass != nullptr);
    check(FindFriendsMenuClass != nullptr);
    ...
    MainMenu = CreateWidget<UAccelByteMainMenu>(PlayerController, MainMenuClass.Get());
    FriendsMenu = CreateWidget<UAccelByteFriends>(PlayerController, FriendsMenuClass.Get());
    FindFriendsMenu = CreateWidget<UAccelByteFindFriend>(PlayerController, FindFriendsMenuClass.Get());
    ...
    }
  3. Add the getter functions for Main Menu and each Friends widget pointer.

    /**
    * @brief Getter for Main Menu widget
    */
    UAccelByteMainMenu* GetMainMenu() const {return MainMenu; }

    /**
    * @brief Getter for Friends Menu widget
    */
    UAccelByteFriends* GetFriendsMenu() const {return FriendsMenu; }

    /**
    * @brief Getter for Find Friends Menu widget
    */
    UAccelByteFindFriend* GetFindFriendsMenu() const {return FindFriendsMenu; }
  4. Create new functions to open the Main Menu, Friends Menu, and Find Friends Menu widgets.

    void ATutorialMenuHUD::OpenMainMenu()
    {
    MainMenu->AddToViewport();
    }

    void ATutorialMenuHUD::OpenFriendsMenu()
    {
    if (!FriendsMenu->IsInViewport())
    {
    FriendsMenu->AddToViewport();
    }
    else
    {
    FriendsMenu->SetVisibility(ESlateVisibility::SelfHitTestInvisible);
    }
    }

    void ATutorialMenuHUD::OpenFindFriendsMenu()
    {
    FindFriendsMenu->AddToViewport();
    }
  5. Create other functions to close the Main Menu, Friends Menu, and Find Friends Menu widgets.

    void ATutorialMenuHUD::CloseMainMenu()
    {
    LoginMenu->AddToViewport();
    MainMenu->RemoveFromViewport();
    LobbyMenu->RemoveFromParent();
    }

    void ATutorialMenuHUD::CloseFriendMenu()
    {
    if (!LobbyMenu->IsInViewport())
    {
    LobbyMenu->AddToViewport();
    }
    FriendsMenu->RemoveFromParent();
    }

    void ATutorialMenuHUD::CloseFindFriendsMenu()
    {
    FindFriendsMenu->RemoveFromParent();
    }

  6. While still in the TutorialMenuHUD class, create a function to initialize the Main Menu widget state. Call the ConnectToLobby() function from this function.

    void ATutorialMenuHUD::InitMainMenu()
    {
    // Add Lobby to Viewport and set its visibility as collapsed
    LobbyMenu->AddToViewport();
    LobbyMenu->ConnectToLobby();
    LobbyMenu->SetVisibility(ESlateVisibility::Collapsed);

    OpenMainMenu();
    }
  7. Now that you have the Main Menu, modify the OpenLobbyMenu and CloseLobbyMenu functions as in the example below:

    void ATutorialMenuHUD::OpenLobbyMenu()
    {
    ...
    MainMenu->RemoveFromParent();
    }

    void ATutorialMenuHUD::CloseLobbyMenu()
    {
    MainMenu->AddToViewport();
    ...
    }
  1. Open the AccelByteFriendEntry class and add the following AccelByte and widget classes to the top of the class:

    ...
    #include "Components/Button.h"
    #include "Components/TextBlock.h"
    #include "Components/WidgetSwitcher.h"
    #include "Components/Image.h"
    #include "Components/ScaleBox.h"
    #include "Components/HorizontalBox.h"
    #include "TutorialProject/TutorialMenuHUD.h"

    This Friend Entry widget will be used for displaying any friends-related lists. The layout inside may be slightly different depending on the required info for the list.

  2. To make it easier to differentiate, create an enum to identify the desired mode list.

    /**
    * Enumerator for Friend Entry widget modes.
    */
    UENUM()
    enum class EFriendEntryMode : uint8
    {
    Friend UMETA(DisplayName = "Friend"),
    Incoming UMETA(DisplayName = "Incoming Request"),
    Outgoing UMETA(DisplayName = "Outgoing Request"),
    Search UMETA(DisplayName = "Search")
    };
  3. Based on Friend Entry's mode, specify all the required widgets in the header file. The Default widgets and entry boxes for each entry mode are as follows:

    /**
    * @brief Text Box for Friend Name (Will be replaced with friend name).
    */
    UPROPERTY(meta = (BindWidget))
    UTextBlock* Tb_FriendName;
    /**
    * @brief Text Box for Friend Status (i.e., Online, Offline, Away).
    */
    UPROPERTY(meta = (BindWidget))
    UTextBlock* Tb_Status;
    /**
    * @brief Widget Switcher to switch Entry Mode.
    */
    UPROPERTY(meta = (BindWidget))
    UWidgetSwitcher* Ws_EntryMode;
    /**
    * @brief Scale Box for User Image.
    */
    UPROPERTY(meta = (BindWidget))
    UScaleBox* Sb_Friend;

    // Horizontal Boxes for Widget Switcher.
    /**
    * @brief Horizontal Box for Friend component.
    */
    UPROPERTY(meta = (BindWidget))
    UHorizontalBox* Hb_Friend;
    /**
    * @brief Horizontal Box for Incoming component.
    */
    UPROPERTY(meta = (BindWidget))
    UHorizontalBox* Hb_IncomingRequest;
    /**
    * @brief Horizontal Box for Outgoing component.
    */
    UPROPERTY(meta = (BindWidget))
    UHorizontalBox* Hb_OutgoingRequest;
    /**
    * @brief Horizontal Box for Search component.
    */
    UPROPERTY(meta = (BindWidget))
    UHorizontalBox* Hb_Search;
    • Friend mode

      A number of buttons (except the Unfriend button) are initialized here, but not explained in this sectionl. See the Full Code section for details.

    // Friend Entry Type
    /**
    * @brief Button for Chat Friend.
    */
    UPROPERTY(meta = (BindWidget))
    UButton* Btn_Chat;
    /**
    * @brief Button for Invite Friend to Party.
    */
    UPROPERTY(meta = (BindWidget))
    UButton* Btn_InviteParty;
    /**
    * @brief Button for Unfriend.
    */
    UPROPERTY(meta = (BindWidget))
    UButton* Btn_Unfriend;
    /**
    * @brief Button for Block Friend.
    */
    UPROPERTY(meta = (BindWidget))
    UButton* Btn_Block;
    • Incoming (Friend Request) mode

    .h

    // Incoming Entry Type
    /**
    * @brief Button for Accepting Friend Request.
    */
    UPROPERTY(meta = (BindWidget))
    UButton* Btn_Accept;
    /**
    * @brief Button for Decline Friend Request.
    */
    UPROPERTY(meta = (BindWidget))
    UButton* Btn_Decline;
    /**
    * @brief Button for Block User by Incoming Request.
    */
    UPROPERTY(meta = (BindWidget))
    UButton* Btn_Block_Request;
    • Outgoing (Friend Request) Mode

    .h

    // Outgoing Entry Type 
    /**
    * @brief Button for canceling outgoing friend request.
    */
    UPROPERTY(meta = (BindWidget))
    UButton* Btn_CancelRequest;
    • Search (Find Friend) Mode

    .h

    // Search Entry Type
    /**
    * @brief Button for Add Friend.
    */
    UPROPERTY(meta = (BindWidget))
    UButton* Btn_AddFriend;
  4. Create a function that will initialize the Friend Entry data when the widget is created. Since this widget will be based on query results that contain UserId, use the SDK's GetUserByUserId() function to get the display name data.

    .cpp

    void UAccelByteFriendEntry::InitData(const EFriendEntryMode& EntryMode, const FString& UserId)
    {
    TutorialMenuHUD = Cast<ATutorialMenuHUD>(GetOwningPlayer()->GetHUD());
    check(TutorialMenuHUD);

    FRegistry::User.GetUserByUserId(
    UserId,
    THandler<FSimpleUserData>::CreateUObject(this, &UAccelByteFriendEntry::OnSuccessGetUserId),
    FErrorHandler::CreateUObject(this, &UAccelByteFriendEntry::OnFailedGetUserId));
    }

    .h

    /**
    * @brief Initialize entry data by getting User Data by User ID and selecting its entry Mode
    * @param EntryMode Select entry mode based on the EFriendEntryMode enum
    * @param UserId User ID needed to get User data.
    */
    void InitData(const EFriendEntryMode& EntryMode, const FString& UserId);

    /**
    * @brief Callback for getting the user ID.
    */
    void OnSuccessGetUserId(const FSimpleUserData& Data);

    /**
    * @brief Callback for getting user ID failure.
    */
    void OnFailedGetUserId(int32 ErrorCode, const FString& ErrorMessage);

    UPROPERTY()
    ATutorialMenuHUD* TutorialMenuHUD;
  5. Define both of the GetUser response delegate functions you just declared to store UserData and update the Display Name's text widget if the query succeeds.

    .cpp

    void UAccelByteFriendEntry::OnSuccessGetUserId(const FSimpleUserData& Data)
    {
    UE_LOG(LogTemp, Log, TEXT("Success Get User By User ID!"));

    UserData = Data;
    Tb_FriendName->SetText(FText::FromString(UserData.DisplayName));
    }

    void UAccelByteFriendEntry::OnFailedGetUserId(int32 ErrorCode, const FString& ErrorMessage)
    {
    UE_LOG(LogTemp, Error, TEXT(" Get User by Id Failed : Code: %d, Reason: %s"), ErrorCode, *ErrorMessage);
    }

    .h

    /**
    * @brief Models to store User Data contained in this entry.
    */
    FSimpleUserData UserData;
  6. Once you have completed the previous steps, you will need to create some functions that will handle the on-click button events for the buttons you created earlier.

    note

    For Friend Mode, leave the Chat, Invite Party, and Block button functions empty since they are related to other services.

    .cpp

    void UAccelByteFriendEntry::OnClickedChat()
    {
    // @TODO: will be covered in another service guide
    }

    void UAccelByteFriendEntry::OnClickedInviteParty()
    {
    // @TODO: will be covered in another service guide
    }

    void UAccelByteFriendEntry::OnClickedBlock()
    {
    // @TODO: will be covered in another service guide
    }

    .h

    // Functionality for Chat Button.
    UFUNCTION()
    void OnClickedChat();

    // Functionality for Invite Party Button.
    UFUNCTION()
    void OnClickedInviteParty();

    // Functionality for Block Friend Button.
    UFUNCTION()
    void OnClickedBlock();
  7. For the Unfriend button, modify the OnClickedUnfriend() function to get the desired UserId from the local UserData variable you made earlier and to remove the entry upon a successful request.

    .cpp

    void UAccelByteFriendEntry::OnClickedUnfriend()
    {
    FRegistry::Lobby.SetUnfriendResponseDelegate(Api::Lobby::FUnfriendResponse::CreateWeakLambda(this, [this](const FAccelByteModelsUnfriendResponse& Result)
    {
    ...
    this->RemoveFromParent();
    }),
    ...

    FRegistry::Lobby.Unfriend(UserData.UserId);
    }
  8. For the Incoming Mode button's functionality, modify the OnClickAccept() and OnClickDecline() functions. You can use CreateWeakLambda and define the result from the response delegate.

  9. For the Block button, use the same Block function you made earlier for Friend Mode.

    .cpp

    void UAccelByteFriendEntry::OnClickedAccept()
    {
    FRegistry::Lobby.SetAcceptFriendsResponseDelegate(
    Api::Lobby::FAcceptFriendsResponse::CreateWeakLambda(this, [this](
    FAccelByteModelsAcceptFriendsResponse Result)
    {
    this->RemoveFromParent();
    ...
    }),
    ...

    FRegistry::Lobby.AcceptFriend(UserData.UserId);
    }

    void UAccelByteFriendEntry::OnClickedDecline()
    {
    FRegistry::Lobby.SetRejectFriendsResponseDelegate(
    Api::Lobby::FRejectFriendsResponse::CreateWeakLambda(this, [this](
    FAccelByteModelsRejectFriendsResponse Result)
    {
    this->RemoveFromParent();
    ...
    }),
    ...

    FRegistry::Lobby.RejectFriend(UserData.UserId);
    }

    void UAccelByteFriendEntry::OnClickedCancelRequest()
    {
    FRegistry::Lobby.CancelFriendRequest(UserData.UserId);
    this->RemoveFromParent();
    }
  10. For Search Mode (that only has the Add Friend button), remove the SDK's RequestFriends functionality in the AccelByteFindFriend class (in the FindFriend() function).

  11. Next, create a new dedicated function.

    .cpp

    void UAccelByteFriendEntry::OnClickedAddFriend()
    {
    FRegistry::Lobby.SetRequestFriendsResponseDelegate(
    Api::Lobby::FRequestFriendsResponse::CreateWeakLambda(this, [](
    FAccelByteModelsRequestFriendsResponse Result)
    {
    UE_LOG(LogTemp, Log, TEXT("Successfully sent a friend request!"));
    }),
    FErrorHandler::CreateWeakLambda(this, [](const int32 Code, const FString& Message)
    {
    UE_LOG(LogTemp, Error, TEXT("Failed to send a friend request! Code : %i , Message : %s"), Code, *Message);
    }));

    FRegistry::Lobby.RequestFriend(UserData.UserId);
    Btn_AddFriend->SetIsEnabled(false);
    }

    .h

    /**
    * @brief Functionality for Add Friend Button.
    */
    UFUNCTION()
    void OnClickedAddFriend();
  12. Now that you have prepared the functions for each button's widgets, create a new function to set the Friend Entry widget based on the entry mode's enum.

    .cpp

    void UAccelByteFriendEntry::SetEntryMode(const EFriendEntryMode& EntryMode) const
    {
    switch(EntryMode)
    {
    case EFriendEntryMode::Friend:
    Sb_Friend->SetVisibility(ESlateVisibility::SelfHitTestInvisible);
    Tb_Status->SetVisibility(ESlateVisibility::SelfHitTestInvisible);
    Ws_EntryMode->SetActiveWidget(Hb_Friend);

    Btn_Chat->OnClicked.AddUniqueDynamic(this, &UAccelByteFriendEntry::OnClickedChat);
    Btn_InviteParty->OnClicked.AddUniqueDynamic(this, &UAccelByteFriendEntry::OnClickedInviteParty);
    Btn_Unfriend->OnClicked.AddUniqueDynamic(this, &UAccelByteFriendEntry::OnClickedUnfriend);
    Btn_Block->OnClicked.AddUniqueDynamic(this, &UAccelByteFriendEntry::OnClickedBlock);
    return;

    case EFriendEntryMode::Incoming:
    Sb_Friend->SetVisibility(ESlateVisibility::SelfHitTestInvisible);
    Tb_Status->SetVisibility(ESlateVisibility::Collapsed);
    Ws_EntryMode->SetActiveWidget(Hb_IncomingRequest);

    Btn_Accept->OnClicked.AddUniqueDynamic(this, &UAccelByteFriendEntry::OnClickedAccept);
    Btn_Decline->OnClicked.AddUniqueDynamic(this, &UAccelByteFriendEntry::OnClickedDecline);
    Btn_Block_Request->OnClicked.AddUniqueDynamic(this, &UAccelByteFriendEntry::OnClickedBlock);
    return;

    case EFriendEntryMode::Outgoing:
    Sb_Friend->SetVisibility(ESlateVisibility::SelfHitTestInvisible);
    Tb_Status->SetVisibility(ESlateVisibility::Collapsed);
    Ws_EntryMode->SetActiveWidget(Hb_OutgoingRequest);

    Btn_CancelRequest->OnClicked.AddUniqueDynamic(this, &UAccelByteFriendEntry::OnClickedCancelRequest);
    return;

    case EFriendEntryMode::Search:
    Sb_Friend->SetVisibility(ESlateVisibility::Collapsed);
    Tb_Status->SetVisibility(ESlateVisibility::Collapsed);
    Ws_EntryMode->SetActiveWidget(Hb_Search);

    Btn_AddFriend->OnClicked.AddUniqueDynamic(this, &UAccelByteFriendEntry::OnClickedAddFriend);
    return;

    default:
    Sb_Friend->SetVisibility(ESlateVisibility::SelfHitTestInvisible);
    Tb_Status->SetVisibility(ESlateVisibility::Hidden);
    Ws_EntryMode->SetActiveWidget(Hb_Friend);

    Btn_Chat->OnClicked.AddUniqueDynamic(this, &UAccelByteFriendEntry::OnClickedChat);
    Btn_InviteParty->OnClicked.AddUniqueDynamic(this, &UAccelByteFriendEntry::OnClickedInviteParty);
    Btn_Unfriend->OnClicked.AddUniqueDynamic(this, &UAccelByteFriendEntry::OnClickedUnfriend);
    Btn_Block->OnClicked.AddUniqueDynamic(this, &UAccelByteFriendEntry::OnClickedBlock);
    }
    }

    .h

    /**
    * @brief Set Mode settings for selected entry enumerator.
    */
    void SetEntryMode(const EFriendEntryMode& EntryMode) const;
  13. Since you need this functionality when the Friend Entry widget is initialized, call SetEntryMode() in the InitData() function.

    .cpp

    void UAccelByteFriendEntry::InitData(const EFriendEntryMode& EntryMode, const FString& UserId)
    {
    ...
    SetEntryMode(EntryMode);
    }

Find Friends

  1. Open the AccelByteFindFriend class and add the following widget-related classes to the top of the class:

    .cpp

    ...
    #include "AccelByteFriends.h"
    #include "Components/Button.h"
    #include "Components/EditableTextBox.h"
    #include "Components/ScrollBox.h"
    #include "TutorialProject/TutorialMenuHUD.h"

    .h

    ...
    #include "AccelByteFriendEntry.h"

    ...
    class UEditableTextBox;
    class UButton;
    class UScrollBox;
    class ATutorialMenuHUD;
  2. Specify the Find Friends widget components in the header file.

    .h

    /**
    * @brief Editable Text Box for Find Friend.
    */
    UPROPERTY(meta = (BindWidget))
    UEditableTextBox* Etb_FindFriend;

    /**
    * @brief Button to start searching Friend.
    */
    UPROPERTY(meta = (BindWidget))
    UButton* Btn_FindFriend;

    /**
    * @brief Button to close Find Friend Widget.
    */
    UPROPERTY(meta = (BindWidget))
    UButton* Btn_Close;

    /**
    * @brief Scrollable Box for Friends List result.
    */
    UPROPERTY(meta = (BindWidget))
    UScrollBox* Sb_FriendList;
  3. You can reuse the Friend Entry widget to display player info based on the SearchUser() query result. To do this, declare the AccelByteFriendEntry class in the AccelByteFindFriend header file and prepare a new array to hold all the player information for players that are either friends or are listed in the Outgoing Friend Request list. This value will be set later.

    .h

    /**
    * @brief Widget to spawn for friend Entry.
    */
    UPROPERTY(EditDefaultsOnly)
    TSubclassOf<UAccelByteFriendEntry> FriendEntryClass;

    /**
    * @brief Container for all friend names copied from outgoing friend list.
    */
    UPROPERTY()
    TArray<FString> FriendSentRequestArray;
  4. Compile the entire project and then assign WB_FriendEntry as the reference for FriendEntryClass in the Unreal editor.

  5. In the FindFriends() function, change CreateWeakLambda() to CreateUObject() for the SearchUsers() query response.

    .cpp

    void UAccelByteFindFriend::FindFriends(const FString& FriendName)
    {
    FRegistry::User.SearchUsers(
    FriendName,
    THandler<FPagedPublicUsersInfo>::CreateUObject(this, &UAccelByteFindFriend::OnSuccessFindFriends),
    FErrorHandler::CreateUObject(this, &UAccelByteFindFriend::OnFailedFindFriends)
    );
    }

    .h

    /**
    * @brief Callback for Success Find Friends.
    */
    void OnSuccessFindFriends(const FPagedPublicUsersInfo& Result);

    /**
    * @brief Callback for Failing Find Friends.
    */
    void OnFailedFindFriends(int32 ErrorCode, const FString& ErrorMessage);
  6. Define both of the response delegate functions you just declared to update the players list widget based on the query result.

    .cpp

    void UAccelByteFindFriend::OnSuccessFindFriends(const FPagedPublicUsersInfo& Result)
    {
    UE_LOG(LogTemp, Log, TEXT("Success Find Friends"));

    Sb_FriendList->ClearChildren();

    for (const FPublicUserInfo& UserData : Result.Data)
    {
    if (UserData.UserId != FRegistry::Credentials.GetUserId())
    {
    const TWeakObjectPtr<UAccelByteFriendEntry> SearchFriendEntry = MakeWeakObjectPtr<UAccelByteFriendEntry>(
    CreateWidget<UAccelByteFriendEntry>(this, FriendEntryClass.Get())
    );

    SearchFriendEntry->InitData(EFriendEntryMode::Search, UserData.UserId);

    for (const FString& FriendSentRequest : FriendSentRequestArray)
    {
    if (UserData.UserId == FriendSentRequest)
    {
    SearchFriendEntry->EnableAddFriendButton(false);
    }
    }

    Sb_FriendList->AddChild(SearchFriendEntry.Get());
    }
    }
    }

    void UAccelByteFindFriend::OnFailedFindFriends(int32 ErrorCode, const FString& ErrorMessage)
    {
    UE_LOG(LogTemp, Error, TEXT("Failed Find Friends : %d , %s"), ErrorCode, *ErrorMessage);
    }
  7. To filter the results of Search User so that players won't be able to add those that have already been added as a friend or to whom they have already sent a friend request:

    • Create a new function.
    • Use the SDK's LoadFriendList and ListOutgoingFriends functions to get player lists.
    • Inside both functions, store the result data in FriendSentRequestArray (which you used to filter the Find Friend feature's result list).

    .cpp

    void UAccelByteFindFriend::FilterSearchEntryList()
    {
    FriendSentRequestArray.Empty();

    FRegistry::Lobby.SetLoadFriendListResponseDelegate(
    Api::Lobby::FLoadFriendListResponse::CreateWeakLambda(this, [this](
    FAccelByteModelsLoadFriendListResponse Result)
    {
    FriendSentRequestArray = Result.friendsId;
    FRegistry::Lobby.ListOutgoingFriends();

    UE_LOG(LogTemp, Log, TEXT("On Load Friend List Success!"));
    }),
    FErrorHandler::CreateWeakLambda(this, [](const int32 Code, const FString& Message)
    {
    UE_LOG(LogTemp, Error, TEXT("Unable to retrieve friends list! Code : %i , Message : %s"), Code, *Message);
    }));

    FRegistry::Lobby.SetListOutgoingFriendsResponseDelegate(
    Api::Lobby::FListOutgoingFriendsResponse::CreateWeakLambda(this, [this](
    FAccelByteModelsListOutgoingFriendsResponse Result)
    {
    FriendSentRequestArray.Append(Result.friendsId);
    FindUsers(Etb_FindFriend->GetText().ToString());

    UE_LOG(LogTemp, Log, TEXT("On List Outgoing Friends Success"));
    }),
    FErrorHandler::CreateWeakLambda(this, [](const int32 Code, const FString& Message)
    {
    UE_LOG(LogTemp, Error, TEXT("Unable to retrieve the list of outgoing/pending friend request! Code : %i , Message : %s"), Code, *Message);
    }));

    FRegistry::Lobby.LoadFriendsList();
    }

    .h

    /**
    * @brief Filter search entry to disable the add button for users who are already in their friends list and outgoing requests list.
    */
    void FilterSearchEntryList();
  8. Create two functions to be called when the Find Friend and Close buttons are clicked.

    .cpp

    void UAccelByteFindFriend::OnClickFindFriends()
    {
    Sb_FriendList->ClearChildren();

    FilterSearchEntryList();
    }

    void UAccelByteFindFriend::OnClickCloseFindFriendWidget()
    {
    TutorialMenuHUD->CloseFindFriendsMenu();
    }

    .h

    /**
    * @brief Find Friends button.
    */
    UFUNCTION()
    void OnClickFindFriends();

    /**
    * @brief Close Find Friend Widget.
    */
    UFUNCTION()
    void OnClickCloseFindFriendWidget();

    /**
    * @brief Tutorial Menu HUD pointer reference.
    */
    UPROPERTY()
    ATutorialMenuHUD* TutorialMenuHUD;
  9. Set up the widgets by adding the NativeConstruct() override function and initialize the TutorialMenuHUD pointer reference.

    .cpp

    void UAccelByteFindFriend::NativeConstruct()
    {
    Super::NativeConstruct();

    check (GetOwningPlayer() != nullptr);
    check (GetOwningPlayer()->GetHUD() != nullptr);

    TutorialMenuHUD = Cast<ATutorialMenuHUD>(GetOwningPlayer()->GetHUD());

    Btn_FindFriend->OnClicked.AddUniqueDynamic(this, &UAccelByteFindFriend::OnClickFindFriends);
    Btn_Close->OnClicked.AddUniqueDynamic(this, &UAccelByteFindFriend::OnClickCloseFindFriendWidget);
    }

    .h

    ...
    virtual void NativeConstruct() override;

Friends Menu

  1. Open the AccelByteFriends class and include the following widget classes at the top:

    .cpp

    ...
    #include "Components/Button.h"
    #include "Components/ScrollBox.h"
    #include "Components/TextBlock.h"
    #include "TutorialProject/TutorialMenuHUD.h"

    .h

    ...
    #include "AccelByteFriendEntry.h"
    #include "Models/AccelByteLobbyModels.h"
    #include "TutorialProject/TutorialMenuHUD.h"
    ...
    class UScrollBox;
    class UButton;
    class UTextBlock;
    ...
  2. Create an enum to determine the current Friends Menu's tab state.

    .h

    /**
    * Enumerator to determine the current state
    */
    UENUM()
    enum class ECurrentStateFriend: uint8
    {
    FRIEND_LIST UMETA(DisplayName = "Friends List"),
    INCOMING_LIST UMETA(DisplayName = "Incoming Requests List"),
    OUTGOING_LIST UMETA(DisplayName = "Outgoing Requests List")
    };
  3. Specify all of the Friends Menu widget components in the header file.

    .h

    /**
    * @brief Button for opening Find Friend Menu pop up.
    */
    UPROPERTY(meta = (BindWidget))
    UButton* Btn_FindFriend;

    /**
    * @brief Button for Friend List displaying.
    */
    UPROPERTY(meta = (BindWidget))
    UButton* Btn_FriendList;

    /**
    * @brief Button for Pending Incoming Friend List.
    */
    UPROPERTY(meta = (BindWidget))
    UButton* Btn_PendingIncomingList;

    /**
    * @brief Button for Pending Outgoing Friend List.
    */
    UPROPERTY(meta = (BindWidget))
    UButton* Btn_PendingSentList;

    /**
    * @brief Button for Back to Main Menu.
    */
    UPROPERTY(meta = (BindWidget))
    UButton* Btn_Back;

    /**
    * @brief Scroll Box for content display of four functionality (FriendList, Incoming, Outgoing, Block).
    */
    UPROPERTY(meta = (BindWidget))
    UScrollBox* Sb_ContentList;

    /**
    * @brief Text Block to show how many player friends are currently online.
    */
    UPROPERTY(meta = (BindWidget))
    UTextBlock* T_FriendsOnline;
  4. Since you are mainly going to display Friends-related lists, initialize the AccelByteFriendEntry class and create a new function to make it easier to create the Friend Entry widget.

  5. When you have done that, create a local array to hold all of the Friend Entry widgets that have been created.

    .cpp

    void UAccelByteFriends::CreateEntryWidget(const EFriendEntryMode& EntryMode, const FString& FriendId)
    {
    const TWeakObjectPtr<UAccelByteFriendEntry> FriendEntryWidget = MakeWeakObjectPtr<UAccelByteFriendEntry>(CreateWidget<UAccelByteFriendEntry>(this, FriendEntryClass.Get()));

    FriendListWidgets.Add(FriendEntryWidget);

    FriendEntryWidget->InitData(EntryMode, FriendId);

    Sb_ContentList->AddChild(FriendEntryWidget.Get());
    }

    .h

    /**
    * @brief Creates Friend Entry Widget through weak object pointer.
    * @param EntryMode Entry mode for the friend widget.
    * @param FriendId Targeted Friend ID for the entry.
    */
    void CreateEntryWidget(const EFriendEntryMode& EntryMode, const FString& FriendId);

    /**
    * @brief Reference to Friend Entry Class.
    */
    UPROPERTY(EditDefaultsOnly)
    TSubclassOf<UAccelByteFriendEntry> FriendEntryClass;

    /**
    * @brief Array for Entry Widgets.
    */
    TArray<TWeakObjectPtr<UAccelByteFriendEntry>> FriendListWidgets;
  6. To prepare the Friends Menu's button functionality, start by creating a new function for the Find Friend button. This will open the Find Friends Menu widget via TutorialMenuHUD.

    .cpp

    void UAccelByteFriends::OnClickedFindFriend()
    {
    TutorialMenuHUD->OpenFindFriendsMenu();
    }

    .h

    /**
    * @brief Callback for Find Friend.
    */
    UFUNCTION()
    void OnClickedFindFriend();

    /**
    * @brief Tutorial Menu HUD pointer reference.
    */
    UPROPERTY()
    ATutorialMenuHUD* TutorialMenuHUD;
  7. Create a new function to go back to the Lobby page and bind it to the Back to Lobby button.

    .cpp

    void UAccelByteFriends::OnClickedBackToLobby()
    {
    TutorialMenuHUD->CloseFriendMenu();
    }

    .h

    /**
    * @brief Callback for Back button to lobby.
    */
    UFUNCTION()
    void OnClickedBackToLobby();
  8. To make it easier to refresh the Friends List, create a new function in the AccelByteFriends class.

    .cpp

    void UAccelByteFriends::RefreshFriendsList()
    {
    switch (CurrentState)
    {
    case ECurrentStateFriend::FRIEND_LIST:
    {
    LoadFriendList();
    break;
    }
    case ECurrentStateFriend::INCOMING_LIST:
    {
    LoadPendingIncomingList();
    break;
    }
    case ECurrentStateFriend::OUTGOING_LIST:
    {
    LoadPendingOutgoingList();
    break;
    }
    }
    }

    .h

    /**
    * @brief Refresh friends list when open the friends menu
    */
    void RefreshFriendsList();

    /**
    * @brief Current state of the menu.
    */
    ECurrentStateFriend CurrentState;
  9. Create a new function to handle the widgets on the Friend List's tab button.

    .cpp

    void UAccelByteFriends::SwitchActiveButton(UButton* CurrentButton)
    {
    if (!CurrentActiveButton) CurrentActiveButton = CurrentButton;

    CurrentActiveButton->SetBackgroundColor(FLinearColor::White);
    CurrentActiveButton = CurrentButton;

    FLinearColor SelectedTabGreyColor = FLinearColor(0.2f, 0.2f, 0.2f, 1.0f);
    CurrentActiveButton->SetBackgroundColor(SelectedTabGreyColor);
    }

    .h

    /**
    * @brief Switch the active button to the current button that player clicked before
    * @param CurrentButton Current target active button
    */
    void SwitchActiveButton(UButton* CurrentButton);

    /**
    * @brief Instantiate current active button
    */
    UPROPERTY()
    UButton* CurrentActiveButton;
  10. Start modifying the current Friends functionality, beginning with the OnClickedFriendList() function.

  11. Call the CreateEntryWidget() function to display the Friends List information.

  12. When you have finished, Once completed, store the current Friends tab type in a local enum variable to make it easier to create the correct Friend Entry widget.

    .cpp

    void UAccelByteFriends::LoadFriendList()
    {
    SwitchActiveButton(Btn_FriendList);

    Sb_ContentList->ClearChildren();
    CurrentState = ECurrentStateFriend::FRIEND_LIST;

    FRegistry::Lobby.SetLoadFriendListResponseDelegate(
    Api::Lobby::FLoadFriendListResponse::CreateWeakLambda(this, [this](const FAccelByteModelsLoadFriendListResponse& Result)
    {
    Sb_ContentList->ClearChildren();
    FriendListWidgets.Empty();

    for (const FString& FriendId : Result.friendsId)
    {
    CreateEntryWidget(EFriendEntryMode::Friend, FriendId);
    }

    UE_LOG(LogTemp, Log, TEXT("Success Retrieve Load Friend List Response!"));
    }),
    FErrorHandler::CreateWeakLambda(this, [](int32 ErrorCode, const FString& ErrorMessage)
    {
    UE_LOG(LogTemp, Error, TEXT("Error LoadFriendListResponseDelegate, Error Code: %d Error Message: %s"), ErrorCode, *ErrorMessage);
    }));

    FRegistry::Lobby.LoadFriendsList();
    }

    .h

    /**
    * @brief Current state of the menu.
    */
    ECurrentStateFriend CurrentState;
  13. Modify both the OnClickedPendingIncomingList() and the OnClickedPendingOutgoingList() functions by adding functionality that will create the entry widget for each of the players listed, based on the query result.

    .cpp

    void UAccelByteFriends::LoadPendingIncomingList()
    {
    SwitchActiveButton(Btn_PendingIncomingList);

    Sb_ContentList->ClearChildren();
    CurrentState = ECurrentStateFriend::INCOMING_LIST;

    FRegistry::Lobby.SetListIncomingFriendsResponseDelegate(
    Api::Lobby::FListIncomingFriendsResponse::CreateWeakLambda(this, [this](
    FAccelByteModelsListIncomingFriendsResponse Result)
    {
    Sb_ContentList->ClearChildren();
    FriendListWidgets.Empty();

    for (const FString& FriendId : Result.friendsId)
    {
    CreateEntryWidget(EFriendEntryMode::Incoming, FriendId);
    }
    UE_LOG(LogTemp, Log, TEXT("Success Retrieve List Incoming Friend Response!"));
    }),
    FErrorHandler::CreateWeakLambda(this, [](const int32 Code, const FString& Message)
    {
    UE_LOG(LogTemp, Error, TEXT("Unable to retrieve the list of incoming friend requests! Code : %i , Message : %s"), Code, *Message);
    }));

    FRegistry::Lobby.ListIncomingFriends();
    }

    void UAccelByteFriends::LoadPendingOutgoingList()
    {
    SwitchActiveButton(Btn_PendingSentList);

    Sb_ContentList->ClearChildren();
    CurrentState = ECurrentStateFriend::OUTGOING_LIST;

    FRegistry::Lobby.SetListOutgoingFriendsResponseDelegate(
    Api::Lobby::FListOutgoingFriendsResponse::CreateWeakLambda(this, [this](
    FAccelByteModelsListOutgoingFriendsResponse Result)
    {
    Sb_ContentList->ClearChildren();
    FriendListWidgets.Empty();

    for (const FString& FriendId : Result.friendsId)
    {
    CreateEntryWidget(EFriendEntryMode::Outgoing, FriendId);
    }

    UE_LOG(LogTemp, Log, TEXT("Success Retrieve List Outgoing Friend Response!"));
    }),
    FErrorHandler::CreateWeakLambda(this, [](const int32 Code, const FString& Message)
    {
    UE_LOG(LogTemp, Error, TEXT("Unable to retrieve the list of outgoing friend requests! Code : %i , Message : %s"), Code, *Message);
    }));

    FRegistry::Lobby.ListOutgoingFriends();
    }
  14. Now that you have prepared all the functions needed for the buttons:

    1. Add the NativeConstruct() override function.
    2. Initialize the TutorialMenuHUD pointer.
    3. Set up all of the Friend Menu buttons with the Friends Notif delegate.

    .cpp

    void UAccelByteFriends::NativeConstruct()
    {
    Super::NativeConstruct();

    check (GetOwningPlayer() != nullptr);
    check (GetOwningPlayer()->GetHUD() != nullptr);
    TutorialMenuHUD = Cast<ATutorialMenuHUD>(GetOwningPlayer()->GetHUD());

    Btn_FindFriend->OnClicked.AddUniqueDynamic(this, &UAccelByteFriends::OnClickedFindFriend);
    Btn_FriendList->OnClicked.AddUniqueDynamic(this, &UAccelByteFriends::OnClickedFriendList);
    Btn_PendingIncomingList->OnClicked.AddUniqueDynamic(this, &UAccelByteFriends::OnClickedPendingIncomingList);
    Btn_PendingSentList->OnClicked.AddUniqueDynamic(this, &UAccelByteFriends::OnClickedPendingOutgoingList);
    Btn_Back->OnClicked.AddUniqueDynamic(this, &UAccelByteFriends::OnClickedBackToLobby);

    SetNotificationDelegate();
    CurrentState = ECurrentStateFriend::FRIEND_LIST;
    RefreshFriendsList();
    }

    .h

    ...
    virtual void NativeConstruct() override;
    ...
  15. Modify the SetNotificationDelegate() delegate to check CurrentState and display its corresponding players, such as in the following example:

    .cpp

    void UAccelByteFriends::SetNotificationDelegate()
    {
    FRegistry::Lobby.SetOnFriendRequestAcceptedNotifDelegate(
    Api::Lobby::FAcceptFriendsNotif::CreateWeakLambda(this, [this](const FAccelByteModelsAcceptFriendsNotif& Result)
    {
    if (CurrentState == ECurrentStateFriend::FRIEND_LIST)
    {
    CreateEntryWidget(EFriendEntryMode::Friend, Result.friendId);
    }
    else if (CurrentState == ECurrentStateFriend::OUTGOING_LIST)
    {
    FRegistry::Lobby.ListOutgoingFriends();
    }
    }));

    FRegistry::Lobby.SetOnUnfriendNotifDelegate(
    Api::Lobby::FUnfriendNotif::CreateWeakLambda(this, [this](const FAccelByteModelsUnfriendNotif& Result)
    {
    if (CurrentState == ECurrentStateFriend::FRIEND_LIST)
    {
    FRegistry::Lobby.LoadFriendsList();
    }
    }));

    FRegistry::Lobby.SetOnCancelFriendsNotifDelegate(
    Api::Lobby::FCancelFriendsNotif::CreateWeakLambda(this, [this](const FAccelByteModelsCancelFriendsNotif& Result)
    {
    if (CurrentState == ECurrentStateFriend::INCOMING_LIST)
    {
    FRegistry::Lobby.ListIncomingFriends();
    }
    }));

    FRegistry::Lobby.SetOnRejectFriendsNotifDelegate(
    Api::Lobby::FRejectFriendsNotif::CreateWeakLambda(this, [this](const FAccelByteModelsRejectFriendsNotif& Result)
    {
    if (CurrentState == ECurrentStateFriend::OUTGOING_LIST)
    {
    FRegistry::Lobby.ListOutgoingFriends();
    }
    }));

    FRegistry::Lobby.SetOnIncomingRequestFriendsNotifDelegate(
    Api::Lobby::FRequestFriendsNotif::CreateWeakLambda(this, [this](const FAccelByteModelsRequestFriendsNotif& Result)
    {
    if (CurrentState == ECurrentStateFriend::INCOMING_LIST)
    {
    FRegistry::Lobby.ListIncomingFriends();
    }
    }));
    }
  16. Open the TutorialMenuHUD class and call the Friends Menu's RefreshFriendsList() function inside the else condition of the OpenFriendsMenu() function.

    .cpp

    void ATutorialMenuHUD::OpenFriendsMenu()
    {
    if (!FriendsMenu->IsInViewport())
    ...
    else
    {
    ...
    FriendsMenu->RefreshFriendsList();
    }
    }
  1. Open the AccelByteLobby class and include the following header:

    .cpp

    ...
    #include "Components/Button.h"
    #include "TutorialProject/TutorialMenuHUD.h"
    ...

    .h

    ...
    class ATutorialMenuHUD;
    class UButton;
    ...
  2. Initialize the TutorialMenuHUD class in its NativeConstruct() function and set up the Leave Lobby button.

    .cpp

    void UAccelByteLobby::NativeConstruct()
    {
    Super::NativeConstruct();

    check(GetOwningPlayer() != nullptr);
    check(GetOwningPlayer()->GetHUD() != nullptr);

    TutorialMenuHUD = Cast<ATutorialMenuHUD>(GetOwningPlayer()->GetHUD());

    Btn_LeaveLobby->OnClicked.AddUniqueDynamic(TutorialMenuHUD, &ATutorialMenuHUD::CloseLobbyMenu);
    }

    .h

    virtual void NativeConstruct() override;

    /**
    * @brief Button for Leave Lobby and back to Main Menu.
    */
    UPROPERTY(meta = (BindWidget))
    UButton* Btn_LeaveLobby;

    /**
    * @brief Tutorial Menu HUD pointer reference.
    */
    UPROPERTY()
    ATutorialMenuHUD* TutorialMenuHUD;
  3. Create a new function to prepare the Friends Management button in the Lobby menu.

    .cpp

    void UAccelByteLobby::OnClickedOpenFriendsManagement()
    {
    TutorialMenuHUD->OpenFriendsMenu();
    }

    .h

    /**
    * @brief Button callback on Open Friends Management.
    */
    UFUNCTION()
    void OnClickedOpenFriendsManagement();
  4. Set up the Friends button with the callback function you created in the NativeConstruct().

    .cpp

    void UAccelByteLobby::NativeConstruct()
    {
    ...
    Btn_FriendsManagement->OnClicked.AddUniqueDynamic(this, &UAccelByteLobby::OnClickedOpenFriendsManagement);
    }

    .h

    /**
    * @brief Button for go to Friend Management Menu.
    */
    UPROPERTY(meta = (BindWidget))
    UButton* Btn_FriendsManagement;
  5. Open your AccelByteAuth class. Under the LoginSuccess() function, change ConnectToLobby() to InitMainMenu().

    .cpp

    void UAccelByteAuth::LoginSuccess()
    {
    ...
    if(GetOwningPlayer() && GetOwningPlayer()->GetHUD())
    {
    TutorialMenuHUD->InitMainMenu();
    }
    }
  6. While still in the AccelByteAuth class, go to the LogoutSuccess() function and call the TutorialMenuHUD's CloseMainMenu() function.

    .cpp

    void UAccelByteAuth::LogoutSuccess()
    {
    ...

    if (TutorialMenuHUD == nullptr)
    {
    TutorialMenuHUD = Cast<ATutorialMenuHUD>(GetOwningPlayer()->GetHUD());
    }

    ensure(TutorialMenuHUD != nullptr);
    TutorialMenuHUD->CloseMainMenu();
    }

Congratulations! You have now fully implemented the Friends service.

Full code for reference

AccelByteFriends.h
// Copyright (c) 2024 AccelByte Inc. All Rights Reserved.
// This is licensed software from AccelByte Inc, for limitations
// and restrictions contact your company contract manager.

#pragma once

#include "CoreMinimal.h"
#include "AccelByteFriendEntry.h"
#include "Blueprint/UserWidget.h"
#include "Models/AccelByteLobbyModels.h"
#include "TutorialProject/TutorialMenuHUD.h"
#include "AccelByteFriends.generated.h"

/**
* Enumerator to determine the current state
* Available options:
* - FRIEND_LIST
* - INCOMING_LIST
* - OUTGOING_LIST
* - BLOCKED_LIST
*/
UENUM()
enum class ECurrentStateFriend: uint8
{
FRIEND_LIST UMETA(DisplayName = "Friends List"),
INCOMING_LIST UMETA(DisplayName = "Incoming Requests List"),
OUTGOING_LIST UMETA(DisplayName = "Outgoing Requests List"),
BLOCKED_LIST UMETA(DisplayName = "Blocked Users List")
};

class UScrollBox;

/**
* Component for to use AccelByte Friend services.
* This code covers AccelByte services including :
*
* - Friend List
* - List Outgoing Friend Request
* - List Incoming Friend Request
* - Block Friend List
*
*/

UCLASS()
class TUTORIALPROJECT_API UAccelByteFriends : public UUserWidget
{
GENERATED_BODY()

virtual void NativeConstruct() override;

public:
/**
* @brief Set Friend services notification delegates.
*/
void SetNotificationDelegate();

private:
/**
* @brief Button for opening Find Friend Menu pop up.
*/
UPROPERTY(meta = (BindWidget))
UButton* Btn_FindFriend;
/**
* @brief Button for Friend List displaying.
*/
UPROPERTY(meta = (BindWidget))
UButton* Btn_FriendList;
/**
* @brief Button for Pending Incoming Friend List.
*/
UPROPERTY(meta = (BindWidget))
UButton* Btn_PendingIncomingList;
/**
* @brief Button for Pending Outgoing Friend List.
*/
UPROPERTY(meta = (BindWidget))
UButton* Btn_PendingSentList;
/**
* @brief Button for Block Friend List.
*/
UPROPERTY(meta = (BindWidget))
UButton* Btn_BlockList;
/**
* @brief Button for Back to Main Menu.
*/
UPROPERTY(meta = (BindWidget))
UButton* Btn_Back;
/**
* @brief Scroll Box for content display of four functionality (FriendList, Incoming, Outgoing, Block).
*/
UPROPERTY(meta = (BindWidget))
UScrollBox* Sb_ContentList;

/**
* @brief Callback for Find Friend.
*/
UFUNCTION()
void OnClickedFindFriend();
/**
* @brief Callback for Friend List.
*/
UFUNCTION()
void OnClickedFriendList();
/**
* @brief Callback for Pending Incoming Friend List.
*/
UFUNCTION()
void OnClickedPendingIncomingList();
/**
* @brief Callback for Pending Outgoing Friend List.
*/
UFUNCTION()
void OnClickedPendingOutgoingList();
/**
* @brief Callback for Block Friend List.
*/
UFUNCTION()
void OnClickedBlockList();
/**
* @brief Callback for Back button to lobby.
*/
UFUNCTION()
void OnClickedBackToLobby();

/**
* @brief Callback to get friends presence response.
* @param Result Model for user presence.
*/
void OnGetFriendsPresenceResponse(const FAccelByteModelsGetOnlineUsersResponse& Result);
/**
* @brief Get all the friend list data response here.
*/
void OnLoadFriendListResponse(const FAccelByteModelsLoadFriendListResponse& Result);
/**
* @brief Callback for list Incoming Friend response.
*/
void OnListIncomingFriendResponse(const FAccelByteModelsListIncomingFriendsResponse& Result);
/**
* @brief Callback for list Outgoing Friend response.
*/
void OnListOutgoingFriendResponse(const FAccelByteModelsListOutgoingFriendsResponse& Result);
/**
* @brief Callback for Successfully Get List Blocked User.
*/
void OnSuccessRetrieveListBlockedUser(const FAccelByteModelsListBlockedUserResponse& Result);
/**
* @brief Callback for Failed Get List Blocked User.
*/
void OnFailedRetrieveListBlockedUser(int32 ErrorCode, const FString& ErrorMessage);

/**
* @brief Creates Friend Entry Widget through weak object pointer.
* @param EntryMode Entry mode for the friend widget.
* @param FriendId Targeted Friend Id for the entry.
*/
void CreateEntryWidget(const EFriendEntryMode& EntryMode, const FString& FriendId);
/**
* @brief Sets the user presence of the entry.
* @param FriendEntry Pointer for Friend Entry Class.
* @param Availability User Presence Availability Code.
*/
void SetUserPresence(const TWeakObjectPtr<UAccelByteFriendEntry> FriendEntry, const FString& Availability) const;

/**
* @brief Reference to Friend Entry Class.
*/
UPROPERTY(EditDefaultsOnly)
TSubclassOf<UAccelByteFriendEntry> FriendEntryClass;
/**
* @brief Tutorial Menu HUD pointer reference.
*/
UPROPERTY()
ATutorialMenuHUD* TutorialMenuHUD;
/**
* @brief Array for Entry Widgets.
*/
TArray<TWeakObjectPtr<UAccelByteFriendEntry>> FriendListWidgets;
/**
* @brief Current state of the menu.
*/
ECurrentStateFriend CurrentState;
};

AccelByteFriends.cpp
// Copyright (c) 2024 AccelByte Inc. All Rights Reserved.
// This is licensed software from AccelByte Inc, for limitations
// and restrictions contact your company contract manager.

#include "AccelByteFriends.h"
#include "Api/AccelByteLobbyApi.h"
#include "Api/AccelByteUserApi.h"
#include "Core/AccelByteRegistry.h"
#include "Components/Button.h"
#include "Components/ScrollBox.h"
#include "TutorialProject/TutorialMenuHUD.h"

void UAccelByteFriends::NativeConstruct()
{
Super::NativeConstruct();

check (GetOwningPlayer() != nullptr);
check (GetOwningPlayer()->GetHUD() != nullptr);
TutorialMenuHUD = Cast<ATutorialMenuHUD>(GetOwningPlayer()->GetHUD());

Btn_FindFriend->OnClicked.AddUniqueDynamic(this, &UAccelByteFriends::OnClickedFindFriend);
Btn_FriendList->OnClicked.AddUniqueDynamic(this, &UAccelByteFriends::LoadFriendList);
Btn_PendingIncomingList->OnClicked.AddUniqueDynamic(this, &UAccelByteFriends::LoadPendingIncomingList);
Btn_PendingSentList->OnClicked.AddUniqueDynamic(this, &UAccelByteFriends::LoadPendingOutgoingList);
Btn_Back->OnClicked.AddUniqueDynamic(this, &UAccelByteFriends::OnClickedBackToLobby);

SetNotificationDelegate();
CurrentState = ECurrentStateFriend::FRIEND_LIST;
RefreshFriendsList();
}

void UAccelByteFriends::SwitchActiveButton(UButton* CurrentButton)
{
if (!CurrentActiveButton) CurrentActiveButton = CurrentButton;

CurrentActiveButton->SetBackgroundColor(FLinearColor::White);
CurrentActiveButton = CurrentButton;

FLinearColor SelectedTabGreyColor = FLinearColor(0.2f, 0.2f, 0.2f, 1.0f);
CurrentActiveButton->SetBackgroundColor(SelectedTabGreyColor);
}

void UAccelByteFriends::SetNotificationDelegate()
{
FRegistry::Lobby.SetOnFriendRequestAcceptedNotifDelegate(
Api::Lobby::FAcceptFriendsNotif::CreateWeakLambda(this, [this](const FAccelByteModelsAcceptFriendsNotif& Result)
{
if (CurrentState == ECurrentStateFriend::FRIEND_LIST)
{
CreateEntryWidget(EFriendEntryMode::Friend, Result.friendId);
}
else if (CurrentState == ECurrentStateFriend::OUTGOING_LIST)
{
FRegistry::Lobby.ListOutgoingFriends();
}
}));

FRegistry::Lobby.SetOnUnfriendNotifDelegate(
Api::Lobby::FUnfriendNotif::CreateWeakLambda(this, [this](const FAccelByteModelsUnfriendNotif& Result)
{
if (CurrentState == ECurrentStateFriend::FRIEND_LIST)
{
FRegistry::Lobby.LoadFriendsList();
}
}));

FRegistry::Lobby.SetOnCancelFriendsNotifDelegate(
Api::Lobby::FCancelFriendsNotif::CreateWeakLambda(this, [this](const FAccelByteModelsCancelFriendsNotif& Result)
{
if (CurrentState == ECurrentStateFriend::INCOMING_LIST)
{
FRegistry::Lobby.ListIncomingFriends();
}
}));

FRegistry::Lobby.SetOnRejectFriendsNotifDelegate(
Api::Lobby::FRejectFriendsNotif::CreateWeakLambda(this, [this](const FAccelByteModelsRejectFriendsNotif& Result)
{
if (CurrentState == ECurrentStateFriend::OUTGOING_LIST)
{
FRegistry::Lobby.ListOutgoingFriends();
}
}));

FRegistry::Lobby.SetOnIncomingRequestFriendsNotifDelegate(
Api::Lobby::FRequestFriendsNotif::CreateWeakLambda(this, [this](const FAccelByteModelsRequestFriendsNotif& Result)
{
if (CurrentState == ECurrentStateFriend::INCOMING_LIST)
{
FRegistry::Lobby.ListIncomingFriends();
}
}));
}

void UAccelByteFriends::RefreshFriendsList()
{
switch (CurrentState)
{
case ECurrentStateFriend::FRIEND_LIST:
{
LoadFriendList();
break;
}
case ECurrentStateFriend::INCOMING_LIST:
{
LoadPendingIncomingList();
break;
}
case ECurrentStateFriend::OUTGOING_LIST:
{
LoadPendingOutgoingList();
break;
}
}
}

void UAccelByteFriends::OnClickedFindFriend()
{
TutorialMenuHUD->OpenFindFriendsMenu();
}

void UAccelByteFriends::LoadFriendList()
{
SwitchActiveButton(Btn_FriendList);

Sb_ContentList->ClearChildren();
CurrentState = ECurrentStateFriend::FRIEND_LIST;

FRegistry::Lobby.SetLoadFriendListResponseDelegate(
Api::Lobby::FLoadFriendListResponse::CreateWeakLambda(this, [this](const FAccelByteModelsLoadFriendListResponse& Result)
{
Sb_ContentList->ClearChildren();
FriendListWidgets.Empty();

for (const FString& FriendId : Result.friendsId)
{
CreateEntryWidget(EFriendEntryMode::Friend, FriendId);
}

UE_LOG(LogTemp, Log, TEXT("Success Retrieve Load Friend List Response!"));
}),
FErrorHandler::CreateWeakLambda(this, [](int32 ErrorCode, const FString& ErrorMessage)
{
UE_LOG(LogTemp, Error, TEXT("Error LoadFriendListResponseDelegate, Error Code: %d Error Message: %s"), ErrorCode, *ErrorMessage);
}));

FRegistry::Lobby.LoadFriendsList();
}

void UAccelByteFriends::LoadPendingIncomingList()
{
SwitchActiveButton(Btn_PendingIncomingList);

Sb_ContentList->ClearChildren();
CurrentState = ECurrentStateFriend::INCOMING_LIST;

FRegistry::Lobby.SetListIncomingFriendsResponseDelegate(
Api::Lobby::FListIncomingFriendsResponse::CreateWeakLambda(this, [this](
FAccelByteModelsListIncomingFriendsResponse Result)
{
Sb_ContentList->ClearChildren();
FriendListWidgets.Empty();

for (const FString& FriendId : Result.friendsId)
{
CreateEntryWidget(EFriendEntryMode::Incoming, FriendId);
}
UE_LOG(LogTemp, Log, TEXT("Success Retrieve List Incoming Friend Response!"));
}),
FErrorHandler::CreateWeakLambda(this, [](const int32 Code, const FString& Message)
{
UE_LOG(LogTemp, Error, TEXT("Unable to retrieve the list of incoming friend requests! Code : %i , Message : %s"), Code, *Message);
}));

FRegistry::Lobby.ListIncomingFriends();
}

void UAccelByteFriends::LoadPendingOutgoingList()
{
SwitchActiveButton(Btn_PendingSentList);

Sb_ContentList->ClearChildren();
CurrentState = ECurrentStateFriend::OUTGOING_LIST;

FRegistry::Lobby.SetListOutgoingFriendsResponseDelegate(
Api::Lobby::FListOutgoingFriendsResponse::CreateWeakLambda(this, [this](
FAccelByteModelsListOutgoingFriendsResponse Result)
{
Sb_ContentList->ClearChildren();
FriendListWidgets.Empty();

for (const FString& FriendId : Result.friendsId)
{
CreateEntryWidget(EFriendEntryMode::Outgoing, FriendId);
}

UE_LOG(LogTemp, Log, TEXT("Success Retrieve List Outgoing Friend Response!"));
}),
FErrorHandler::CreateWeakLambda(this, [](const int32 Code, const FString& Message)
{
UE_LOG(LogTemp, Error, TEXT("Unable to retrieve the list of outgoing friend requests! Code : %i , Message : %s"), Code, *Message);
}));

FRegistry::Lobby.ListOutgoingFriends();
}

void UAccelByteFriends::OnClickedBackToLobby()
{
TutorialMenuHUD->CloseFriendMenu();
}

void UAccelByteFriends::CreateEntryWidget(const EFriendEntryMode& EntryMode, const FString& FriendId)
{
const TWeakObjectPtr<UAccelByteFriendEntry> FriendEntryWidget = MakeWeakObjectPtr<UAccelByteFriendEntry>(CreateWidget<UAccelByteFriendEntry>(this, FriendEntryClass.Get()));

FriendListWidgets.Add(FriendEntryWidget);

FriendEntryWidget->InitData(EntryMode, FriendId);

Sb_ContentList->AddChild(FriendEntryWidget.Get());
}
AccelByteFindFriend.h
// Copyright (c) 2024 AccelByte Inc. All Rights Reserved.
// This is licensed software from AccelByte Inc, for limitations
// and restrictions contact your company contract manager.

#pragma once

#include "CoreMinimal.h"
#include "AccelByteFriendEntry.h"
#include "Models/AccelByteLobbyModels.h"
#include "TutorialProject/TutorialMenuHUD.h"
#include "AccelByteFriends.generated.h"

/**
* Enumerator to determine the current state
* Available options:
* - FRIEND_LIST
* - INCOMING_LIST
* - OUTGOING_LIST
* - BLOCKED_LIST
*/

UENUM()
enum class ECurrentStateFriend: uint8
{
FRIEND_LIST UMETA(DisplayName = "Friends List"),
INCOMING_LIST UMETA(DisplayName = "Incoming Requests List"),
OUTGOING_LIST UMETA(DisplayName = "Outgoing Requests List"),
BLOCKED_LIST UMETA(DisplayName = "Blocked Users List")
};

class UScrollBox;
class UButton;

/**
* Component for to use AccelByte Friend services.
* This code covers AccelByte services including :
*
* - Friend List
* - List Outgoing Friend Request
* - List Incoming Friend Request
* - Block Friend List
*
*/

UCLASS()
class TUTORIALPROJECT_API UAccelByteFriends : public UUserWidget
{
GENERATED_BODY()

virtual void NativeConstruct() override;

/**
* @brief Switch the active button to the current button that player clicked before
* @param CurrentButton Current target active button
*/
void SwitchActiveButton(UButton* CurrentButton);

/**
* @brief Instantiate current active button
*/
UPROPERTY()
UButton* CurrentActiveButton;

public:

/**
* @brief Set Friend services notification delegates.
*/
void SetNotificationDelegate();

/**
* @brief Refresh friends list when open the friends menu
*/
void RefreshFriendsList();

private:

/**
* @brief Button for opening Find Friend Menu pop up.
*/
UPROPERTY(meta = (BindWidget))
UButton* Btn_FindFriend;
/**
* @brief Button for Friend List displaying.
*/
UPROPERTY(meta = (BindWidget))
UButton* Btn_FriendList;
/**
* @brief Button for Pending Incoming Friend List.
*/
UPROPERTY(meta = (BindWidget))
UButton* Btn_PendingIncomingList;
/**
* @brief Button for Pending Outgoing Friend List.
*/
UPROPERTY(meta = (BindWidget))
UButton* Btn_PendingSentList;
/**
* @brief Button for Back to Main Menu.
*/
UPROPERTY(meta = (BindWidget))
UButton* Btn_Back;
/**
* @brief Scroll Box for content display of four functionality (FriendList, Incoming, Outgoing, Block).
*/
UPROPERTY(meta = (BindWidget))
UScrollBox* Sb_ContentList;

/**
* @brief Callback for Find Friend.
*/
UFUNCTION()
void OnClickedFindFriend();
/**
* @brief Callback for Friend List.
*/
UFUNCTION()
void LoadFriendList();
/**
* @brief Callback for Pending Incoming Friend List.
*/
UFUNCTION()
void LoadPendingIncomingList();
/**
* @brief Callback for Pending Outgoing Friend List.
*/
UFUNCTION()
void LoadPendingOutgoingList();
/**
* @brief Callback for Back button to lobby.
*/
UFUNCTION()
void OnClickedBackToLobby();

/**
* @brief Creates Friend Entry Widget through weak object pointer.
* @param EntryMode Entry mode for the friend widget.
* @param FriendId Targeted Friend Id for the entry.
*/
void CreateEntryWidget(const EFriendEntryMode& EntryMode, const FString& FriendId);

/**
* @brief Reference to Friend Entry Class.
*/
UPROPERTY(EditDefaultsOnly)
TSubclassOf<UAccelByteFriendEntry> FriendEntryClass;
/**
* @brief Tutorial Menu HUD pointer reference.
*/
UPROPERTY()
ATutorialMenuHUD* TutorialMenuHUD;
/**
* @brief Array for Entry Widgets.
*/
TArray<TWeakObjectPtr<UAccelByteFriendEntry>> FriendListWidgets;
/**
* @brief Current state of the menu.
*/
ECurrentStateFriend CurrentState;
};
AccelByteFindFriend.cpp
// Copyright (c) 2024 AccelByte Inc. All Rights Reserved.
// This is licensed software from AccelByte Inc, for limitations
// and restrictions contact your company contract manager.

#include "AccelbyteFindFriend.h"
#include "AccelByteFriends.h"
// AccelByte Services
#include "Api/AccelByteLobbyApi.h"
#include "Api/AccelByteUserApi.h"
// Widget Components
#include "Components/Button.h"
#include "Components/EditableTextBox.h"
#include "Components/ScrollBox.h"
// Menu HUD
#include "TutorialProject/TutorialMenuHUD.h"

void UAccelbyteFindFriend::NativeConstruct()
{
Super::NativeConstruct();

check (GetOwningPlayer() != nullptr);
check (GetOwningPlayer()->GetHUD() != nullptr);
TutorialMenuHUD = Cast<ATutorialMenuHUD>(GetOwningPlayer()->GetHUD());

Btn_FindFriend->OnClicked.AddUniqueDynamic(this, &UAccelbyteFindFriend::OnClickFindFriends);
Btn_Close->OnClicked.AddUniqueDynamic(this, &UAccelbyteFindFriend::OnClickCloseFindFriendWidget);
}

void UAccelbyteFindFriend::OnClickFindFriends()
{
Sb_FriendList->ClearChildren();

FilterSearchEntryList();
}

void UAccelbyteFindFriend::OnClickCloseFindFriendWidget()
{
TutorialMenuHUD->CloseFindFriendsMenu();
}

void UAccelbyteFindFriend::FilterSearchEntryList()
{
FRegistry::Lobby.SetLoadFriendListResponseDelegate(Api::Lobby::FLoadFriendListResponse::CreateUObject(this, &UAccelbyteFindFriend::OnLoadFriendListResponse));
FRegistry::Lobby.SetListOutgoingFriendsResponseDelegate(Api::Lobby::FListOutgoingFriendsResponse::CreateUObject(this, &UAccelbyteFindFriend::OnListOutgoingFriendResponse));

// Filter users on Friends List.
FRegistry::Lobby.LoadFriendsList();
}

void UAccelbyteFindFriend::OnLoadFriendListResponse(const FAccelByteModelsLoadFriendListResponse& Result)
{
FriendSentRequestArray.Empty();

if (Result.Code == "0")
{
FriendSentRequestArray = Result.friendsId;

// Filter users on Outgoing Friends List.
FRegistry::Lobby.ListOutgoingFriends();
}
else
{
UE_LOG(LogTemp, Warning, TEXT("Unable to retrieve friends list!"));
}
}

void UAccelbyteFindFriend::OnListOutgoingFriendResponse(const FAccelByteModelsListOutgoingFriendsResponse& Result)
{
if (Result.Code == "0")
{
FriendSentRequestArray.Append(Result.friendsId);

// Search friends after filtering users.
FindFriends(Etb_FindFriend->GetText().ToString());
}
else
{
UE_LOG(LogTemp, Warning, TEXT("Unable to retrieve the list of outgoing/pending friend request!"));
}
}

void UAccelbyteFindFriend::FindFriends(const FString& FriendName)
{
FRegistry::User.SearchUsers(
FriendName,
THandler<FPagedPublicUsersInfo>::CreateUObject(this, &UAccelbyteFindFriend::OnSuccessFindFriends),
FErrorHandler::CreateUObject(this, &UAccelbyteFindFriend::OnFailedFindFriends)
);
}

void UAccelbyteFindFriend::OnSuccessFindFriends(const FPagedPublicUsersInfo& Result)
{
Sb_FriendList->ClearChildren();

for (const FPublicUserInfo& UserData : Result.Data)
{
// If search entry is not local player.
if (UserData.UserId != TutorialMenuHUD->PlayerInfo.UserId)
{
// Create friend entry.
const TWeakObjectPtr<UAccelByteFriendEntry> SearchFriendEntry = MakeWeakObjectPtr<UAccelByteFriendEntry>(
CreateWidget<UAccelByteFriendEntry>(this, FriendEntryClass.Get())
);

// Init entry data.
SearchFriendEntry->InitData(EFriendEntryMode::SEARCH_ENTRY, UserData.UserId);

// Disable add friend button when the request was already sent from our previous filtered array.
for (const FString& FriendSentRequest : FriendSentRequestArray)
{
if (UserData.UserId == FriendSentRequest)
{
SearchFriendEntry->EnableAddFriendButton(false);
}
}

// Add Entry to Scroll Box.
Sb_FriendList->AddChild(SearchFriendEntry.Get());
}
}
}

void UAccelbyteFindFriend::OnFailedFindFriends(int32 ErrorCode, const FString& ErrorMessage)
{
const FString DebugMessage = FString::Printf(TEXT("Failed Find Friends : %d , %s"), ErrorCode, *ErrorMessage);
GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Cyan, DebugMessage);

UE_LOG(LogTemp, Warning, TEXT(" Failed Find Friends : Code: %d, Reason: %s"), ErrorCode, *ErrorMessage);
}
AccelByteFriendEntry.h
// Copyright (c) 2024 AccelByte Inc. All Rights Reserved.
// This is licensed software from AccelByte Inc, for limitations
// and restrictions contact your company contract manager.

#pragma once

#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "Models/AccelByteUserModels.h"
#include "Models/AccelByteLobbyModels.h"
#include "AccelByteFriendEntry.generated.h"

class UTextBlock;
class UButton;
class UScaleBox;
class UHorizontalBox;
class UWidgetSwitcher;

/**
* Enumerator for Friend Entry widget modes.
* Available options:
* - Friend
* - Incoming Request
* - Outgoing Request
* - Blocked Users
* - Search User Entry
*/

UENUM()
enum class EFriendEntryMode : uint8
{
FRIEND_ENTRY UMETA(DisplayName = "Friend"),
INCOMING_ENTRY UMETA(DisplayName = "Incoming Request"),
OUTGOING_ENTRY UMETA(DisplayName = "Outgoing Request"),
BLOCKED_ENTRY UMETA(DisplayName = "Blocked"),
SEARCH_ENTRY UMETA(DisplayName = "Search")
};

/**
* Entry for Friend data.
* In this code you will use :
*
* - Get User by User ID
* - Chat with specific person
* - Unfriend specific person
* - Block specific person
*/

UCLASS()
class TUTORIALPROJECT_API UAccelByteFriendEntry : public UUserWidget
{
GENERATED_BODY()

public:
/**
* @brief Initialize Entry Data by getting User Data by User Id and selecting its Entry Mode
* @param EntryMode Select Entry Mode. Available modes: FRIEND, INCOMING, OUTGOING, BLOCKED, SEARCH.
* @param UserId User Id needed to get User Data.
*/
void InitData(const EFriendEntryMode& EntryMode, const FString& UserId);

/**
* @brief Set user presence text block.
* @param FriendPresence Replace this value with available user presences.
*/
void SetPresenceText(const FString& FriendPresence) const;

/**
* @brief Enable or Disable Add Friend button for search entry.
* @param bEnable Value to enable or disable the button.
*/
void EnableAddFriendButton(bool bEnable) const;

private:
// Friend Entry Type
/**
* @brief Button for Chat Friend.
*/
UPROPERTY(meta = (BindWidget))
UButton* Btn_Chat;
/**
* @brief Button for Invite Friend to Party.
*/
UPROPERTY(meta = (BindWidget))
UButton* Btn_InviteParty;
/**
* @brief Button for Unfriend.
*/
UPROPERTY(meta = (BindWidget))
UButton* Btn_Unfriend;
/**
* @brief Button for Block Friend.
*/
UPROPERTY(meta = (BindWidget))
UButton* Btn_Block;

// Block Entry Type
/**
* @brief Button to Unblock User.
*/
UPROPERTY(meta = (BindWidget))
UButton* Btn_Unblock;

// Incoming Entry Type
/**
* @brief Button for Accepting Friend Request.
*/
UPROPERTY(meta = (BindWidget))
UButton* Btn_Accept;
/**
* @brief Button for Decline Friend Request.
*/
UPROPERTY(meta = (BindWidget))
UButton* Btn_Decline;
/**
* @brief Button for Block User by Incoming Request.
*/
UPROPERTY(meta = (BindWidget))
UButton* Btn_Block_Request;

// Outgoing Entry Type
/**
* @brief Button for canceling outgoing friend request.
*/
UPROPERTY(meta = (BindWidget))
UButton* Btn_CancelRequest;

// Search Entry Type
/**
* @brief Button for Add Friend.
*/
UPROPERTY(meta = (BindWidget))
UButton* Btn_AddFriend;

/**
* @brief Text Box for Friend Name (Will be replaced with friend name).
*/
UPROPERTY(meta = (BindWidget))
UTextBlock* Tb_FriendName;
/**
* @brief Text Box for Friend Status e.g., Online, Offline, or Away.
*/
UPROPERTY(meta = (BindWidget))
UTextBlock* Tb_Status;
/**
* @brief Widget Switcher to switch Entry Mode.
*/
UPROPERTY(meta = (BindWidget))
UWidgetSwitcher* Ws_EntryMode;
/**
* @brief Scale Box for User Image.
*/
UPROPERTY(meta = (BindWidget))
UScaleBox* Sb_Friend;

// Horizontal Boxes for Widget Switcher.
/**
* @brief Horizontal Box for Friend component.
*/
UPROPERTY(meta = (BindWidget))
UHorizontalBox* Hb_Friend;
/**
* @brief Horizontal Box for Incoming component.
*/
UPROPERTY(meta = (BindWidget))
UHorizontalBox* Hb_IncomingRequest;
/**
* @brief Horizontal Box for Outgoing component.
*/
UPROPERTY(meta = (BindWidget))
UHorizontalBox* Hb_OutgoingRequest;
/**
* @brief Horizontal Box for Blocked component.
*/
UPROPERTY(meta = (BindWidget))
UHorizontalBox* Hb_Blocked;
/**
* @brief Horizontal Box for Search component.
*/
UPROPERTY(meta = (BindWidget))
UHorizontalBox* Hb_Search;

/**
* @brief Functionality for Chat Button.
*/
UFUNCTION()
void OnClickedChat();
/**
* @brief Functionality for Invite Party Button.
*/
UFUNCTION()
void OnClickedInviteParty();
/**
* @brief Functionality for Unfriend Button.
*/
UFUNCTION()
void OnClickedUnfriend();
/**
* @brief Functionality for Block Friend Button.
*/
UFUNCTION()
void OnClickedBlock();
/**
* @brief Callback for Clicking Unblock button.
*/
UFUNCTION()
void OnClickedUnblock();
/**
* @brief Callback for Accept Button.
*/
UFUNCTION()
void OnClickedAccept();
/**
* @brief Callback for Decline Button.
*/
UFUNCTION()
void OnClickedDecline();
/**
* @brief Click callback to cancel request.
*/
UFUNCTION()
void OnClickedCancelRequest();
/**
* @brief Functionality for Add Friend Button.
*/
UFUNCTION()
void OnClickedAdd();

/**
* @brief Callback for successfully get the user ID.
*/
void OnSuccessGetUserId(const FSimpleUserData& Data);

/**
* @brief Callback for failed getting user ID.
*/
void OnFailedGetUserId(int32 ErrorCode, const FString& ErrorMessage);

/**
* @brief Callback for Unfriend.
*/
void OnUnfriendResponse(const FAccelByteModelsUnfriendResponse& Result);

/**
* @brief Callback for Block.
*/
void OnBlockPlayerResponse(const FAccelByteModelsBlockPlayerResponse& Result);

/**
* @brief Callback for when successfully unblock user.
*/
void OnUnblockPlayerResponse(const FAccelByteModelsUnblockPlayerResponse& Result);

/**
* @brief Response for Accepting Friend Request.
*/
void OnAcceptFriendResponse(const FAccelByteModelsAcceptFriendsResponse& Result);

/**
* @brief Response for Rejecting Friend Request.
*/
void OnRejectFriendResponse(const FAccelByteModelsRejectFriendsResponse& Result);

/**
* @brief Response for Friend Request.
*/
void OnRequestFriendResponse(const FAccelByteModelsRequestFriendsResponse& Result);

/**
* @brief Set Mode settings for selected entry enumerator.
*/
void SetEntryMode(const EFriendEntryMode& EntryMode) const;

public:
/**
* @brief Models to store User Data contained in this entry.
*/
FSimpleUserData UserData;
};
AccelByteFriendEntry.cpp
// Copyright (c) 2024 AccelByte Inc. All Rights Reserved.
// This is licensed software from AccelByte Inc, for limitations
// and restrictions contact your company contract manager.

#include "AccelByteFriendEntry.h"
#include "../Party/AccelByteParty.h"
// AccelByte Services
#include "Api/AccelByteLobbyApi.h"
#include "Api/AccelByteUserApi.h"
#include "Core/AccelByteRegistry.h"
// Widget Components
#include "Components/Button.h"
#include "Components/TextBlock.h"
#include "Components/WidgetSwitcher.h"
#include "Components/Image.h"
#include "Components/ScaleBox.h"
#include "Components/HorizontalBox.h"
// Menu HUD
#include "TutorialProject/TutorialMenuHUD.h"

void UAccelByteFriendEntry::InitData(const EFriendEntryMode& EntryMode, const FString& UserId)
{
FRegistry::User.GetUserByUserId(
UserId,
THandler<FSimpleUserData>::CreateUObject(this, &UAccelByteFriendEntry::OnSuccessGetUserId),
FErrorHandler::CreateUObject(this, &UAccelByteFriendEntry::OnFailedGetUserId));

SetEntryMode(EntryMode);
}

void UAccelByteFriendEntry::EnableAddFriendButton(bool bEnable) const
{
Btn_AddFriend->SetIsEnabled(bEnable);
}

void UAccelByteFriendEntry::OnClickedChat()
{
}

void UAccelByteFriendEntry::OnClickedInviteParty()
{
Cast<ATutorialMenuHUD>(GetOwningPlayer()->GetHUD())->GetPartyMenu()->OnClickedInviteParty(UserData.UserId);
}

void UAccelByteFriendEntry::OnClickedUnfriend()
{
FRegistry::Lobby.SetUnfriendResponseDelegate(Api::Lobby::FUnfriendResponse::CreateUObject(this, & UAccelByteFriendEntry::OnUnfriendResponse));
FRegistry::Lobby.Unfriend(UserData.UserId);
}

void UAccelByteFriendEntry::OnClickedBlock()
{
FRegistry::Lobby.SetBlockPlayerResponseDelegate(Api::Lobby::FBlockPlayerResponse::CreateUObject(this, &UAccelByteFriendEntry::OnBlockPlayerResponse));
FRegistry::Lobby.BlockPlayer(UserData.UserId);
}

void UAccelByteFriendEntry::OnClickedUnblock()
{
FRegistry::Lobby.SetUnblockPlayerResponseDelegate(Api::Lobby::FUnblockPlayerResponse::CreateUObject(this, &UAccelByteFriendEntry::OnUnblockPlayerResponse));
FRegistry::Lobby.UnblockPlayer(UserData.UserId);
}

void UAccelByteFriendEntry::OnClickedAccept()
{
FRegistry::Lobby.SetAcceptFriendsResponseDelegate(Api::Lobby::FAcceptFriendsResponse::CreateUObject(this, &UAccelByteFriendEntry::OnAcceptFriendResponse));
FRegistry::Lobby.AcceptFriend(UserData.UserId);
}

void UAccelByteFriendEntry::OnClickedDecline()
{
FRegistry::Lobby.SetRejectFriendsResponseDelegate(Api::Lobby::FRejectFriendsResponse::CreateUObject(this, &UAccelByteFriendEntry::OnRejectFriendResponse));
FRegistry::Lobby.RejectFriend(UserData.UserId);
}

void UAccelByteFriendEntry::OnClickedCancelRequest()
{
FRegistry::Lobby.CancelFriendRequest(UserData.UserId);
this->RemoveFromParent();
}

void UAccelByteFriendEntry::OnClickedAdd()
{
FRegistry::Lobby.SetRequestFriendsResponseDelegate(Api::Lobby::FRequestFriendsResponse::CreateUObject(this, &UAccelByteFriendEntry::OnRequestFriendResponse));
FRegistry::Lobby.RequestFriend(UserData.UserId);
Btn_AddFriend->SetIsEnabled(false);
}

void UAccelByteFriendEntry::OnSuccessGetUserId(const FSimpleUserData& Data)
{
UserData = Data;
Tb_FriendName->SetText(FText::FromString(UserData.DisplayName));
}

void UAccelByteFriendEntry::OnFailedGetUserId(int32 ErrorCode, const FString& ErrorMessage)
{
const FString DebugMessage = FString::Printf(TEXT("Get User Id Failed : %d , %s"), ErrorCode, *ErrorMessage);
GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Cyan, DebugMessage);

UE_LOG(LogTemp, Warning, TEXT(" Get User Id Failed : Code: %d, Reason: %s"), ErrorCode, *ErrorMessage);
}

void UAccelByteFriendEntry::OnUnfriendResponse(const FAccelByteModelsUnfriendResponse& Result)
{
if (Result.Code == "0")
{
UE_LOG(LogTemp, Log, TEXT("Successfully unfriend a friend!"));
this->RemoveFromParent();
}
else
{
UE_LOG(LogTemp, Warning, TEXT("Failed to unfriend a friend! Code: %s"), *Result.Code);
}
}

void UAccelByteFriendEntry::OnBlockPlayerResponse(const FAccelByteModelsBlockPlayerResponse& Result)
{
if (Result.Code == "0")
{
UE_LOG(LogTemp, Log, TEXT("Successfully block a friend!"));
this->RemoveFromParent();
}
else
{
UE_LOG(LogTemp, Warning, TEXT("Failed to block a friend! Code: %s"), *Result.Code);
}
}

void UAccelByteFriendEntry::OnUnblockPlayerResponse(const FAccelByteModelsUnblockPlayerResponse& Result)
{
if (Result.Code == "0")
{
UE_LOG(LogTemp, Log, TEXT("Successfully Unblock User!"));
this->RemoveFromParent();
}
else
{
UE_LOG(LogTemp, Warning, TEXT("Cannot retrieve the list of outgoing/pending friend request!"));
}
}

void UAccelByteFriendEntry::OnAcceptFriendResponse(const FAccelByteModelsAcceptFriendsResponse& Result)
{
if (Result.Code == "0")
{
UE_LOG(LogTemp, Log, TEXT("Successfully accept a friend request!"));
this->RemoveFromParent();
}
else
{
UE_LOG(LogTemp, Warning, TEXT("Failed to accept a friend request! Code: %s"), *Result.Code);
}
}

void UAccelByteFriendEntry::OnRejectFriendResponse(const FAccelByteModelsRejectFriendsResponse& Result)
{
if (Result.Code == "0")
{
UE_LOG(LogTemp, Log, TEXT("Successfully reject a friend request!"));
this->RemoveFromParent();
}
else
{
UE_LOG(LogTemp, Warning, TEXT("Failed to reject a friend request! Code: %s"), *Result.Code);
}
}

void UAccelByteFriendEntry::OnRequestFriendResponse(const FAccelByteModelsRequestFriendsResponse& Result)
{
if (Result.Code == "0")
{
UE_LOG(LogTemp, Log, TEXT("Successfully send a friend request!"));
}
else
{
UE_LOG(LogTemp, Warning, TEXT("Failed to send a friend request! Code: %s"), *Result.Code);
}
}

void UAccelByteFriendEntry::SetEntryMode(const EFriendEntryMode& EntryMode) const
{
switch(EntryMode)
{
case EFriendEntryMode::FRIEND_ENTRY:
Sb_Friend->SetVisibility(ESlateVisibility::SelfHitTestInvisible);
Tb_Status->SetVisibility(ESlateVisibility::SelfHitTestInvisible);
Ws_EntryMode->SetActiveWidget(Hb_Friend);

Btn_Chat->OnClicked.AddUniqueDynamic(this, &UAccelByteFriendEntry::OnClickedChat);
Btn_InviteParty->OnClicked.AddUniqueDynamic(this, &UAccelByteFriendEntry::OnClickedInviteParty);
Btn_Unfriend->OnClicked.AddUniqueDynamic(this, &UAccelByteFriendEntry::OnClickedUnfriend);
Btn_Block->OnClicked.AddUniqueDynamic(this, &UAccelByteFriendEntry::OnClickedBlock);
return;

case EFriendEntryMode::INCOMING_ENTRY:
Sb_Friend->SetVisibility(ESlateVisibility::SelfHitTestInvisible);
Tb_Status->SetVisibility(ESlateVisibility::Collapsed);
Ws_EntryMode->SetActiveWidget(Hb_IncomingRequest);

Btn_Accept->OnClicked.AddUniqueDynamic(this, &UAccelByteFriendEntry::OnClickedAccept);
Btn_Decline->OnClicked.AddUniqueDynamic(this, &UAccelByteFriendEntry::OnClickedDecline);
Btn_Block_Request->OnClicked.AddUniqueDynamic(this, &UAccelByteFriendEntry::OnClickedBlock);
return;

case EFriendEntryMode::OUTGOING_ENTRY:
Sb_Friend->SetVisibility(ESlateVisibility::SelfHitTestInvisible);
Tb_Status->SetVisibility(ESlateVisibility::Collapsed);
Ws_EntryMode->SetActiveWidget(Hb_OutgoingRequest);

Btn_CancelRequest->OnClicked.AddUniqueDynamic(this, &UAccelByteFriendEntry::OnClickedCancelRequest);
return;

case EFriendEntryMode::BLOCKED_ENTRY:
Sb_Friend->SetVisibility(ESlateVisibility::SelfHitTestInvisible);
Tb_Status->SetVisibility(ESlateVisibility::Collapsed);
Ws_EntryMode->SetActiveWidget(Hb_Blocked);

Btn_Unblock->OnClicked.AddUniqueDynamic(this, &UAccelByteFriendEntry::OnClickedUnblock);
return;

case EFriendEntryMode::SEARCH_ENTRY:
Sb_Friend->SetVisibility(ESlateVisibility::Collapsed);
Tb_Status->SetVisibility(ESlateVisibility::Collapsed);
Ws_EntryMode->SetActiveWidget(Hb_Search);

Btn_AddFriend->OnClicked.AddUniqueDynamic(this, &UAccelByteFriendEntry::OnClickedAdd);
return;

// FRIEND_ENTRY Default Entry Mode.
default:
Sb_Friend->SetVisibility(ESlateVisibility::SelfHitTestInvisible);
Tb_Status->SetVisibility(ESlateVisibility::Hidden);
Ws_EntryMode->SetActiveWidget(Hb_Friend);

Btn_Chat->OnClicked.AddUniqueDynamic(this, &UAccelByteFriendEntry::OnClickedChat);
Btn_InviteParty->OnClicked.AddUniqueDynamic(this, &UAccelByteFriendEntry::OnClickedInviteParty);
Btn_Unfriend->OnClicked.AddUniqueDynamic(this, &UAccelByteFriendEntry::OnClickedUnfriend);
Btn_Block->OnClicked.AddUniqueDynamic(this, &UAccelByteFriendEntry::OnClickedBlock);
}
}
AccelByteMainMenu.h
// Copyright (c) 2024 AccelByte Inc. All Rights Reserved.
// This is licensed software from AccelByte Inc, for limitations
// and restrictions contact your company contract manager.

#pragma once

#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "AccelByteMainMenu.generated.h"

class UButton;
class UTextBlock;
class USizeBox;
class ATutorialMenuHUD;

/**
* Main Menu controller.
*/

UCLASS()
class TUTORIALPROJECT_API UAccelByteMainMenu : public UUserWidget
{
GENERATED_BODY()

protected:

virtual void NativeConstruct() override;

/**
* @brief Button for Accessing Lobby Menu.
*/
UPROPERTY(meta = (BindWidget))
UButton* Btn_Lobby;

/**
* @brief A Tutorial Menu HUD to handle all instantiating and casting to the HUD framework
*/
ATutorialMenuHUD* TutorialMenuHUD;
};
AccelByteMainMenu.cpp
// Copyright (c) 2024 AccelByte Inc. All Rights Reserved.
// This is licensed software from AccelByte Inc, for limitations
// and restrictions contact your company contract manager.

#include "AccelByteMainMenu.h"
#include "Authentication/AccelByteAuth.h"
#include "Components/Button.h"
#include "TutorialProject/TutorialMenuHUD.h"

void UAccelByteMainMenu::NativeConstruct()
{
Super::NativeConstruct();

TutorialMenuHUD = Cast<ATutorialMenuHUD>(GetOwningPlayer()->GetHUD());

check(TutorialMenuHUD);

Btn_Lobby->OnClicked.AddUniqueDynamic(TutorialMenuHUD, &ATutorialMenuHUD::OpenLobbyMenu);
}
TutorialMenuHUD.h
// Copyright (c) 2024 AccelByte Inc. All Rights Reserved.
// This is licensed software from AccelByte Inc, for limitations
// and restrictions contact your company contract manager.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/HUD.h"
#include "TutorialMenuHUD.generated.h"

class UAccelByteAuth;
class UAccelByteLobby;
class UAccelByteFriends;
class UAccelByteFindFriend;

/**
* Menu Widget Controller. All Widget functionality controlled here.
*/
UCLASS()
class TUTORIALPROJECT_API ATutorialMenuHUD : public AHUD
{
GENERATED_BODY()

protected:

virtual void BeginPlay() override;

public:

/**
* @brief Shows Login Menu on screen
*/
void OpenLoginMenu();

/**
* @brief Shows Main Menu on screen and destroys Login Menu
*/
void OpenMainMenu();

/**
* @brief Init the construct function when the first time open main menu;
*/
void InitMainMenu();

/**
* @brief Shows Lobby Menu which adds Party Menu in Sb_Party and destroys Main Menu
*/
UFUNCTION()
void OpenLobbyMenu();

/**
* @brief Shows Friends Menu on screen
*/
void OpenFriendsMenu();

/**
* @brief Shows Find Friends Menu on screen
*/
void OpenFindFriendsMenu();

/**
* @brief Destroys Main Menu widget
*/
void CloseMainMenu();

/**
* @brief Destroys Lobby Menu widget and shows Main Menu
*/
UFUNCTION()
void CloseLobbyMenu();

/**
* @brief Destroys Friend Menu widget
*/
void CloseFriendMenu();

/**
* @brief Destroys Find Friend Menu widget
*/
void CloseFindFriendsMenu();

protected:

/**
* @brief Login Menu widget class
*/
UPROPERTY(EditDefaultsOnly)
TSubclassOf<UAccelByteAuth> LoginMenuClass;

/**
* @brief Lobby Menu widget class
*/
UPROPERTY(EditDefaultsOnly)
TSubclassOf<UAccelByteLobby> LobbyMenuClass;

/**
* @brief Main Menu widget class
*/
UPROPERTY(EditDefaultsOnly)
TSubclassOf<UAccelByteMainMenu> MainMenuClass;

/**
* @brief Friends Menu widget class
*/
UPROPERTY(EditDefaultsOnly)
TSubclassOf<UAccelByteFriends> FriendsMenuClass;

/**
* @brief Find Friends Menu widget class
*/
UPROPERTY(EditDefaultsOnly)
TSubclassOf<UAccelByteFindFriend> FindFriendsMenuClass;

public:

/**
* @brief Getter for Login Menu widget
*/
UAccelByteAuth* GetLoginMenu() const {return LoginMenu; }

/**
* @brief Getter for Main Menu widget
*/
UAccelByteMainMenu* GetMainMenu() const {return MainMenu; }

/**
* @brief Getter for Lobby Menu widget
*/
UAccelByteLobby* GetLobbyMenu() const {return LobbyMenu; }

/**
* @brief Getter for Friends Menu widget
*/
UAccelByteFriends* GetFriendsMenu() const {return FriendsMenu; }

/**
* @brief Getter for Find Friends Menu widget
*/
UAccelByteFindFriend* GetFindFriendsMenu() const {return FindFriendsMenu; }

private:

/**
* @brief Login Menu widget pointer
*/
UPROPERTY()
UAccelByteAuth* LoginMenu;

/**
* @brief Lobby Menu widget pointer
*/
UPROPERTY()
UAccelByteLobby* LobbyMenu;

/**
* @brief Main Menu widget pointer
*/
UPROPERTY()
UAccelByteMainMenu* MainMenu;

/**
* @brief Friends Menu widget pointer
*/
UPROPERTY()
UAccelByteFriends* FriendsMenu;

/**
* @brief Find Friends Menu widget pointer
*/
UPROPERTY()
UAccelByteFindFriend* FindFriendsMenu;
};
TutorialMenuHUD.cpp
// Copyright (c) 2024 AccelByte Inc. All Rights Reserved.
// This is licensed software from AccelByte Inc, for limitations
// and restrictions contact your company contract manager.

#include "TutorialMenuHUD.h"
#include "AccelByte/Authentication/AccelByteAuth.h"
#include "AccelByte/Lobby/AccelByteLobby.h"
#include "AccelByte/AccelByteMainMenu.h"
#include "AccelByte/Friends/AccelbyteFindFriend.h"
#include "AccelByte/Friends/AccelByteFriends.h"

void ATutorialMenuHUD::BeginPlay()
{
Super::BeginPlay();

APlayerController* PlayerController = GetOwningPlayerController();

check(LoginMenuClass != nullptr);
check(MainMenuClass != nullptr);
check(LobbyMenuClass != nullptr);
check(FriendsMenuClass != nullptr);
check(FindFriendsMenuClass != nullptr);

LoginMenu = CreateWidget<UAccelByteAuth>(PlayerController, LoginMenuClass.Get());
MainMenu = CreateWidget<UAccelByteMainMenu>(PlayerController, MainMenuClass.Get());
LobbyMenu = CreateWidget<UAccelByteLobby>(PlayerController, LobbyMenuClass.Get());
FriendsMenu = CreateWidget<UAccelByteFriends>(PlayerController, FriendsMenuClass.Get());
FindFriendsMenu = CreateWidget<UAccelByteFindFriend>(PlayerController, FindFriendsMenuClass.Get());
}

void ATutorialMenuHUD::OpenLoginMenu()
{
LoginMenu->AddToViewport();
}

void ATutorialMenuHUD::OpenMainMenu()
{
MainMenu->AddToViewport();
}

void ATutorialMenuHUD::InitMainMenu()
{
// Add Lobby to Viewport and set its visibility as collapsed
LobbyMenu->AddToViewport();
LobbyMenu->ConnectToLobby();
LobbyMenu->SetVisibility(ESlateVisibility::Collapsed);

OpenMainMenu();
}

void ATutorialMenuHUD::OpenLobbyMenu()
{
LobbyMenu->SetVisibility(ESlateVisibility::Visible);
MainMenu->RemoveFromParent();
}

void ATutorialMenuHUD::OpenFriendsMenu()
{
if (!FriendsMenu->IsInViewport())
{
FriendsMenu->AddToViewport();
}
else
{
FriendsMenu->SetVisibility(ESlateVisibility::SelfHitTestInvisible);
FriendsMenu->RefreshFriendsList();
}
}

void ATutorialMenuHUD::OpenFindFriendsMenu()
{
FindFriendsMenu->AddToViewport();
}

void ATutorialMenuHUD::CloseMainMenu()
{
LoginMenu->AddToViewport();
MainMenu->RemoveFromViewport();
LobbyMenu->RemoveFromParent();
}

void ATutorialMenuHUD::CloseFindFriendsMenu()
{
FindFriendsMenu->RemoveFromParent();
}

void ATutorialMenuHUD::CloseLobbyMenu()
{
MainMenu->AddToViewport();
LobbyMenu->SetVisibility(ESlateVisibility::Collapsed);
}

void ATutorialMenuHUD::CloseFriendMenu()
{
if (!LobbyMenu->IsInViewport())
{
LobbyMenu->AddToViewport();
}
FriendsMenu->SetVisibility(ESlateVisibility::Collapsed);
}
AccelByteLobby.h
// Copyright (c) 2024 AccelByte Inc. All Rights Reserved.
// This is licensed software from AccelByte Inc, for limitations
// and restrictions contact your company contract manager.

#pragma once

#include "CoreMinimal.h"
#include "AccelByteLobby.generated.h"

class ATutorialMenuHUD;
class UButton;

/**
* Component for joining the AccelByte Lobby.
* This code covers AccelByte services including:
*
* - Join Lobby
* - Leave Lobby
*/
UCLASS()
class TUTORIALPROJECT_API UAccelByteLobby : public UUserWidget
{
GENERATED_BODY()

#pragma region Initialization

protected:

virtual void NativeConstruct() override;

#pragma endregion

#pragma region Widget Components

protected:

/**
* @brief Button for Leave Lobby and back to Main Menu.
*/
UPROPERTY(meta = (BindWidget))
UButton* Btn_LeaveLobby;

/**
* @brief Button for go to Friend Management Menu.
*/
UPROPERTY(meta = (BindWidget))
UButton* Btn_FriendsManagement;

#pragma endregion
#pragma region Widget Callbacks

private:

/**
* @brief Button callback on Open Friends Management.
*/
UFUNCTION()
void OnClickedOpenFriendsManagement();

#pragma endregion
#pragma region Utilities

public:

/**
* @brief Connect to AccelByte lobby.
*/
UFUNCTION()
void ConnectToLobby();

/**
* @brief Set Lobby services notification delegates.
*/
void SetLobbyNotificationDelegate();

private:

/**
* @brief Tutorial Menu HUD pointer reference.
*/
UPROPERTY()
ATutorialMenuHUD* TutorialMenuHUD;

#pragma endregion
};
AccelByteLobby.cpp
// Copyright (c) 2024 AccelByte Inc. All Rights Reserved.
// This is licensed software from AccelByte Inc, for limitations
// and restrictions contact your company contract manager.

#include "AccelByteLobby.h"
#include "Api/AccelByteLobbyApi.h"
#include "Core/AccelByteRegistry.h"
#include "Components/Button.h"
#include "TutorialProject/TutorialMenuHUD.h"

#pragma region Initialization

void UAccelByteLobby::NativeConstruct()
{
Super::NativeConstruct();

check(GetOwningPlayer() != nullptr);
check(GetOwningPlayer()->GetHUD() != nullptr);

TutorialMenuHUD = Cast<ATutorialMenuHUD>(GetOwningPlayer()->GetHUD());

Btn_LeaveLobby->OnClicked.AddUniqueDynamic(TutorialMenuHUD, &ATutorialMenuHUD::CloseLobbyMenu);
Btn_FriendsManagement->OnClicked.AddUniqueDynamic(this, &UAccelByteLobby::OnClickedOpenFriendsManagement);
}

#pragma endregion
#pragma region Widget Callbacks

void UAccelByteLobby::OnClickedOpenFriendsManagement()
{
TutorialMenuHUD->OpenFriendsMenu();
}

#pragma endregion
#pragma region Utilities

void UAccelByteLobby::ConnectToLobby()
{
SetLobbyNotificationDelegate();
FRegistry::Lobby.Connect();
}

void UAccelByteLobby::SetLobbyNotificationDelegate()
{
FRegistry::Lobby.SetConnectSuccessDelegate(FSimpleDelegate::CreateWeakLambda(this, [this]()
{
UE_LOG(LogTemp, Log, TEXT("Successfully Connected to Lobby"));
}));

FRegistry::Lobby.SetConnectFailedDelegate(FErrorHandler::CreateWeakLambda(this, [](int32 Code, const FString& Message)
{
UE_LOG(LogTemp, Error, TEXT("Failed Connect to Lobby : Code: %d; Message: %s"), Code, *Message);
}));

FRegistry::Lobby.SetErrorNotifDelegate(FErrorHandler::CreateWeakLambda(this, [](int32 Code, const FString& Message)
{
UE_LOG(LogTemp, Error, TEXT("Error Connect to Lobby : Code: %d; Message: %s"), Code, *Message);
}));

FRegistry::Lobby.SetConnectionClosedDelegate(Api::Lobby::FConnectionClosed::CreateWeakLambda(this, [this](int32 StatusCode, const FString& Reason, bool bWasClean)
{
UE_LOG(LogTemp, Error, TEXT("Connection Closed, Code: %d Reason: %s Clean: %s"), StatusCode, *Reason, bWasClean ? TEXT("true") : TEXT("false"));
}));

FRegistry::Lobby.SetDisconnectNotifDelegate(Api::Lobby::FDisconnectNotif::CreateWeakLambda(this, [this](const FAccelByteModelsDisconnectNotif& Result)
{
UE_LOG(LogTemp, Log, TEXT("Disconnected from Lobby"));
}));
}

#pragma endregion
AccelByteAuth.cpp
// Copyright (c) 2024 AccelByte Inc. All Rights Reserved.
// This is licensed software from AccelByte Inc, for limitations
// and restrictions contact your company contract manager.

#include "AccelByteAuth.h"
#include "Api/AccelByteUserApi.h"
#include "Core/AccelByteRegistry.h"
#include "Components/Button.h"
#include "Components/EditableTextBox.h"
#include "Components/TextBlock.h"
#include "TutorialProject/TutorialMenuHUD.h"

void UAccelByteAuth::NativeConstruct()
{
Super::NativeConstruct();

TutorialMenuHUD = Cast<ATutorialMenuHUD>(GetWorld()->GetFirstPlayerController()->GetHUD());

UE_LOG(LogTemp, Log, TEXT("Login with Username"));

T_LoginStatus->SetText(FText::FromString("Please Login"));
Btn_Login->OnClicked.AddUniqueDynamic(this, &UAccelByteAuth::OnLoginButtonClicked);
}

void UAccelByteAuth::OnLoginButtonClicked()
{
T_LoginStatus->SetText(FText::FromString("Logging in..."));

FRegistry::User.LoginWithUsername(
Etb_Username->GetText().ToString(),
Etb_Password->GetText().ToString(),
FVoidHandler::CreateUObject(this, &UAccelByteAuth::LoginSuccess),
FCustomErrorHandler::CreateUObject(this, &UAccelByteAuth::LoginFailed));
}

void UAccelByteAuth::OnLogoutButtonClicked()
{
FRegistry::User.Logout(
FVoidHandler::CreateUObject(this, &UAccelByteAuth::LogoutSuccess),
FErrorHandler::CreateUObject(this, &UAccelByteAuth::LogoutFailed));

if (FRegistry::Lobby.IsConnected())
{
FRegistry::Lobby.Disconnect();
}
}

void UAccelByteAuth::LoginSuccess()
{
UE_LOG(LogTemp, Log, TEXT("Login Success"));
T_LoginStatus->SetText(FText::FromString("Login successful"));

if(GetOwningPlayer() && GetOwningPlayer()->GetHUD())
{
TutorialMenuHUD->InitMainMenu();
this->RemoveFromParent();
}
}

void UAccelByteAuth::LoginFailed(int32 ErrorCode, const FString& ErrorMessage, const FJsonObject& ErrorJson)
{
UE_LOG(LogTemp, Error, TEXT("Login Failed : %d , %s"), ErrorCode, *ErrorMessage);
T_LoginStatus->SetText(FText::FromString(FString::Printf(TEXT("Login Failed : %d , %s"), ErrorCode, *ErrorMessage)));
}

void UAccelByteAuth::LogoutSuccess()
{
UE_LOG(LogTemp, Log, TEXT("Logout Success"));

if (TutorialMenuHUD == nullptr)
{
TutorialMenuHUD = Cast<ATutorialMenuHUD>(GetOwningPlayer()->GetHUD());
}

ensure(TutorialMenuHUD != nullptr);
TutorialMenuHUD->CloseMainMenu();
}

void UAccelByteAuth::LogoutFailed(int32 ErrorCode, const FString& ErrorMessage)
{
UE_LOG(LogTemp, Error, TEXT("Logout Failed : %d , %s"), ErrorCode, *ErrorMessage);
}