using AppUI.Localization;
using AppUI.Manager;
using AppUI.Manager.Role;
using AppUI.Util.Avatar;
using JCUnityLib;
using JCUnityLib.UI;
using System.Collections;
using System.Text.RegularExpressions;
using TMPro;
using UnityEngine;
namespace AppUI.View
{
///
/// AppUI 注册向导:账号密码 → 短信/邮箱验证码并注册 → 昵称与预设头像。
/// 全部输入使用 ;提示走 ,网络复用旧版 Controller。
///
public class RegisterView : MonoBehaviour
{
enum RegisterPhase
{
AccountPassword,
SmsAndRegister,
Profile
}
RegisterPhase _phase = RegisterPhase.AccountPassword;
[Header("阶段面板(可选,未绑定则仅逻辑推进)")]
[SerializeField]
GameObject panelAccountPassword;
[SerializeField]
GameObject panelSms;
[SerializeField]
GameObject panelProfile;
[Header("阶段一:账号 / 密码(可 Inspector 绑定,或运行时按路径查找)")]
[SerializeField]
TMP_InputField accountInput;
[SerializeField]
TMP_InputField passwordInput;
[Header("阶段二:短信验证码")]
[SerializeField]
TMP_InputField smsCodeInput;
[SerializeField]
TMP_Text sendSmsCountdownLabel;
[Header("阶段三:昵称 / 预设头像")]
[SerializeField]
TMP_InputField nicknameInput;
[SerializeField]
UnityEngine.UI.Image avatarPreviewImage;
[Tooltip("头像列表与 Sprite 数据源;Sprite 仅在此组件的数组中配置一份即可")]
[SerializeField]
AvatarGroup avatarGroup;
[Header("拼图验证(与旧版 RegisterView 一致;未赋值则直接请求发送验证码)")]
[SerializeField]
GameObject prefabValidateJigsawView;
[Header("行为")]
[SerializeField]
bool resetFlowOnEnable = true;
// TODO(AppUI.RegisterView): 若注册前必须勾选用户协议,取消注释并在 OnClick_ConfirmRegister 开头调用。
// [SerializeField] bool requireAgreementBeforeRegister = true;
string _storedAccount = "";
string _storedPassword = "";
string _registrationTicket = "";
int _selectedAvatarIndex;
static long _lastSendSmsMs;
Coroutine _sendCooldownRoutine;
bool _sendCooldownActive;
readonly Throttler _throttlerRegister = new Throttler(3000);
readonly Throttler _throttlerProfileSave = new Throttler(2000);
void OnEnable()
{
if (resetFlowOnEnable)
ResetFlowToAccountPassword();
}
void Awake()
{
InitPasswordAlphanumericFilter();
}
void Start()
{
WireAvatarGroup();
}
void OnDestroy()
{
if (avatarGroup != null)
avatarGroup.OnAvatarSelected -= OnAvatarGroupSelection;
}
///
/// 将 的写入目标设为 ,并订阅选中事件以同步 。
///
void WireAvatarGroup()
{
if (avatarGroup == null)
return;
if (avatarPreviewImage != null)
avatarGroup.targetImage = avatarPreviewImage;
avatarGroup.OnAvatarSelected -= OnAvatarGroupSelection;
avatarGroup.OnAvatarSelected += OnAvatarGroupSelection;
avatarGroup.SelectByIndex(0, notify: false);
_selectedAvatarIndex = avatarGroup.SelectedIndex;
}
void OnAvatarGroupSelection(int index, Sprite _)
{
_selectedAvatarIndex = index;
}
void InitPasswordAlphanumericFilter()
{
if (passwordInput == null)
return;
passwordInput.onValueChanged.AddListener(text =>
{
var match = new Regex("[^A-Za-z0-9]").Match(text);
if (match.Success)
passwordInput.text = text.Replace(match.Value, "");
});
}
/// 外部(例如从登录页)切回本页时可调用,清空状态与 UI。
public void ResetFlowToAccountPassword()
{
_phase = RegisterPhase.AccountPassword;
_storedAccount = "";
_storedPassword = "";
_registrationTicket = "";
_selectedAvatarIndex = 0;
if (avatarGroup != null)
avatarGroup.SelectByIndex(0, notify: false);
if (smsCodeInput != null)
smsCodeInput.text = "";
if (nicknameInput != null)
nicknameInput.text = "";
ApplyPhasePanels();
}
void ApplyPhasePanels()
{
if (panelAccountPassword != null)
panelAccountPassword.SetActive(_phase == RegisterPhase.AccountPassword);
if (panelSms != null)
panelSms.SetActive(_phase == RegisterPhase.SmsAndRegister);
if (panelProfile != null)
panelProfile.SetActive(_phase == RegisterPhase.Profile);
}
bool IsDomesticPhoneMode() => CommonConfig.AppArea == 0;
void ShowTip(string message)
{
ModuleViewMgr.ins.Show(message);
}
/// 阶段一「下一步」:校验账号密码后进入验证码阶段。
public void OnClick_ContinueToSmsStep()
{
if (accountInput == null || passwordInput == null)
{
Debug.LogError("[AppUI.RegisterView] 未绑定账号或密码 TMP_InputField。");
return;
}
string account = accountInput.text.Trim();
if (account.Length == 0)
{
ShowTip(AppUILocalization.GetTextByCNKey("请输入账号"));
return;
}
if (IsDomesticPhoneMode())
{
if (!ValidateHelper.IsMobilePhone(account))
{
ShowTip(AppUILocalization.GetTextByKey("RelateValidateView-a1"));
return;
}
}
else
{
if (!ValidateHelper.IsEmail(account))
{
ShowTip(AppUILocalization.GetTextByKey("RelateValidateView-a0"));
return;
}
}
string pwd = passwordInput.text.Trim();
if (pwd.Length == 0)
{
ShowTip(AppUILocalization.GetTextByCNKey("请输入密码"));
return;
}
if (pwd.Length < 6)
{
ShowTip(AppUILocalization.GetTextByCNKey("密码长度至少6位"));
return;
}
_storedAccount = account;
_storedPassword = pwd;
_phase = RegisterPhase.SmsAndRegister;
ApplyPhasePanels();
//发送验证码
OnClick_SendSmsCode();
}
/// 发送短信或邮箱验证码(与旧版 对齐;限流 60s)。
public void OnClick_SendSmsCode()
{
long gap = TimeUtils.GetTimestamp() - _lastSendSmsMs;
const long maxMs = 60 * 1000;
if (gap < maxMs)
{
long second = (maxMs - gap) / 1000;
if (second <= 0)
second = 1;
ShowTip(string.Format(AppUILocalization.GetTextByKey("RelateValidateView-a2"), second));
return;
}
if (_sendCooldownActive)
{
return;
}
if (IsDomesticPhoneMode())
{
if (!ValidateHelper.IsMobilePhone(_storedAccount))
{
ShowTip(AppUILocalization.GetTextByKey("RelateValidateView-a1"));
return;
}
}
else
{
if (!ValidateHelper.IsEmail(_storedAccount))
{
ShowTip(AppUILocalization.GetTextByKey("RelateValidateView-a0"));
return;
}
}
if (prefabValidateJigsawView != null)
{
var go = Instantiate(prefabValidateJigsawView);
var jigsaw = go.GetComponent();
if (jigsaw != null)
{
jigsaw.onComplete = RequestSendValidateCode;
jigsaw.SetTextLabel(AppUILocalization.GetTextByKey("ValidateJigsawView_label"));
jigsaw.SetTextTip(AppUILocalization.GetTextByKey("ValidateJigsawView_tip"));
jigsaw.SetTextOK(AppUILocalization.GetTextByKey("ValidateJigsawView_ok"));
}
else
{
Destroy(go);
RequestSendValidateCode();
}
}
else
{
RequestSendValidateCode();
}
}
void RequestSendValidateCode()
{
_lastSendSmsMs = JCUnityLib.TimeUtils.GetTimestamp();
if (_sendCooldownRoutine != null)
StopCoroutine(_sendCooldownRoutine);
_sendCooldownRoutine = StartCoroutine(SendSmsCooldownUiRoutine());
if (IsDomesticPhoneMode())
{
StartCoroutine(PhoneValidateController.Instance.SendPhoneValidateCode(_storedAccount, res =>
{
if (res.code == 0)
ShowTip(AppUILocalization.GetTextByKey("RelateValidateView-b2"));
else
ShowTip(AppUILocalization.GetTextByKey("RelateValidateView-b3"));
}));
}
else
{
StartCoroutine(EmailValidateController.Instance.SendEmailValidateCode(_storedAccount, res =>
{
if (res.code == 0)
ShowTip(AppUILocalization.GetTextByKey("RelateValidateView-b0"));
else
ShowTip(AppUILocalization.GetTextByKey("RelateValidateView-b1"));
}));
}
}
IEnumerator SendSmsCooldownUiRoutine()
{
_sendCooldownActive = true;
const float maxSec = 60f;
float left = maxSec;
while (left > 0f)
{
if (sendSmsCountdownLabel != null)
sendSmsCountdownLabel.text = string.Format(AppUILocalization.GetTextByKey("appui-resend"),"("+ Mathf.CeilToInt(left) + " S)");
left -= Time.deltaTime;
yield return null;
}
_sendCooldownActive = false;
if (sendSmsCountdownLabel != null)
sendSmsCountdownLabel.text = sendSmsCountdownLabel.text = string.Format(AppUILocalization.GetTextByKey("appui-resend"), "");
_sendCooldownRoutine = null;
}
/// 提交验证码并调用注册接口;失败提示,成功进入资料步骤。
public void OnClick_ConfirmRegister()
{
// TODO(AppUI.RegisterView): 需要协议时在此校验 AgreenmentOption.ins.IsAgreementChecked()。
if (smsCodeInput == null)
{
Debug.LogError("[AppUI.RegisterView] 未绑定验证码 TMP_InputField。");
return;
}
string codeText = smsCodeInput.text.Trim();
if (codeText.Length != 6)
{
ShowTip(AppUILocalization.GetTextByKey("RelateValidateView-a3"));
return;
}
if (!int.TryParse(codeText, out int codeValue))
{
ShowTip(AppUILocalization.GetTextByKey("RelateValidateView-a3"));
return;
}
if (!_throttlerRegister.CanPass())
{
ShowTip(AppUILocalization.GetTextByCNKey("操作过于频繁"));
return;
}
string emailField = IsDomesticPhoneMode() ? "" : _storedAccount;
string phoneField = IsDomesticPhoneMode() ? _storedAccount : "";
StartCoroutine(LoginController.Instance.RegisterByCode(
_storedAccount,
_storedPassword,
emailField,
phoneField,
codeValue,
res =>
{
ShowTip(AppUILocalization.GetTextByCNKey(res.msg));
if (res.code != 0)
return;
_registrationTicket = LoginController.TryGetRegistrationTicketFromData(res.data);
if (string.IsNullOrEmpty(_registrationTicket))
{
ShowTip(AppUILocalization.GetTextByCNKey("注册成功但缺少资料票据,请重新登录后完善资料"));
return;
}
_phase = RegisterPhase.Profile;
if (nicknameInput != null)
{
nicknameInput.text = _storedAccount;
}
ApplyPhasePanels();
TryRefreshAvatarPreview();
var loginView = FindObjectOfType();
loginView?.FillLoginInput(_storedAccount, _storedPassword);
}));
}
/// 预设头像列表项按钮可绑定:索引与 内 Sprite 数组一致。
public void OnSelectPresetAvatar(int index)
{
if (avatarGroup == null)
return;
var max = Mathf.Max(0, avatarGroup.AvatarCount - 1);
avatarGroup.SelectByIndex(Mathf.Clamp(index, 0, max), notify: true);
}
/// Inspector 无法直接传 int 参数时,可用多个按钮分别绑定下列方法。
public void OnSelectPresetAvatar0() => OnSelectPresetAvatar(0);
public void OnSelectPresetAvatar1() => OnSelectPresetAvatar(1);
public void OnSelectPresetAvatar2() => OnSelectPresetAvatar(2);
public void OnSelectPresetAvatar3() => OnSelectPresetAvatar(3);
public void OnSelectPresetAvatar4() => OnSelectPresetAvatar(4);
public void OnSelectPresetAvatar5() => OnSelectPresetAvatar(5);
void TryRefreshAvatarPreview()
{
if (avatarPreviewImage == null || avatarGroup == null)
return;
var sp = avatarGroup.GetSelectedSprite();
if (sp != null)
avatarPreviewImage.sprite = sp;
}
/// 返回上一阶段(供 UI 返回键绑定)。
public void OnClick_Back()
{
switch (_phase)
{
case RegisterPhase.SmsAndRegister:
_phase = RegisterPhase.AccountPassword;
ApplyPhasePanels();
break;
case RegisterPhase.Profile:
// TODO(AppUI.RegisterView): 注册已成功落库时是否允许退回验证码阶段需产品确认;当前仅回到短信页并保留已填昵称。
_phase = RegisterPhase.SmsAndRegister;
ApplyPhasePanels();
break;
default:
// TODO(AppUI.RegisterView): 若需关闭注册页回到登录,在此调用 LoginMgr.showLoginView() 或关闭当前弹窗。
break;
}
}
///
/// TODO(AppUI.RegisterView): 接入 CommonConfig.banBindRelateAccount 为 true 时的 Register2(邮箱+手机双绑)分支时在此封装专用提交流程。
///
public void OnClick_RegisterWithBoundContacts_Todo()
{
}
///
/// TODO(AppUI.RegisterView): 从相册/拍照选取自定义头像后得到 Texture2D,再调用 LoginController.UploadAvatar。
///
public void OnClick_PickCustomAvatarFromGallery_Todo()
{
}
/// 资料提交:完善用户信息;头像上传在登录拿 token 后尝试。
public void OnClick_CompleteProfile()
{
if (nicknameInput == null)
{
Debug.LogError("[AppUI.RegisterView] 未绑定昵称 TMP_InputField。");
return;
}
string nickname = nicknameInput.text.Trim();
if (nickname.Length == 0)
{
ShowTip(AppUILocalization.GetTextByCNKey("请输入游戏昵称"));
return;
}
if (!_throttlerProfileSave.CanPass())
{
ShowTip(AppUILocalization.GetTextByCNKey("操作过于频繁"));
return;
}
const int defaultGender = 1;
const string defaultBirthday = "2000-01-01";
if (string.IsNullOrEmpty(_registrationTicket))
{
ShowTip(AppUILocalization.GetTextByCNKey("资料票据已失效,请从验证码步骤重新注册"));
return;
}
int avatarId = avatarGroup != null
? avatarGroup.GetSelectedAvatarId()
: RoleManager.PresetGridIndexToAvatarId(_selectedAvatarIndex);
if (!RoleManager.IsAppUIPresetAvatarId(avatarId))
{
ShowTip(AppUILocalization.GetTextByCNKey("请选择头像"));
return;
}
StartCoroutine(LoginController.Instance.CompleteUserInfoByTicket(
_registrationTicket,
avatarId,
nickname,
defaultGender,
defaultBirthday,
"",
"",
"",
res =>
{
ShowTip(AppUILocalization.GetTextByCNKey(res.msg));
if (res.code != 0)
return;
StartCoroutine(AfterProfileSavedRoutine());
}));
}
IEnumerator AfterProfileSavedRoutine()
{
var loginOk = false;
yield return LoginController.Instance.LoginNormal(_storedAccount, _storedPassword, res =>
{
if (res.code != 0 || !(res.data is string loginToken))
return;
var parts = loginToken.Split('&');
if (parts.Length > 2)
CommonConfig.businessServerWsURL = parts[2];
PlayerPrefs.SetString(LoginMgr.LoginTokenKey, loginToken);
loginOk = true;
});
if (!loginOk)
ShowTip(AppUILocalization.GetTextByKey("home_loginAuthFail"));
var mgr = FindObjectOfType();
if (mgr != null)
mgr.showLoginView();
else
{
// TODO(AppUI.RegisterView): LoginMgr 不在当前场景时,改为跳转到登录场景或打开 AppUI 登录页。
Debug.LogWarning("[AppUI.RegisterView] 未找到 LoginMgr,无法自动返回登录页。");
}
}
}
}