using AppUI.Bluetooth;
using AppUI.Localization;
using AppUI.Manager.View;
using AppUI.View.Home.Main;
using AppUIDeviceView = AppUI.View.Home.DeviceView.DeviceView;
using DG.Tweening;
using InfraredManager;
using SmartBowSDK;
using System;
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
using BLEAimhandler = AppUI.Bluetooth.AimHandler;
namespace AppUI.View.Component
{
///
/// 侧滑面板:BG 遮罩(可选点击关闭),Right 自右向左滑入;
/// 左上关闭、底部切换 Step,Content 下步骤内容留给业务扩展。
///
[DisallowMultipleComponent]
[ExecuteAlways]
public class ModuleSideSlip : MonoBehaviour
{
const string PathMaskBg = "BG";
const string PathRight = "Right";
const string PathBtnClose = "Right/BtnClose";
const string PathContent = "Right/Content";
const string PathBottomBar = "Right/BottomBar";
const string PathBtnStep = "Right/BottomBar/BtnStep";
const float Step3BatteryRefreshInterval = 1f;
const float DefaultSlideDuration = 0.32f;
const string KeyBtnNext = "appui-common-next";
const string KeyBtnStartConnect = "appui-sideslip-btn-start-connect";
const string KeyBtnConnecting = "appui-sideslip-btn-connecting";
const string KeyBtnDone = "appui-sideslip-btn-done";
const string KeyBtnStep = "appui-sideslip-btn-step";
const string KeyBtnStepProgress = "appui-sideslip-btn-step-progress";
const string KeyBtnScreenPositioning = "appui-sideslip-btn-screen-positioning";
const string KeyScreenPositioningDone = "appui-sideslip-screen-positioning-done";
const string KeyScreenPositioningIncomplete = "appui-sideslip-screen-positioning-incomplete";
[SerializeField]
bool maskCloseOnClick = true;
[Header("动画")]
[SerializeField]
float slideDuration = DefaultSlideDuration;
[SerializeField]
Ease slideInEase = Ease.OutCubic;
[SerializeField]
Ease slideOutEase = Ease.InCubic;
[Header("侧滑坐标(Right 需右锚点 + 右 Pivot,贴边 X 一般为 0)")]
[Tooltip("面板完全展开时 Right 的 AnchoredPosition.x,勿用编辑器里拖偏后的值")]
[SerializeField]
float shownAnchorPosX;
[Tooltip("打开前将 Right.PosX 归位到贴边值,避免预制体残留错误坐标")]
[SerializeField]
bool normalizeRightPosBeforeSlide = true;
[Header("步骤(可留空,自动收集 Content 下 Step_* 子物体)")]
[SerializeField]
List stepPanels = new List();
[Header("引用(可留空,自动绑定)")]
[SerializeField]
RectTransform maskBg;
[SerializeField]
Button maskButton;
[SerializeField]
RectTransform rightPanel;
[SerializeField]
Button btnClose;
[SerializeField]
Button btnStep;
[SerializeField]
TMP_Text btnStepLabel;
[Header("Step_0~2 按设备类型配置(可留空,留空则不改对应 Step 内容)")]
[SerializeField]
SideSlipDeviceGuideEntry[] guideEntries;
[Header("Step_0 引导页")]
[SerializeField] Image step0Image;
[SerializeField] TMP_Text step0Description;
[Header("Step_1 引导页")]
[SerializeField] Image step1Image;
[SerializeField] TMP_Text step1Description;
[Header("Step_2 连接中页")]
[SerializeField] Image step2Image;
[SerializeField] TMP_Text step2Description;
[Header("Step_3 连接成功页")]
[SerializeField] Image step3DeviceImage;
[SerializeField] TMP_Text step3Title;
[SerializeField] Image step3BatteryBar;
[SerializeField] Text step3BatteryValue;
[SerializeField] TMP_Text step3ConnectStatus;
[SerializeField] TMP_Text step3ScreenStatus;
AimDeviceType? _guideDeviceTypeOverride;
bool _closing;
int _stepIndex;
float _shownAnchorX;
float _hiddenAnchorX;
Tween _slideTween;
Coroutine _slideInRoutine;
float _step3BatteryTimer;
bool _hubStep3RefreshBound;
static readonly Color32 StatusCompleteColor = new Color32(0, 0, 0, 255);
static readonly Color32 StatusIncompleteColor = new Color32(145, 144, 144, 255);
public bool MaskCloseOnClick
{
get => maskCloseOnClick;
set
{
maskCloseOnClick = value;
ApplyMaskCloseInteractable();
}
}
public int StepCount => stepPanels != null ? stepPanels.Count : 0;
public int CurrentStepIndex => _stepIndex;
public RectTransform ContentRoot => transform.Find(PathContent) as RectTransform;
public UnityEvent onClosed = new UnityEvent();
public event Action OnStepChanged;
/// 补齐 BG / Right / BtnClose / Content / BottomBar 标准层级。
public static void EnsurePrefabHierarchy(Transform root) => SideSlipUIBuilder.EnsureHierarchy(root);
public static ModuleSideSlip Show(bool dontDestroyOnLoad = false, AimDeviceType? guideDeviceType = null)
{
ViewConfig viewConfig = ViewManager.GetConfig(UIPrefabType.ModuleSideSlip);
GameObject go = Instantiate(Resources.Load(viewConfig.path));
var slip = go.GetComponent();
if (slip == null)
slip = go.AddComponent();
if (guideDeviceType.HasValue)
slip.SetGuideDeviceType(guideDeviceType.Value);
if (dontDestroyOnLoad)
DontDestroyOnLoad(go);
return slip;
}
/// 打开侧滑前指定当前引导所针对的设备类型(如从设备列表点击传入)。
public void SetGuideDeviceType(AimDeviceType deviceType)
{
_guideDeviceTypeOverride = deviceType;
if (Application.isPlaying && isActiveAndEnabled)
RefreshGuideSteps();
}
void OnDestroy()
{
UnbindHubStep3Refresh();
//退出面板时候,取消正在连接的连接
SmartBowDeviceHub.ins?.CancelConnecting();
if (_slideInRoutine != null)
StopCoroutine(_slideInRoutine);
_slideTween?.Kill();
if (rightPanel != null)
rightPanel.DOKill();
}
#if UNITY_EDITOR
void OnValidate()
{
if (Application.isPlaying)
return;
SideSlipUIBuilder.EnsureHierarchy(transform);
ResolveReferences();
ResolveGuideStepReferences();
ApplyMaskCloseInteractable();
AppUILocalization.Init();
RefreshGuideSteps();
}
#endif
void Awake()
{
AppUILocalization.Init();
transform.localScale = Vector3.one;
SideSlipUIBuilder.EnsureHierarchy(transform);
ResolveReferences();
ApplyMaskCloseInteractable();
}
void OnEnable()
{
AppUILocalization.OnLanguageChanged += HandleLanguageChanged;
}
void OnDisable()
{
AppUILocalization.OnLanguageChanged -= HandleLanguageChanged;
}
void HandleLanguageChanged(LanguageEnum _)
{
if (!Application.isPlaying)
return;
RefreshStepButtonForIndex(_stepIndex);
if (_stepIndex is >= 0 and <= 2)
RefreshGuideSteps();
if (_stepIndex == 3)
RefreshStep3ConnectedInfo();
}
void Start()
{
resetAimType();
BindButtons();
CollectStepPanelsIfEmpty();
ResolveGuideStepReferences();
if (Application.isPlaying)
EnsureGuideDescriptionLocalization();
RefreshGuideSteps();
ShowStep(0, notify: false);
_slideInRoutine = StartCoroutine(PlaySlideInAfterLayout());
}
void Update()
{
if (!Application.isPlaying)
return;
UpdateBtnForConnect();
if (_stepIndex == 3)
{
_step3BatteryTimer += Time.deltaTime;
if (_step3BatteryTimer >= Step3BatteryRefreshInterval)
{
_step3BatteryTimer = 0f;
RefreshStep3Battery();
}
}
}
void ResolveReferences()
{
if (maskBg == null)
{
var bgTr = transform.Find(PathMaskBg) as RectTransform;
if (bgTr != null)
maskBg = bgTr;
}
if (maskBg != null && maskButton == null)
maskButton = maskBg.GetComponent