Files
Racoon-MinecraftConsoles/Minecraft.Client/Common/UI/UIScene_LceLiveRequests.cpp
veroxsity e93d57b2bc Add LceLive Friends and Requests UI Scenes
- Implement UIScene_LceLiveFriends for managing friends list with options to add, refresh, and remove friends.
- Implement UIScene_LceLiveRequests for handling incoming and outgoing friend requests with options to accept or decline requests.
- Introduce UIScene_LceLiveLinking for device linking functionality.
- Update CMake files to include new UI scene source files.
- Add necessary UI components and logic for displaying and interacting with friends and requests.
- Ensure compatibility with Windows64 builds and handle UI updates accordingly.
2026-04-17 00:11:35 +01:00

446 lines
11 KiB
C++

#include "stdafx.h"
#include "UI.h"
#include "UIScene_LceLiveRequests.h"
#include "../../Minecraft.h"
// Fallbacks until string ID headers are regenerated.
#ifndef IDS_TITLE_FRIEND_REQUEST
#define IDS_TITLE_FRIEND_REQUEST IDS_TOOLTIPS_SELECT
#endif
#ifndef IDS_TEXT_ACCEPT_REQUEST_CONFIRMATION
#define IDS_TEXT_ACCEPT_REQUEST_CONFIRMATION IDS_CONFIRM_EXIT_GAME
#endif
#ifdef _WINDOWS64
namespace
{
std::wstring Utf8ToWideLocal(const std::string &text)
{
if (text.empty())
return L"";
const int required = MultiByteToWideChar(CP_UTF8, 0, text.c_str(), -1, nullptr, 0);
if (required <= 0)
return L"";
std::wstring result(static_cast<size_t>(required), L'\0');
MultiByteToWideChar(CP_UTF8, 0, text.c_str(), -1, &result[0], required);
if (!result.empty() && result.back() == L'\0')
result.pop_back();
return result;
}
std::wstring BuildRequestLabel(bool incoming, const std::string &username, const std::string &displayName)
{
std::wstring line = incoming ? L"[IN] " : L"[OUT] ";
if (!displayName.empty())
line += Utf8ToWideLocal(displayName);
else if (!username.empty())
line += Utf8ToWideLocal(username);
else
line += L"<unknown>";
if (!username.empty())
{
line += L" (@";
line += Utf8ToWideLocal(username);
line += L")";
}
return line;
}
}
#endif
UIScene_LceLiveRequests::UIScene_LceLiveRequests(int iPad, void *initData, UILayer *parentLayer) : UIScene(iPad, parentLayer)
{
initialiseMovie();
parentLayer->addComponent(iPad, eUIComponent_Panorama);
parentLayer->addComponent(iPad, eUIComponent_Logo);
m_requestsList.init(eControl_RequestsList);
m_actionsList.init(eControl_ActionsList);
m_labelRequestsTitle.init(L"REQUESTS");
m_labelActionsTitle.init(L"ACTIONS");
m_labelStatus.init(L"");
m_controlRequestsTimer.setVisible(false);
m_controlActionsTimer.setVisible(false);
m_actionsList.addItem(L"REFRESH");
m_actionsList.setCurrentSelection(eAction_Refresh);
m_entries.clear();
m_statusMessage.clear();
m_bDataReady = false;
#ifdef _WINDOWS64
m_pendingRequestAccountId.clear();
m_pendingRequestIncoming = false;
#endif
doHorizontalResizeCheck();
FetchAndDisplay();
}
UIScene_LceLiveRequests::~UIScene_LceLiveRequests()
{
m_parentLayer->removeComponent(eUIComponent_Panorama);
m_parentLayer->removeComponent(eUIComponent_Logo);
}
wstring UIScene_LceLiveRequests::getMoviePath()
{
return L"LoadOrJoinMenu";
}
void UIScene_LceLiveRequests::updateTooltips()
{
ui.SetTooltips(m_iPad, IDS_TOOLTIPS_SELECT, IDS_TOOLTIPS_BACK, IDS_TOOLTIPS_DELETE, IDS_TOOLTIPS_REFRESH);
}
void UIScene_LceLiveRequests::updateComponents()
{
const bool notInGame = (Minecraft::GetInstance()->level == nullptr);
m_parentLayer->showComponent(m_iPad, eUIComponent_Panorama, notInGame);
m_parentLayer->showComponent(m_iPad, eUIComponent_Logo, true);
}
void UIScene_LceLiveRequests::handleReload()
{
doHorizontalResizeCheck();
m_controlRequestsTimer.setVisible(false);
m_controlActionsTimer.setVisible(false);
FetchAndDisplay();
}
void UIScene_LceLiveRequests::handleFocusChange(F64 controlId, F64 childId)
{
if (static_cast<int>(controlId) == eControl_RequestsList)
m_requestsList.updateChildFocus(static_cast<int>(childId));
else if (static_cast<int>(controlId) == eControl_ActionsList)
m_actionsList.updateChildFocus(static_cast<int>(childId));
updateTooltips();
}
void UIScene_LceLiveRequests::handleInput(int iPad, int key, bool repeat, bool pressed, bool released, bool &handled)
{
ui.AnimateKeyPress(m_iPad, key, repeat, pressed, released);
switch (key)
{
case ACTION_MENU_CANCEL:
if (pressed && !repeat)
{
ui.PlayUISFX(eSFX_Back);
navigateBack();
}
break;
case ACTION_MENU_OK:
#ifdef __ORBIS__
case ACTION_MENU_TOUCHPAD_PRESS:
#endif
if (pressed)
ui.PlayUISFX(eSFX_Press);
if (pressed && !repeat)
{
handled = true;
if (controlHasFocus(eControl_ActionsList))
PerformSelectedAction();
else if (controlHasFocus(eControl_RequestsList))
PromptResolveSelectedRequest();
}
break;
case ACTION_MENU_X:
if (pressed && !repeat)
{
PerformDecline();
handled = true;
}
break;
case ACTION_MENU_Y:
if (pressed && !repeat)
{
FetchAndDisplay();
handled = true;
}
break;
case ACTION_MENU_UP:
case ACTION_MENU_DOWN:
case ACTION_MENU_LEFT:
case ACTION_MENU_RIGHT:
case ACTION_MENU_PAGEUP:
case ACTION_MENU_PAGEDOWN:
sendInputToMovie(key, repeat, pressed, released);
break;
}
}
void UIScene_LceLiveRequests::handlePress(F64 controlId, F64 childId)
{
if (static_cast<int>(controlId) == eControl_RequestsList)
{
m_requestsList.updateChildFocus(static_cast<int>(childId));
PromptResolveSelectedRequest();
}
else if (static_cast<int>(controlId) == eControl_ActionsList)
{
m_actionsList.updateChildFocus(static_cast<int>(childId));
PerformSelectedAction();
}
}
void UIScene_LceLiveRequests::FetchAndDisplay()
{
#ifdef _WINDOWS64
const std::string accessToken = Win64LceLive::GetAccessToken();
if (accessToken.empty())
{
m_entries.clear();
m_bDataReady = true;
m_statusMessage = L"Sign in to view and manage friend requests.";
RebuildLists();
return;
}
const int previousSelection = m_requestsList.getCurrentSelection();
const Win64LceLive::PendingRequestsResult result = Win64LceLive::GetPendingRequestsSync();
if (!result.success)
{
m_entries.clear();
m_bDataReady = true;
m_statusMessage = Utf8ToWideLocal(result.error);
RebuildLists();
return;
}
m_entries.clear();
for (const Win64LceLive::SocialEntry &entry : result.incoming)
{
RequestEntry row = {};
row.incoming = true;
row.accountId = entry.accountId;
row.username = entry.username;
row.displayName = entry.displayName;
m_entries.push_back(row);
}
for (const Win64LceLive::SocialEntry &entry : result.outgoing)
{
RequestEntry row = {};
row.incoming = false;
row.accountId = entry.accountId;
row.username = entry.username;
row.displayName = entry.displayName;
m_entries.push_back(row);
}
m_bDataReady = true;
if (m_entries.empty() && m_statusMessage.empty())
m_statusMessage = L"No pending requests.";
else if (!m_entries.empty() && m_statusMessage == L"No pending requests.")
m_statusMessage.clear();
RebuildLists();
if (!m_entries.empty())
{
int newSelection = previousSelection;
if (newSelection < 0)
newSelection = 0;
if (newSelection >= static_cast<int>(m_entries.size()))
newSelection = static_cast<int>(m_entries.size()) - 1;
m_requestsList.setCurrentSelection(newSelection);
}
#else
m_entries.clear();
m_bDataReady = true;
m_statusMessage = L"Requests are only available on Windows64 builds.";
RebuildLists();
#endif
}
void UIScene_LceLiveRequests::RebuildLists()
{
m_requestsList.clearList();
#ifdef _WINDOWS64
for (const RequestEntry &entry : m_entries)
m_requestsList.addItem(BuildRequestLabel(entry.incoming, entry.username, entry.displayName));
#endif
UpdateStatusLabel();
}
void UIScene_LceLiveRequests::UpdateStatusLabel()
{
if (m_statusMessage.empty())
{
m_labelStatus.setVisible(false);
}
else
{
m_labelStatus.setLabel(m_statusMessage, true, true);
m_labelStatus.setVisible(true);
}
m_labelRequestsTitle.setLabel(L"REQUESTS", true, true);
m_labelActionsTitle.setLabel(L"ACTIONS", true, true);
}
int UIScene_LceLiveRequests::FocusedRequestIndex()
{
return m_requestsList.getCurrentSelection();
}
int UIScene_LceLiveRequests::SelectedActionIndex()
{
return m_actionsList.getCurrentSelection();
}
void UIScene_LceLiveRequests::PerformSelectedAction()
{
switch (SelectedActionIndex())
{
case eAction_Refresh:
FetchAndDisplay();
break;
default:
break;
}
}
void UIScene_LceLiveRequests::PromptResolveSelectedRequest()
{
const int selectedIndex = FocusedRequestIndex();
if (selectedIndex < 0 || selectedIndex >= static_cast<int>(m_entries.size()))
{
m_statusMessage = L"Select a request first.";
UpdateStatusLabel();
return;
}
if (!m_entries[selectedIndex].incoming)
{
m_statusMessage = L"Outgoing requests are waiting on the other player.";
UpdateStatusLabel();
return;
}
#ifdef _WINDOWS64
m_pendingRequestAccountId = m_entries[selectedIndex].accountId;
m_pendingRequestIncoming = true;
UINT optionIds[2];
optionIds[0] = IDS_NO;
optionIds[1] = IDS_YES;
ui.RequestAlertMessage(
IDS_TITLE_FRIEND_REQUEST,
IDS_TEXT_ACCEPT_REQUEST_CONFIRMATION,
optionIds,
2,
m_iPad,
&UIScene_LceLiveRequests::ResolveRequestConfirmCallback,
this);
#endif
}
void UIScene_LceLiveRequests::PerformAccept()
{
#ifdef _WINDOWS64
const int selectedIndex = FocusedRequestIndex();
if (selectedIndex < 0 || selectedIndex >= static_cast<int>(m_entries.size()))
{
m_statusMessage = L"Select a request first.";
UpdateStatusLabel();
return;
}
if (!m_entries[selectedIndex].incoming)
{
m_statusMessage = L"Only incoming requests can be accepted.";
UpdateStatusLabel();
return;
}
const std::string accountId = m_entries[selectedIndex].accountId;
const Win64LceLive::SocialActionResult result = Win64LceLive::AcceptFriendRequestSync(accountId);
if (result.success)
m_statusMessage = L"Friend request accepted.";
else
m_statusMessage = Utf8ToWideLocal(result.error);
FetchAndDisplay();
#endif
}
void UIScene_LceLiveRequests::PerformDecline()
{
#ifdef _WINDOWS64
const int selectedIndex = FocusedRequestIndex();
if (selectedIndex < 0 || selectedIndex >= static_cast<int>(m_entries.size()))
{
m_statusMessage = L"Select a request first.";
UpdateStatusLabel();
return;
}
if (!m_entries[selectedIndex].incoming)
{
m_statusMessage = L"Only incoming requests can be declined.";
UpdateStatusLabel();
return;
}
const std::string accountId = m_entries[selectedIndex].accountId;
const Win64LceLive::SocialActionResult result = Win64LceLive::DeclineFriendRequestSync(accountId);
if (result.success)
m_statusMessage = L"Friend request declined.";
else
m_statusMessage = Utf8ToWideLocal(result.error);
FetchAndDisplay();
#endif
}
#ifdef _WINDOWS64
void UIScene_LceLiveRequests::ResolvePendingRequest(bool accept)
{
if (!m_pendingRequestIncoming || m_pendingRequestAccountId.empty())
return;
const std::string accountId = m_pendingRequestAccountId;
m_pendingRequestAccountId.clear();
m_pendingRequestIncoming = false;
const Win64LceLive::SocialActionResult result = accept
? Win64LceLive::AcceptFriendRequestSync(accountId)
: Win64LceLive::DeclineFriendRequestSync(accountId);
if (result.success)
m_statusMessage = accept ? L"Friend request accepted." : L"Friend request declined.";
else
m_statusMessage = Utf8ToWideLocal(result.error);
FetchAndDisplay();
}
int UIScene_LceLiveRequests::ResolveRequestConfirmCallback(void *pParam, int iPad, C4JStorage::EMessageResult result)
{
UIScene_LceLiveRequests *scene = static_cast<UIScene_LceLiveRequests *>(pParam);
if (scene == nullptr)
return 0;
(void)iPad;
// UI returns "Decline" for the 2nd option. With [NO, YES], that means YES => accept.
if (result == C4JStorage::EMessage_ResultDecline)
scene->ResolvePendingRequest(true);
else
scene->ResolvePendingRequest(false);
return 0;
}
#endif