233 lines
7.7 KiB
C#
233 lines
7.7 KiB
C#
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEngine.UI;
|
|
using TMPro;
|
|
using DG.Tweening;
|
|
using Ashwild.Network;
|
|
using Ashwild.Player;
|
|
|
|
namespace Ashwild.UI
|
|
{
|
|
/// <summary>
|
|
/// The invitation card that slides in from the right when a friend invites the local player
|
|
/// to their session. Shows the inviter's Steam avatar and name, with Join (connect to their
|
|
/// room) and Cancel (dismiss) actions. Invites are queued and shown one at a time; each is
|
|
/// animated entirely with DOTween. Drop this on a card in the menu canvas.
|
|
/// </summary>
|
|
[DisallowMultipleComponent]
|
|
public class InviteNotificationUI : MonoBehaviour
|
|
{
|
|
#region Types
|
|
|
|
/// <summary>
|
|
/// One pending invitation: who sent it and the code needed to join their room.
|
|
/// </summary>
|
|
private readonly struct Invite
|
|
{
|
|
public readonly ulong SteamId;
|
|
public readonly string Name;
|
|
public readonly string Code;
|
|
|
|
public Invite(ulong steamId, string name, string code)
|
|
{
|
|
SteamId = steamId;
|
|
Name = name;
|
|
Code = code;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Serialized Fields
|
|
|
|
[Header("References")]
|
|
[Tooltip("The card RectTransform that slides in/out (anchored to the left edge).")]
|
|
[SerializeField] private RectTransform card;
|
|
[SerializeField] private Image avatarImage;
|
|
[Tooltip("Receives the inviter's display name.")]
|
|
[SerializeField] private TMP_Text nameText;
|
|
[SerializeField] private Button joinButton;
|
|
[SerializeField] private Button cancelButton;
|
|
|
|
[Header("Slide Animation")]
|
|
[Tooltip("Off-screen anchoredPosition.x (card hidden to the right).")]
|
|
[SerializeField] private float hiddenX = 560f;
|
|
[Tooltip("On-screen anchoredPosition.x (card resting position).")]
|
|
[SerializeField] private float shownX = -24f;
|
|
[SerializeField] private float slideInDuration = 0.4f;
|
|
[SerializeField] private Ease slideInEase = Ease.OutBack;
|
|
[SerializeField] private float slideOutDuration = 0.25f;
|
|
[SerializeField] private Ease slideOutEase = Ease.InBack;
|
|
|
|
#endregion
|
|
|
|
#region State
|
|
|
|
private readonly Queue<Invite> queue = new Queue<Invite>();
|
|
private Invite current;
|
|
private bool isShowing;
|
|
private Tween slideTween;
|
|
|
|
/// <summary>
|
|
/// Texture backing the currently displayed Steam avatar — destroyed when replaced to
|
|
/// avoid leaking a Texture2D per invitation.
|
|
/// </summary>
|
|
private Texture2D currentAvatarTexture;
|
|
|
|
#endregion
|
|
|
|
#region Unity Lifecycle
|
|
|
|
/// <summary>
|
|
/// Wires the buttons and parks the card off-screen; runs once.
|
|
/// </summary>
|
|
private void Awake()
|
|
{
|
|
if (joinButton != null) joinButton.onClick.AddListener(HandleJoinClicked);
|
|
if (cancelButton != null) cancelButton.onClick.AddListener(HandleCancelClicked);
|
|
|
|
if (card != null)
|
|
{
|
|
card.anchoredPosition = new Vector2(hiddenX, card.anchoredPosition.y);
|
|
card.gameObject.SetActive(false);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Subscribes to incoming invitations.
|
|
/// </summary>
|
|
private void OnEnable()
|
|
{
|
|
PlayerEvents.InviteReceived += HandleInviteReceived;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unsubscribes — mirrors OnEnable exactly.
|
|
/// </summary>
|
|
private void OnDisable()
|
|
{
|
|
PlayerEvents.InviteReceived -= HandleInviteReceived;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Kills the tween and frees the avatar texture on teardown.
|
|
/// </summary>
|
|
private void OnDestroy()
|
|
{
|
|
slideTween?.Kill();
|
|
ReleaseAvatarTexture();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Event Handlers
|
|
|
|
/// <summary>
|
|
/// Queues a new invitation and shows it immediately if the card is idle.
|
|
/// </summary>
|
|
private void HandleInviteReceived(ulong steamId, string inviterName, string code)
|
|
{
|
|
queue.Enqueue(new Invite(steamId, inviterName, code));
|
|
if (!isShowing) ShowNext();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Join the inviter's room, then slide the card away (the scene load takes over on success).
|
|
/// </summary>
|
|
private void HandleJoinClicked()
|
|
{
|
|
if (NetworkSessionManager.Instance != null && !string.IsNullOrEmpty(current.Code))
|
|
NetworkSessionManager.Instance.JoinSession(current.Code);
|
|
else
|
|
Debug.LogError("[InviteNotification] Cannot join — NetworkSessionManager missing or code empty.", this);
|
|
|
|
Dismiss();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Declines the current invitation and moves on to the next queued one.
|
|
/// </summary>
|
|
private void HandleCancelClicked() => Dismiss();
|
|
|
|
#endregion
|
|
|
|
#region Internal Helpers
|
|
|
|
/// <summary>
|
|
/// Pops the next invitation off the queue and slides the card in; idles when empty.
|
|
/// </summary>
|
|
private void ShowNext()
|
|
{
|
|
if (queue.Count == 0)
|
|
{
|
|
isShowing = false;
|
|
return;
|
|
}
|
|
|
|
current = queue.Dequeue();
|
|
isShowing = true;
|
|
|
|
if (nameText != null) nameText.text = current.Name;
|
|
// Clear the previous invite's avatar; Steam fills it back in (almost always instantly,
|
|
// since a friend's picture is normally already cached).
|
|
SetAvatar(null, null);
|
|
|
|
// Ask Steam for the real avatar; it may arrive now or a moment later.
|
|
if (SteamInviteService.Instance != null)
|
|
{
|
|
ulong requestedId = current.SteamId;
|
|
SteamInviteService.Instance.RequestAvatar(requestedId, sprite =>
|
|
{
|
|
// Ignore a late avatar for an invitation we are no longer showing.
|
|
if (!isShowing || current.SteamId != requestedId || sprite == null) return;
|
|
SetAvatar(sprite, sprite.texture);
|
|
});
|
|
}
|
|
|
|
slideTween?.Kill();
|
|
card.gameObject.SetActive(true);
|
|
card.anchoredPosition = new Vector2(hiddenX, card.anchoredPosition.y);
|
|
slideTween = card.DOAnchorPosX(shownX, slideInDuration).SetEase(slideInEase);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Slides the card out and shows the next queued invitation when done.
|
|
/// </summary>
|
|
private void Dismiss()
|
|
{
|
|
slideTween?.Kill();
|
|
slideTween = card.DOAnchorPosX(hiddenX, slideOutDuration)
|
|
.SetEase(slideOutEase)
|
|
.OnComplete(() =>
|
|
{
|
|
card.gameObject.SetActive(false);
|
|
ShowNext();
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Swaps the avatar sprite, releasing any previous Steam-built texture first.
|
|
/// </summary>
|
|
private void SetAvatar(Sprite sprite, Texture2D ownedTexture)
|
|
{
|
|
ReleaseAvatarTexture();
|
|
currentAvatarTexture = ownedTexture;
|
|
if (avatarImage != null) avatarImage.sprite = sprite;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Destroys the previously shown Steam avatar texture, if we created one.
|
|
/// </summary>
|
|
private void ReleaseAvatarTexture()
|
|
{
|
|
if (currentAvatarTexture != null)
|
|
{
|
|
Destroy(currentAvatarTexture);
|
|
currentAvatarTexture = null;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|