From a0a859aa868cdf56bd81534404e9d85656e0a808 Mon Sep 17 00:00:00 2001
From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com>
Date: Tue, 19 Aug 2025 23:33:53 -0500
Subject: [PATCH] [ShareBubbles] Fixes for 2025r180
---
ShareBubbles/Main.cs | 2 +-
ShareBubbles/Patches.cs | 1631 +----------------
ShareBubbles/Properties/AssemblyInfo.cs | 4 +-
.../API/Exceptions/ShareApiExceptions.cs | 66 -
.../API/PedestalInfoBatchProcessor.cs | 3 +-
.../API/Responses/ActiveSharesResponse.cs | 22 -
.../ShareBubbles/API/ShareApiHelper.cs | 237 ---
.../Implementation/AvatarBubbleImpl.cs | 4 +-
.../Implementation/SpawnableBubbleImpl.cs | 4 +-
.../Implementation/TempShareManager.cs | 4 +-
.../Networking/ModNetwork.Inbound.cs | 4 +-
.../Networking/ModNetwork.Outbound.cs | 4 +-
.../ShareBubbles/UI/BubbleInteract.cs | 18 +-
ShareBubbles/format.json | 10 +-
14 files changed, 60 insertions(+), 1953 deletions(-)
delete mode 100644 ShareBubbles/ShareBubbles/API/Exceptions/ShareApiExceptions.cs
delete mode 100644 ShareBubbles/ShareBubbles/API/Responses/ActiveSharesResponse.cs
delete mode 100644 ShareBubbles/ShareBubbles/API/ShareApiHelper.cs
diff --git a/ShareBubbles/Main.cs b/ShareBubbles/Main.cs
index 72f3c3d..802a6be 100644
--- a/ShareBubbles/Main.cs
+++ b/ShareBubbles/Main.cs
@@ -28,7 +28,7 @@ public class ShareBubblesMod : MelonMod
LoadAssetBundle();
}
-
+
public override void OnApplicationQuit()
{
ModNetwork.Unsubscribe();
diff --git a/ShareBubbles/Patches.cs b/ShareBubbles/Patches.cs
index 5f95231..eaf2440 100644
--- a/ShareBubbles/Patches.cs
+++ b/ShareBubbles/Patches.cs
@@ -1,14 +1,6 @@
using ABI_RC.Core.InteractionSystem;
-using ABI_RC.Core.Networking.API.Responses;
-using ABI_RC.Core.Networking.API.UserWebsocket;
using ABI_RC.Core.Player;
-using ABI_RC.Core.Savior;
using HarmonyLib;
-using MTJobSystem;
-using NAK.ShareBubbles.API;
-using NAK.ShareBubbles.API.Exceptions;
-using NAK.ShareBubbles.API.Responses;
-using Newtonsoft.Json;
namespace NAK.ShareBubbles.Patches;
@@ -68,1419 +60,31 @@ internal static class ControllerRay_Patches
internal static class ViewManager_Patches
{
-
-private const string DETAILS_TOOLBAR_PATCHES = """
-
-const ContentShareMod = {
- debugMode: false,
- currentContentData: null,
- themeColors: null,
-
- /* Theme Handling */
-
- getThemeColors: function() {
- if (this.themeColors) return this.themeColors;
-
- // Default fallback colors
- const defaultColors = {
- background: '#373021',
- border: '#59885d'
- };
-
- // Try to get colors from favorite category element
- const favoriteCategoryElement = document.querySelector('.favorite-category-selection');
- if (!favoriteCategoryElement) return defaultColors;
-
- const computedStyle = window.getComputedStyle(favoriteCategoryElement);
- this.themeColors = {
- background: computedStyle.backgroundColor || defaultColors.background,
- border: computedStyle.borderColor || defaultColors.border
- };
-
- return this.themeColors;
- },
-
- applyThemeToDialog: function(dialog) {
- const colors = this.getThemeColors();
- dialog.style.backgroundColor = colors.background;
- dialog.style.borderColor = colors.border;
-
- // Update any close or page buttons to match theme
- const buttons = dialog.querySelectorAll('.close-btn, .page-btn');
- buttons.forEach(button => {
- button.style.borderColor = colors.border;
- });
-
- return colors;
- },
-
- /* Core Initialization */
-
- init: function() {
- const styles = [
- this.getSharedStyles(),
- this.ShareBubble.initStyles(),
- this.ShareSelect.initStyles(),
- this.DirectShare.initStyles(),
- this.Unshare.initStyles()
- ].join('\n');
-
- const styleElement = document.createElement('style');
- styleElement.type = 'text/css';
- styleElement.innerHTML = styles;
- document.head.appendChild(styleElement);
-
- this.shareBubbleDialog = this.ShareBubble.createDialog();
- this.shareSelectDialog = this.ShareSelect.createDialog();
- this.directShareDialog = this.DirectShare.createDialog();
- this.unshareDialog = this.Unshare.createDialog();
-
- this.initializeToolbars();
- this.bindEvents();
- },
-
- getSharedStyles: function() {
- return `
- .content-sharing-base-dialog {
- position: fixed;
- background-color: #373021;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- width: 800px;
- min-width: 500px;
- border: 3px solid #59885d;
- padding: 20px;
- z-index: 100000;
- opacity: 0;
- transition: opacity 0.2s linear;
- }
-
- .content-sharing-base-dialog.in {
- opacity: 1;
- }
-
- .content-sharing-base-dialog.out {
- opacity: 0;
- }
-
- .content-sharing-base-dialog.hidden {
- display: none;
- }
-
- .content-sharing-base-dialog h2,
- .content-sharing-base-dialog h3 {
- margin-top: 0;
- margin-bottom: 0.5em;
- text-align: left;
- }
-
- .content-sharing-base-dialog .description {
- margin-bottom: 1em;
- text-align: left;
- font-size: 0.9em;
- color: #aaa;
- }
-
- .content-sharing-base-dialog .close-btn {
- position: absolute;
- top: 1%;
- right: 1%;
- border-radius: 0.25em;
- border: 3px solid #59885d;
- padding: 0.5em;
- width: 8em;
- text-align: center;
- }
-
- .page-btn {
- border-radius: 0.25em;
- border: 3px solid #59885d;
- padding: 0.5em;
- width: 8em;
- text-align: center;
- }
-
- .page-btn:disabled {
- opacity: 0.5;
- cursor: not-allowed;
- }
-
- .inp-hidden {
- display: none;
- }
- `;
- },
-
- /* Feature Modules */
-
- ShareBubble: {
- initStyles: function() {
- return `
- .share-bubble-dialog {
- max-width: 800px;
- transform: translate(-50%, -60%);
- }
-
- .share-bubble-dialog .content-btn {
- position: relative;
- margin-bottom: 0.5em;
- }
-
- .share-bubble-dialog .btn-group {
- display: flex;
- margin-bottom: 1em;
- }
-
- .share-bubble-dialog .option-select {
- flex: 1 1 0;
- min-width: 0;
- background-color: inherit;
- text-align: center;
- padding: 0.5em;
- cursor: pointer;
- display: flex;
- align-items: center;
- justify-content: center;
- border: 1px solid inherit;
- }
-
- .share-bubble-dialog .option-select + .option-select {
- margin-left: -1px;
- }
-
- .share-bubble-dialog .option-select.active,
- .share-bubble-dialog .option-select:hover {
- background-color: rgba(27, 80, 55, 1);
- }
-
- .share-bubble-dialog .action-buttons {
- margin-top: 1em;
- display: flex;
- justify-content: space-between;
- }
-
- .share-bubble-dialog .action-btn {
- flex: 1;
- text-align: center;
- padding: 0.5em;
- border-radius: 0.25em;
- cursor: pointer;
- display: flex;
- align-items: center;
- justify-content: center;
- margin-right: 0.5em;
- }
-
- .share-bubble-dialog .action-btn:last-child {
- margin-right: 0;
- }
- `;
- },
-
- createDialog: function() {
- const dialog = document.createElement('div');
- dialog.id = 'content-share-bubble-dialog';
- dialog.className = 'content-sharing-base-dialog share-bubble-dialog hidden';
- dialog.innerHTML = `
-
Share Bubble
- Close
-
- Visibility
- Choose who can see your Sharing Bubble.
-
-
Everyone
-
Friends Only
-
-
- Lifetime
- How long the Sharing Bubble lasts. You can delete it at any time.
-
-
2 Minutes
-
For Session
-
-
-
-
Access Control
-
-
- Users will keep access to this Private Content. You can manage permissions later on the Hub.
-
-
- Users can only access this Private Content while in the same instance.
-
-
- Users cannot access your Private Content through this share.
-
-
-
-
Keep
-
Instance Only
-
None
-
-
-
-
-
Access Control
-
You cannot control access to this content because it is Public or you do not own it.
-
-
-
-
-
-
-
- `;
- document.body.appendChild(dialog);
- return dialog;
- },
-
- show: function(contentDetails) {
- const dialog = ContentShareMod.shareBubbleDialog;
- const colors = ContentShareMod.applyThemeToDialog(dialog);
-
- // Additional ShareBubble-specific theming
- const optionSelects = dialog.querySelectorAll('.option-select');
- optionSelects.forEach(element => {
- element.style.borderColor = colors.border;
- });
-
- const grantAccessSection = dialog.querySelector('.grant-access-section');
- const noAccessControlMessage = dialog.querySelector('.no-access-control-message');
- const showGrantAccess = contentDetails.IsMine && !contentDetails.IsPublic;
-
- if (grantAccessSection && noAccessControlMessage) {
- grantAccessSection.style.display = showGrantAccess ? '' : 'none';
- noAccessControlMessage.style.display = showGrantAccess ? 'none' : '';
- }
-
- dialog.classList.remove('hidden', 'out');
- setTimeout(() => dialog.classList.add('in'), 50);
-
- ContentShareMod.currentContentData = contentDetails;
- },
-
- hide: function() {
- const dialog = ContentShareMod.shareBubbleDialog;
- dialog.classList.remove('in');
- dialog.classList.add('out');
- setTimeout(() => {
- dialog.classList.add('hidden');
- dialog.classList.remove('out');
- }, 200);
- },
-
- changeVisibility: function(element) {
- document.getElementById('share-visibility').value = element.dataset.visibilityValue;
- const buttons = ContentShareMod.shareBubbleDialog.querySelectorAll('.visibility-btn');
- buttons.forEach(btn => btn.classList.remove('active'));
- element.classList.add('active');
- },
-
- changeDuration: function(element) {
- document.getElementById('share-duration').value = element.dataset.durationValue;
- const buttons = ContentShareMod.shareBubbleDialog.querySelectorAll('.duration-btn');
- buttons.forEach(btn => btn.classList.remove('active'));
- element.classList.add('active');
- },
-
- changeAccess: function(element) {
- document.getElementById('share-access').value = element.dataset.accessValue;
- const buttons = ContentShareMod.shareBubbleDialog.querySelectorAll('.access-btn');
- buttons.forEach(btn => btn.classList.remove('active'));
- element.classList.add('active');
-
- const descriptions = ContentShareMod.shareBubbleDialog.querySelectorAll('.access-desc');
- descriptions.forEach(desc => {
- desc.style.display = desc.dataset.accessType === element.dataset.accessValue ? '' : 'none';
- });
- },
-
- submit: function(action) {
- const contentDetails = ContentShareMod.currentContentData;
- let bubbleImpl, bubbleContent, contentImage, contentName;
-
- if (contentDetails.AvatarId) {
- bubbleImpl = 'Avatar';
- bubbleContent = contentDetails.AvatarId;
- contentImage = contentDetails.AvatarImageCoui;
- contentName = contentDetails.AvatarName;
- } else if (contentDetails.SpawnableId) {
- bubbleImpl = 'Spawnable';
- bubbleContent = contentDetails.SpawnableId;
- contentImage = contentDetails.SpawnableImageCoui;
- contentName = contentDetails.SpawnableName;
- } else if (contentDetails.WorldId) {
- bubbleImpl = 'World';
- bubbleContent = contentDetails.WorldId;
- contentImage = contentDetails.WorldImageCoui;
- contentName = contentDetails.WorldName;
- } else if (contentDetails.UserId) {
- bubbleImpl = 'User';
- bubbleContent = contentDetails.UserId;
- contentImage = contentDetails.UserImageCoui;
- contentName = contentDetails.UserName;
- } else {
- console.error('No valid content ID found');
- return;
- }
-
- const visibility = document.getElementById('share-visibility').value;
- const duration = document.getElementById('share-duration').value;
- const access = document.getElementById('share-access').value;
-
- const shareRule = visibility === 'FriendsOnly' ? 'FriendsOnly' : 'Everyone';
- const shareLifetime = duration === 'Session' ? 'Session' : 'TwoMinutes';
- const shareAccess = access === 'Permanent' ? 'Permanent' :
- access === 'Session' ? 'Session' : 'NoAccess';
-
- if (ContentShareMod.debugMode) {
- console.log('Sharing content:', {
- action, bubbleImpl, bubbleContent,
- shareRule, shareLifetime, shareAccess
- });
- }
-
- engine.call('NAKCallShareContent', action, bubbleImpl, bubbleContent,
- shareRule, shareLifetime, shareAccess, contentImage, contentName);
-
- this.hide();
- }
- },
-
- Unshare: {
- currentPage: 1,
- totalPages: 1,
- sharesPerPage: 5,
- sharesList: null,
-
- initStyles: function() {
- return `
- .unshare-dialog {
- width: 800px;
- height: 1000px;
- transform: translate(-50%, -60%);
- display: flex;
- flex-direction: column;
- }
-
- .unshare-dialog .shares-container {
- flex: 1;
- overflow-y: auto;
- margin: 20px 0;
- min-height: 0;
- }
-
- .unshare-dialog #shares-loading,
- .unshare-dialog #shares-error,
- .unshare-dialog #shares-empty {
- text-align: center;
- padding: 2em;
- font-size: 1.1em;
- }
-
- .unshare-dialog #shares-error {
- color: #ff6b6b;
- }
-
- .unshare-dialog .share-item {
- display: flex;
- align-items: center;
- padding: 15px;
- border: 1px solid rgba(255, 255, 255, 0.1);
- margin-bottom: 10px;
- background: rgba(0, 0, 0, 0.2);
- border-radius: 4px;
- min-height: 120px;
- }
-
- .unshare-dialog .share-item img {
- width: 96px;
- height: 96px;
- border-radius: 4px;
- margin-right: 15px;
- cursor: pointer;
- }
-
- .unshare-dialog .share-item .user-name {
- flex: 1;
- font-size: 1.3em;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- margin-right: 15px;
- cursor: pointer;
- }
-
- .unshare-dialog .action-btn {
- width: 180px;
- height: 60px;
- font-size: 1.2em;
- color: white;
- border-radius: 4px;
- display: inline-flex;
- align-items: center;
- justify-content: center;
- margin-left: 10px;
- padding: 0 20px;
- text-align: center;
- }
-
- .unshare-dialog .revoke-btn {
- background-color: #ff6b6b;
- }
-
- .unshare-dialog .undo-btn {
- background-color: #4a9eff;
- }
-
- .unshare-dialog .action-btn:disabled {
- opacity: 0.5;
- cursor: not-allowed;
- background-color: #666;
- }
-
- .unshare-dialog .pagination {
- border-top: 1px solid rgba(255, 255, 255, 0.1);
- padding-top: 20px;
- }
-
- .unshare-dialog .page-info {
- font-size: 1.1em;
- opacity: 0.8;
- margin-bottom: 15px;
- text-align: center;
- }
-
- .unshare-dialog .page-buttons {
- display: flex;
- justify-content: center;
- gap: 20px;
- }
-
- .unshare-dialog .share-item.revoked {
- opacity: 0.7;
- }
- `;
- },
-
- createDialog: function() {
- const dialog = document.createElement('div');
- dialog.id = 'content-unshare-dialog';
- dialog.className = 'content-sharing-base-dialog unshare-dialog hidden';
- dialog.innerHTML = `
- Manage Shares
- Close
-
-
-
Loading shares...
-
Failed to load shares. Please try again.
-
No active shares found.
-
-
-
-
- `;
- document.body.appendChild(dialog);
- return dialog;
- },
-
- show: function(contentDetails) {
- const dialog = ContentShareMod.unshareDialog;
- ContentShareMod.applyThemeToDialog(dialog);
-
- dialog.classList.remove('hidden', 'out');
- setTimeout(() => dialog.classList.add('in'), 50);
-
- ContentShareMod.currentContentData = contentDetails;
- this.currentPage = 1;
- this.totalPages = 1;
- this.sharesList = null;
- this.requestShares();
- },
-
- hide: function() {
- const dialog = ContentShareMod.unshareDialog;
- dialog.classList.remove('in');
- dialog.classList.add('out');
- setTimeout(() => {
- dialog.classList.add('hidden');
- dialog.classList.remove('out');
- }, 200);
- },
-
- requestShares: function() {
- const dialog = ContentShareMod.unshareDialog;
- const sharesContainer = dialog.querySelector('.shares-container');
-
- sharesContainer.querySelector('#shares-loading').style.display = '';
- sharesContainer.querySelector('#shares-error').style.display = 'none';
- sharesContainer.querySelector('#shares-empty').style.display = 'none';
- sharesContainer.querySelector('#shares-list').style.display = 'none';
-
- const contentDetails = ContentShareMod.currentContentData;
- const contentType = contentDetails.AvatarId ? 'Avatar' : 'Spawnable';
- const contentId = contentDetails.AvatarId || contentDetails.SpawnableId;
-
- engine.call('NAKGetContentShares', contentType, contentId);
- },
-
- handleSharesResponse: function(success, shares) {
- const dialog = ContentShareMod.unshareDialog;
- const sharesContainer = dialog.querySelector('.shares-container');
- const loadingElement = sharesContainer.querySelector('#shares-loading');
- const errorElement = sharesContainer.querySelector('#shares-error');
- const emptyElement = sharesContainer.querySelector('#shares-empty');
- const sharesListElement = sharesContainer.querySelector('#shares-list');
-
- loadingElement.style.display = 'none';
-
- if (!success) {
- errorElement.style.display = '';
- return;
- }
-
- try {
- const response = JSON.parse(shares);
- this.sharesList = response.Data.value;
-
- if (!this.sharesList || this.sharesList.length === 0) {
- emptyElement.style.display = '';
- const pagination = dialog.querySelector('.pagination');
- const [prevButton, nextButton] = pagination.querySelectorAll('.page-btn');
- prevButton.disabled = true;
- nextButton.disabled = true;
- pagination.querySelector('.page-info').textContent = '1/1';
- return;
- }
-
- this.totalPages = Math.ceil(this.sharesList.length / this.sharesPerPage);
- this.updatePageContent();
- } catch (error) {
- console.error('Error parsing shares:', error);
- errorElement.style.display = '';
- }
- },
-
- updatePageContent: function() {
- const dialog = ContentShareMod.unshareDialog;
- const sharesListElement = dialog.querySelector('#shares-list');
-
- const startIndex = (this.currentPage - 1) * this.sharesPerPage;
- const endIndex = startIndex + this.sharesPerPage;
- const currentShares = this.sharesList.slice(startIndex, endIndex);
-
- sharesListElement.innerHTML = currentShares.map(share => `
-
-

-
${share.name}
-
-
- `).join('');
-
- sharesListElement.style.display = '';
-
- const pagination = dialog.querySelector('.pagination');
- pagination.querySelector('.page-info').textContent = `${this.currentPage}/${this.totalPages}`;
- const [prevButton, nextButton] = pagination.querySelectorAll('.page-btn');
- prevButton.disabled = this.currentPage === 1;
- nextButton.disabled = this.currentPage === this.totalPages;
- },
-
- previousPage: function() {
- if (this.currentPage > 1) {
- this.currentPage--;
- this.updatePageContent();
- }
- },
-
- nextPage: function() {
- if (this.currentPage < this.totalPages) {
- this.currentPage++;
- this.updatePageContent();
- }
- },
-
- viewUserProfile: function(userId) {
- this.hide();
- getUserDetails(userId);
- },
-
- revokeShare: function(userId, buttonElement) {
- const contentDetails = ContentShareMod.currentContentData;
- const contentType = contentDetails.AvatarId ? 'Avatar' : 'Spawnable';
- const contentId = contentDetails.AvatarId || contentDetails.SpawnableId;
-
- buttonElement.disabled = true;
- buttonElement.textContent = 'Revoking...';
-
- engine.call('NAKRevokeContentShare', contentType, contentId, userId);
- },
-
- handleRevokeResponse: function(success, userId, error) {
- const dialog = ContentShareMod.unshareDialog;
- const shareItem = dialog.querySelector(`[data-user-id="${userId}"]`);
- if (!shareItem) return;
-
- const actionButton = shareItem.querySelector('button');
-
- if (success) {
- shareItem.classList.add('revoked');
- actionButton.className = 'action-btn undo-btn button';
- actionButton.textContent = 'Undo';
- actionButton.onclick = () => {
- actionButton.disabled = true;
- actionButton.textContent = 'Restoring...';
-
- const contentDetails = ContentShareMod.currentContentData;
- const contentType = contentDetails.AvatarId ? 'Avatar' : 'Spawnable';
- const contentId = contentDetails.AvatarId || contentDetails.SpawnableId;
-
- engine.call('NAKCallShareContentDirect', contentType, contentId, userId);
- };
- uiPushShow("Share revoked successfully", 3);
- } else {
- actionButton.textContent = 'Failed';
- actionButton.classList.add('failed');
- uiPushShow(error || "Failed to revoke share", 3);
-
- // Reset button after a moment
- setTimeout(() => {
- actionButton.disabled = false;
- actionButton.textContent = 'Revoke';
- actionButton.classList.remove('failed');
- }, 1000);
- }
- },
-
- handleShareResponse: function(success, userId, error) {
- const dialog = ContentShareMod.unshareDialog;
- const shareItem = dialog.querySelector(`[data-user-id="${userId}"]`);
- if (!shareItem) return;
-
- const actionButton = shareItem.querySelector('button');
-
- if (success) {
- this.requestShares();
- uiPushShow("Share restored successfully", 3);
- } else {
- actionButton.textContent = 'Failed';
- actionButton.classList.add('failed');
- uiPushShow(error || "Failed to restore share", 3);
-
- // Reset button after a moment
- setTimeout(() => {
- actionButton.disabled = false;
- actionButton.textContent = 'Undo';
- actionButton.classList.remove('failed');
- }, 1000);
- }
- }
- },
-
- DirectShare: {
- currentPage: 1,
- totalPages: 1,
- usersPerPage: 5,
- usersList: null,
- isInstanceUsers: true,
-
- initStyles: function() {
- return `
- .direct-share-dialog {
- width: 800px;
- height: 1000px;
- transform: translate(-50%, -60%);
- display: flex;
- flex-direction: column;
- }
-
- .direct-share-dialog .search-container {
- margin: 20px 0;
- padding: 10px;
- background: rgba(0, 0, 0, 0.2);
- border-radius: 4px;
- }
-
- .direct-share-dialog .search-input {
- width: 100%;
- padding: 10px;
- border: none;
- background: transparent;
- color: inherit;
- font-size: 1.1em;
- }
-
- .direct-share-dialog .source-indicator {
- padding: 10px;
- text-align: center;
- opacity: 0.8;
- background: rgba(0, 0, 0, 0.1);
- border-radius: 4px;
- margin-bottom: 20px;
- }
-
- .direct-share-dialog .users-container {
- flex: 1;
- overflow-y: auto;
- margin-bottom: 20px;
- min-height: 0;
- }
-
- .direct-share-dialog .user-item {
- display: flex;
- align-items: center;
- padding: 15px;
- border: 1px solid rgba(255, 255, 255, 0.1);
- margin-bottom: 10px;
- background: rgba(0, 0, 0, 0.2);
- border-radius: 4px;
- min-height: 96px;
- }
-
- .direct-share-dialog .user-item img {
- width: 96px;
- height: 96px;
- margin-right: 15px;
- cursor: pointer;
- border-radius: 4px;
- }
-
- .direct-share-dialog .user-item .user-name {
- flex: 1;
- font-size: 1.3em;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- margin-right: 15px;
- cursor: pointer;
- }
-
- .direct-share-dialog .user-item .action-btn {
- width: 140px;
- height: 50px;
- font-size: 1.2em;
- color: white;
- background-color: rgba(27, 80, 55, 1);
- border-radius: 4px;
- display: inline-flex;
- align-items: center;
- justify-content: center;
- }
-
- .direct-share-dialog .user-item .action-btn:disabled {
- opacity: 0.5;
- cursor: not-allowed;
- background-color: #4a9eff;
- }
-
- .direct-share-dialog .pagination {
- border-top: 1px solid rgba(255, 255, 255, 0.1);
- padding-top: 20px;
- }
-
- .direct-share-dialog .page-info {
- font-size: 1.1em;
- opacity: 0.8;
- margin-bottom: 15px;
- text-align: center;
- }
-
- .direct-share-dialog .page-buttons {
- display: flex;
- justify-content: center;
- gap: 20px;
- }
-
- .direct-share-dialog .user-item .action-btn {
- width: 180px;
- height: 60px;
- font-size: 1.2em;
- color: white;
- border-radius: 4px;
- display: inline-flex;
- align-items: center;
- justify-content: center;
- margin-left: 10px;
- padding: 0 20px;
- text-align: center;
- background-color: rgba(27, 80, 55, 1);
- }
-
- .direct-share-dialog .user-item .action-btn:disabled {
- opacity: 0.5;
- cursor: not-allowed;
- }
-
- .direct-share-dialog .user-item .action-btn.shared {
- background-color: #4a9eff;
- }
-
- .direct-share-dialog .user-item .action-btn.failed {
- background-color: #ff6b6b;
- }
- `;
- },
-
- createDialog: function() {
- const dialog = document.createElement('div');
- dialog.id = 'content-direct-share-dialog';
- dialog.className = 'content-sharing-base-dialog direct-share-dialog hidden';
- dialog.innerHTML = `
- Direct Share
- Close
-
-
-
Loading users...
-
Failed to load users. Please try again.
-
No users found.
-
-
-
-
- `;
- document.body.appendChild(dialog);
- return dialog;
- },
-
- show: function(contentDetails) {
- const dialog = ContentShareMod.directShareDialog;
- ContentShareMod.applyThemeToDialog(dialog);
-
- dialog.classList.remove('hidden', 'out');
- setTimeout(() => dialog.classList.add('in'), 50);
-
- ContentShareMod.currentContentData = contentDetails;
- this.currentPage = 1;
- this.totalPages = 1;
- this.usersList = null;
- this.requestUsers(true);
- },
-
- hide: function() {
- const dialog = ContentShareMod.directShareDialog;
- dialog.classList.remove('in');
- dialog.classList.add('out');
- setTimeout(() => {
- dialog.classList.add('hidden');
- dialog.classList.remove('out');
- }, 200);
- },
-
- handleUsersResponse: function(success, users, isInstanceUsers) {
- const dialog = ContentShareMod.directShareDialog;
- const usersContainer = dialog.querySelector('.users-container');
- // const sourceIndicator = dialog.querySelector('.source-indicator');
- const loadingElement = usersContainer.querySelector('#users-loading');
- const errorElement = usersContainer.querySelector('#users-error');
- const emptyElement = usersContainer.querySelector('#users-empty');
- const usersListElement = usersContainer.querySelector('#users-list');
-
- loadingElement.style.display = 'none';
- // sourceIndicator.textContent = isInstanceUsers ?
- // 'Showing users in current instance' :
- // 'Showing search results';
-
- // TODO: Add source indicator to html:
- //
- // Showing users in current instance
- //
-
- if (!success) {
- errorElement.style.display = '';
- return;
- }
-
- try {
- const response = JSON.parse(users);
- this.usersList = response.entries;
- this.isInstanceUsers = isInstanceUsers;
-
- if (!this.usersList || this.usersList.length === 0) {
- emptyElement.style.display = '';
- this.updatePagination();
- return;
- }
-
- this.totalPages = Math.ceil(this.usersList.length / this.usersPerPage);
- this.updatePageContent();
- } catch (error) {
- console.error('Error parsing users:', error);
- errorElement.style.display = '';
- }
- },
-
- handleSearch: function(event) {
- if (event.key === 'Enter') {
- const searchValue = event.target.value.trim();
- // Pass true for instance users when empty search, false for search results
- this.requestUsers(searchValue === '', searchValue);
- }
- },
-
- requestUsers: function(isInstanceUsers, searchQuery = '') {
- const dialog = ContentShareMod.directShareDialog;
- const usersContainer = dialog.querySelector('.users-container');
-
- usersContainer.querySelector('#users-loading').style.display = '';
- usersContainer.querySelector('#users-error').style.display = 'none';
- usersContainer.querySelector('#users-empty').style.display = 'none';
- usersContainer.querySelector('#users-list').style.display = 'none';
-
- engine.call('NAKGetUsersForSharing', searchQuery);
- },
-
- updatePageContent: function() {
- const dialog = ContentShareMod.directShareDialog;
- const usersListElement = dialog.querySelector('#users-list');
-
- const startIndex = (this.currentPage - 1) * this.usersPerPage;
- const endIndex = startIndex + this.usersPerPage;
- const currentUsers = this.usersList.slice(startIndex, endIndex);
-
- usersListElement.innerHTML = currentUsers.map(user => `
-
-

-
${user.name}
-
-
- `).join('');
-
- usersListElement.style.display = '';
- this.updatePagination();
- },
-
- updatePagination: function() {
- const dialog = ContentShareMod.directShareDialog;
- const pagination = dialog.querySelector('.pagination');
- const [prevButton, nextButton] = pagination.querySelectorAll('.page-btn');
-
- pagination.querySelector('.page-info').textContent = `Page ${this.currentPage}/${this.totalPages}`;
- prevButton.disabled = this.currentPage === 1;
- nextButton.disabled = this.currentPage === this.totalPages;
- },
-
- previousPage: function() {
- if (this.currentPage > 1) {
- this.currentPage--;
- this.updatePageContent();
- }
- },
-
- nextPage: function() {
- if (this.currentPage < this.totalPages) {
- this.currentPage++;
- this.updatePageContent();
- }
- },
-
- viewUserProfile: function(userId) {
- this.hide();
- getUserDetails(userId);
- },
-
- shareWithUser: function(userId, buttonElement) {
- const contentDetails = ContentShareMod.currentContentData;
- const contentType = contentDetails.AvatarId ? 'Avatar' : 'Spawnable';
- const contentId = contentDetails.AvatarId || contentDetails.SpawnableId;
- const contentName = contentDetails.AvatarName || contentDetails.SpawnableName;
- const contentImage = contentDetails.AvatarImageURL || contentDetails.SpawnableImageURL;
-
- buttonElement.disabled = true;
- buttonElement.textContent = 'Sharing...';
-
- engine.call('NAKCallShareContentDirect', contentType, contentId, userId, contentName, contentImage);
- },
-
- handleShareResponse: function(success, userId, error) {
- const dialog = ContentShareMod.directShareDialog;
- const userItem = dialog.querySelector(`[data-user-id="${userId}"]`);
- if (!userItem) return;
-
- const actionButton = userItem.querySelector('button');
-
- if (success) {
- actionButton.textContent = 'Shared';
- actionButton.disabled = true;
- actionButton.classList.add('shared');
- uiPushShow("Content shared successfully", 3, "shareresponse");
- } else {
- actionButton.disabled = false;
- actionButton.textContent = 'Failed';
- actionButton.classList.add('failed');
- uiPushShow(error || "Failed to share content", 3, "shareresponse");
-
- // Reset button after a moment
- setTimeout(() => {
- actionButton.disabled = false;
- actionButton.textContent = 'Share';
- actionButton.classList.remove('failed', 'shared');
- }, 1000);
- }
- }
- },
-
- ShareSelect: {
- initStyles: function() {
- return `
- .share-select-dialog {
- width: 650px;
- height: 480px;
- transform: translate(-50%, -80%);
- }
-
- .share-select-dialog .share-options {
- display: flex;
- flex-direction: column;
- gap: 15px;
- margin-top: 20px;
- }
-
- .share-select-dialog .share-option {
- padding: 20px;
- text-align: left;
- cursor: pointer;
- background: rgba(0, 0, 0, 0.2);
- border: 1px solid rgba(255, 255, 255, 0.1);
- border-radius: 4px;
- transition: background-color 0.2s ease;
- }
-
- .share-select-dialog .share-option:hover {
- background-color: rgba(27, 80, 55, 1);
- border-color: rgba(255, 255, 255, 0.2);
- }
-
- .share-select-dialog h3 {
- margin: 0 0 8px 0;
- font-size: 1.2em;
- }
-
- .share-select-dialog p {
- margin: 0;
- opacity: 0.8;
- font-size: 0.95em;
- line-height: 1.4;
- }
- `;
- },
-
- createDialog: function() {
- const dialog = document.createElement('div');
- dialog.id = 'content-share-select-dialog';
- dialog.className = 'content-sharing-base-dialog share-select-dialog hidden';
- dialog.innerHTML = `
- Share Content
- Close
-
-
-
-
Share Bubble
-
Drop or place a bubble in the world that others can interact with
-
-
-
-
Direct Share
-
Share directly with specific users
-
-
- `;
- document.body.appendChild(dialog);
- return dialog;
- },
-
- show: function(contentDetails) {
- const dialog = ContentShareMod.shareSelectDialog;
- ContentShareMod.applyThemeToDialog(dialog);
-
- dialog.classList.remove('hidden', 'out');
- setTimeout(() => dialog.classList.add('in'), 50);
-
- ContentShareMod.currentContentData = contentDetails;
- },
-
- hide: function() {
- const dialog = ContentShareMod.shareSelectDialog;
- dialog.classList.remove('in');
- dialog.classList.add('out');
- setTimeout(() => {
- dialog.classList.add('hidden');
- dialog.classList.remove('out');
- }, 200);
- },
-
- openShareBubble: function() {
- this.hide();
- ContentShareMod.ShareBubble.show(ContentShareMod.currentContentData);
- },
-
- openDirectShare: function() {
- this.hide();
- ContentShareMod.DirectShare.show(ContentShareMod.currentContentData);
- }
- },
-
- // Toolbar initialization and event bindings
- initializeToolbars: function() {
- const findEmptyButtons = (toolbar) => {
- return Array.from(toolbar.querySelectorAll('.toolbar-btn')).filter(
- btn => btn.textContent.trim() === ""
- );
- };
-
- const setupToolbar = (selector) => {
- const toolbar = document.querySelector(selector);
- if (!toolbar) return;
-
- const emptyButtons = findEmptyButtons(toolbar);
- if (emptyButtons.length >= 2) {
- emptyButtons[0].classList.add('content-share-btn');
- emptyButtons[0].textContent = 'Share';
-
- emptyButtons[1].classList.add('content-unshare-btn');
- emptyButtons[1].textContent = 'Unshare';
- }
- };
-
- setupToolbar('#avatar-detail .avatar-toolbar');
- setupToolbar('#prop-detail .avatar-toolbar');
- },
-
- bindEvents: function() {
- // Avatar events
- engine.on("LoadAvatarDetails", (avatarDetails) => {
- const shareBtn = document.querySelector('#avatar-detail .content-share-btn');
- const unshareBtn = document.querySelector('#avatar-detail .content-unshare-btn');
- const canShareDirectly = avatarDetails.IsMine;
- const canUnshare = avatarDetails.IsMine || avatarDetails.IsSharedWithMe;
-
- if (shareBtn) {
- shareBtn.classList.remove('disabled');
-
- if (canShareDirectly) {
- shareBtn.onclick = () => ContentShareMod.ShareSelect.show(avatarDetails);
- } else {
- shareBtn.onclick = () => ContentShareMod.ShareBubble.show(avatarDetails);
- }
- }
-
- if (unshareBtn) {
- if (canUnshare) {
- unshareBtn.classList.remove('disabled');
- unshareBtn.onclick = () => {
- if (avatarDetails.IsMine) {
- ContentShareMod.Unshare.show(avatarDetails);
- } else {
- uiConfirmShow("Unshare Avatar",
- "Are you sure you want to unshare this avatar?",
- "unshare_avatar_confirmation",
- avatarDetails.AvatarId);
- }
- };
- } else {
- unshareBtn.classList.add('disabled');
- unshareBtn.onclick = null;
- }
- }
-
- ContentShareMod.currentContentData = avatarDetails;
- });
-
- // Prop events
- engine.on("LoadPropDetails", (propDetails) => {
- const shareBtn = document.querySelector('#prop-detail .content-share-btn');
- const unshareBtn = document.querySelector('#prop-detail .content-unshare-btn');
- const canShareDirectly = propDetails.IsMine;
- const canUnshare = propDetails.IsMine || propDetails.IsSharedWithMe;
-
- if (shareBtn) {
- shareBtn.classList.remove('disabled');
-
- if (canShareDirectly) {
- shareBtn.onclick = () => ContentShareMod.ShareSelect.show(propDetails);
- } else {
- shareBtn.onclick = () => ContentShareMod.ShareBubble.show(propDetails);
- }
- }
-
- if (unshareBtn) {
- if (canUnshare) {
- unshareBtn.classList.remove('disabled');
- unshareBtn.onclick = () => {
- if (propDetails.IsMine) {
- ContentShareMod.Unshare.show(propDetails);
- } else {
- uiConfirmShow("Unshare Prop",
- "Are you sure you want to unshare this prop?",
- "unshare_prop_confirmation",
- propDetails.SpawnableId);
- }
- };
- } else {
- unshareBtn.classList.add('disabled');
- unshareBtn.onclick = null;
- }
- }
-
- ContentShareMod.currentContentData = propDetails;
- });
-
- // Share response handlers
- engine.on("OnHandleSharesResponse", (success, shares) => {
- if (ContentShareMod.debugMode) {
- console.log('Shares response:', success, shares);
- }
- ContentShareMod.Unshare.handleSharesResponse(success, shares);
- });
-
- engine.on("OnHandleRevokeResponse", (success, userId, error) => {
- ContentShareMod.Unshare.handleRevokeResponse(success, userId, error);
- });
-
- engine.on("OnHandleShareResponse", function(success, userId, error) {
- // Pass event to Unshare and DirectShare modules depending on which dialog is open
- if (ContentShareMod.unshareDialog && !ContentShareMod.unshareDialog.classList.contains('hidden')) {
- ContentShareMod.Unshare.handleShareResponse(success, userId, error);
- } else if (ContentShareMod.directShareDialog && !ContentShareMod.directShareDialog.classList.contains('hidden')) {
- ContentShareMod.DirectShare.handleShareResponse(success, userId, error);
- }
- });
-
- // Share release handlers
- engine.on("OnReleasedAvatarShare", (contentId) => {
- if (!ContentShareMod.currentContentData ||
- ContentShareMod.currentContentData.AvatarId !== contentId) return;
-
- const unshareBtn = document.querySelector('#avatar-detail .content-unshare-btn');
- ContentShareMod.currentContentData.IsSharedWithMe = false;
-
- if (unshareBtn) {
- unshareBtn.classList.add('disabled');
- unshareBtn.onclick = null;
- }
-
- const contentIsAccessible = ContentShareMod.currentContentData.IsMine ||
- ContentShareMod.currentContentData.IsPublic;
-
- if (!contentIsAccessible) {
- const detail = document.querySelector('#avatar-detail');
- if (detail) {
- ['drop-btn', 'select-btn', 'fav-btn'].forEach(className => {
- const button = detail.querySelector('.' + className);
- if (button) {
- button.classList.add('disabled');
- button.removeAttribute('onclick');
- }
- });
- }
- }
- });
-
- engine.on("OnReleasedPropShare", (contentId) => {
- if (!ContentShareMod.currentContentData ||
- ContentShareMod.currentContentData.SpawnableId !== contentId) return;
-
- const unshareBtn = document.querySelector('#prop-detail .content-unshare-btn');
- ContentShareMod.currentContentData.IsSharedWithMe = false;
-
- if (unshareBtn) {
- unshareBtn.classList.add('disabled');
- unshareBtn.onclick = null;
- }
-
- const contentIsAccessible = ContentShareMod.currentContentData.IsMine ||
- ContentShareMod.currentContentData.IsPublic;
-
- if (!contentIsAccessible) {
- const detail = document.querySelector('#prop-detail');
- if (detail) {
- ['drop-btn', 'select-btn', 'fav-btn'].forEach(className => {
- const button = detail.querySelector('.' + className);
- if (button) {
- button.classList.add('disabled');
- button.removeAttribute('onclick');
- }
- });
- }
- }
- });
-
- engine.on("OnHandleUsersResponse", (success, users, isInstanceUsers) => {
- if (ContentShareMod.debugMode) {
- console.log('Users response:', success, isInstanceUsers, users);
- }
- ContentShareMod.DirectShare.handleUsersResponse(success, users, isInstanceUsers);
- });
- }
-};
-
-ContentShareMod.init();
-
-""";
-
- private const string UiConfirmId_ReleaseAvatarShareWarning = "unshare_avatar_confirmation";
- private const string UiConfirmId_ReleasePropShareWarning = "unshare_prop_confirmation";
-
[HarmonyPostfix]
- [HarmonyPatch(typeof(ViewManager), nameof(ViewManager.Start))]
- public static void Postfix_ViewManager_Start(ViewManager __instance)
+ [HarmonyPatch(typeof(ViewManager), nameof(ViewManager.RegisterShareEvents))]
+ public static void Postfix_ViewManager_RegisterShareEvents(ViewManager __instance)
{
- // Inject the details toolbar patches when the game menu view is loaded
- __instance.gameMenuView.Listener.FinishLoad += (_) => {
- __instance.gameMenuView.View._view.ExecuteScript(DETAILS_TOOLBAR_PATCHES);
- __instance.gameMenuView.View.BindCall("NAKCallShareContent", OnShareContent);
- __instance.gameMenuView.View.BindCall("NAKGetContentShares", OnGetContentShares);
- __instance.gameMenuView.View.BindCall("NAKRevokeContentShare", OnRevokeContentShare);
- __instance.gameMenuView.View.BindCall("NAKCallShareContentDirect", OnShareContentDirect);
- __instance.gameMenuView.View.BindCall("NAKGetUsersForSharing", OnGetUsersForSharing);
+ __instance.cohtmlView.View.BindCall("NAKCallShareContent", OnShareContent);
+ __instance.cohtmlView.Listener.FinishLoad += (_) => {
+ __instance.cohtmlView.View._view.ExecuteScript(
+"""
+(function waitForContentShare(){
+ if (typeof ContentShare !== 'undefined') {
+ ContentShare.HasShareBubbles = true;
+ console.log('ShareBubbles patch applied');
+ } else {
+ setTimeout(waitForContentShare, 50);
+ }
+})();
+""");
};
-
- // Add the event listener for the unshare confirmation dialog
- __instance.OnUiConfirm.AddListener(OnReleaseContentShareConfirmation);
-
- return;
+ return;
void OnShareContent(
- string action,
- string bubbleImpl,
- string bubbleContent,
- string shareRule,
+ string action,
+ string bubbleImpl,
+ string bubbleContent,
+ string shareRule,
string shareLifetime,
string shareAccess,
string contentImage,
@@ -1492,21 +96,21 @@ ContentShareMod.init();
// ShareRule: Public, FriendsOnly
// ShareLifetime: TwoMinutes, Session
// ShareAccess: PermanentAccess, SessionAccess, NoAccess
-
+
ShareRule rule = shareRule switch
{
"Everyone" => ShareRule.Everyone,
"FriendsOnly" => ShareRule.FriendsOnly,
_ => ShareRule.Everyone
};
-
+
ShareLifetime lifetime = shareLifetime switch
{
"Session" => ShareLifetime.Session,
"TwoMinutes" => ShareLifetime.TwoMinutes,
_ => ShareLifetime.TwoMinutes
};
-
+
ShareAccess access = shareAccess switch
{
"Permanent" => ShareAccess.Permanent,
@@ -1536,196 +140,9 @@ ContentShareMod.init();
ShareBubbleManager.Instance.SelectBubbleForPlace(contentImage, contentName, bubbleData);
break;
}
-
+
// Close menu
ViewManager.Instance.UiStateToggle(false);
}
-
- void OnReleaseContentShareConfirmation(string id, string value, string contentId)
- {
- // Check if the confirmation event is for unsharing content
- if (id != UiConfirmId_ReleaseAvatarShareWarning
- && id != UiConfirmId_ReleasePropShareWarning)
- return;
-
- //ShareBubblesMod.Logger.Msg($"Unshare confirmation received: {id}, {value}");
-
- // Check if the user confirmed the unshare action
- if (value != "true")
- {
- //ShareBubblesMod.Logger.Msg("Unshare action cancelled by user");
- return;
- }
-
- //ShareBubblesMod.Logger.Msg("Releasing share...");
-
- // Determine the content type based on the confirmation ID
- ShareApiHelper.ShareContentType contentType = id == UiConfirmId_ReleaseAvatarShareWarning
- ? ShareApiHelper.ShareContentType.Avatar
- : ShareApiHelper.ShareContentType.Spawnable;
-
- Task.Run(async () => {
- try
- {
- await ShareApiHelper.ReleaseShareAsync(contentType, contentId);
- MTJobManager.RunOnMainThread("release_share_response", () =>
- {
- // Cannot display a success message as opening details page pushes itself to top
- // after talking to api, so success message would need to be timed to show after
- // if (contentType == ApiShareHelper.ShareContentType.Avatar)
- // ViewManager.Instance.RequestAvatarDetailsPage(contentId);
- // else
- // ViewManager.Instance.GetPropDetails(contentId);
-
- ViewManager.Instance.gameMenuView.View._view.TriggerEvent(
- contentType == ShareApiHelper.ShareContentType.Avatar
- ? "OnReleasedAvatarShare" : "OnReleasedPropShare",
- contentId);
-
- ViewManager.Instance.TriggerPushNotification("Content unshared successfully", 3f);
- });
- }
- catch (ShareApiException ex)
- {
- ShareBubblesMod.Logger.Error($"Share API error: {ex.Message}");
- MTJobManager.RunOnMainThread("release_share_error", () => {
- ViewManager.Instance.TriggerAlert("Release Share Error", ex.UserFriendlyMessage, -1, true);
- });
- }
- catch (Exception ex)
- {
- ShareBubblesMod.Logger.Error($"Unexpected error releasing share: {ex.Message}");
- MTJobManager.RunOnMainThread("release_share_error", () => {
- ViewManager.Instance.TriggerAlert("Release Share Error", "An unexpected error occurred", -1, true);
- });
- }
- });
- }
-
- async void OnGetContentShares(string contentType, string contentId)
- {
- try
- {
- var response = await ShareApiHelper.GetSharesAsync>(
- contentType == "Avatar" ? ShareApiHelper.ShareContentType.Avatar : ShareApiHelper.ShareContentType.Spawnable,
- contentId
- );
-
- // TODO: somethign better than this cause this is ass and i need to replace the image urls with ImageCache coui ones
- // FUICJK<
- string json = JsonConvert.SerializeObject(response.Data);
-
- // log the json to console
- //ShareBubblesMod.Logger.Msg($"Shares response: {json}");
-
- __instance.gameMenuView.View.TriggerEvent("OnHandleSharesResponse", true, json);
- }
- catch (Exception ex)
- {
- ShareBubblesMod.Logger.Error($"Failed to get content shares: {ex.Message}");
- __instance.gameMenuView.View.TriggerEvent("OnHandleSharesResponse", false);
- }
- }
-
- async void OnRevokeContentShare(string contentType, string contentId, string userId)
- {
- try
- {
- await ShareApiHelper.ReleaseShareAsync(
- contentType == "Avatar" ? ShareApiHelper.ShareContentType.Avatar : ShareApiHelper.ShareContentType.Spawnable,
- contentId,
- userId
- );
-
- __instance.gameMenuView.View.TriggerEvent("OnHandleRevokeResponse", true, userId);
- }
- catch (ShareApiException ex)
- {
- ShareBubblesMod.Logger.Error($"Share API error revoking share: {ex.Message}");
- __instance.gameMenuView.View.TriggerEvent("OnHandleRevokeResponse", false, userId, ex.UserFriendlyMessage);
- }
- catch (Exception ex)
- {
- ShareBubblesMod.Logger.Error($"Unexpected error revoking share: {ex.Message}");
- __instance.gameMenuView.View.TriggerEvent("OnHandleRevokeResponse", false, userId, "An unexpected error occurred");
- }
- }
-
- async void OnShareContentDirect(string contentType, string contentId, string userId, string contentName = "", string contentImage = "")
- {
- try
- {
- ShareApiHelper.ShareContentType shareContentType = contentType == "Avatar"
- ? ShareApiHelper.ShareContentType.Avatar
- : ShareApiHelper.ShareContentType.Spawnable;
-
- await ShareApiHelper.ShareContentAsync(
- shareContentType,
- contentId,
- userId
- );
-
- // Alert the user that the share occurred
- //ModNetwork.SendDirectShareNotification(userId, shareContentType, contentId);
-
- __instance.gameMenuView.View._view.TriggerEvent("OnHandleShareResponse", true, userId);
- }
- catch (ShareApiException ex)
- {
- ShareBubblesMod.Logger.Error($"Share API error: {ex.Message}");
- __instance.gameMenuView.View._view.TriggerEvent("OnHandleShareResponse", false, userId, ex.UserFriendlyMessage);
- }
- catch (Exception ex)
- {
- ShareBubblesMod.Logger.Error($"Unexpected error sharing content: {ex.Message}");
- __instance.gameMenuView.View._view.TriggerEvent("OnHandleShareResponse", false, userId, "An unexpected error occurred");
- }
- }
- void OnGetUsersForSharing(string searchTerm = "")
- {
- try
- {
- if (!string.IsNullOrEmpty(searchTerm))
- {
- // TODO: Search users implementation will go here
- // For now just return an empty list
- var response = new { entries = new List