You've already forked CC-Framework.BriskGameServer
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 88b7d9c757 | |||
| 9c57faa16c | |||
| 3a4f2f6652 | |||
| 4fd2222d6d | |||
| 0e5cab4f27 | |||
| a257a8a912 |
@@ -198,7 +198,16 @@ public static class Brisk
|
|||||||
|
|
||||||
internal static void NotifyAuthExpired(BriskAuthExpiredException exception)
|
internal static void NotifyAuthExpired(BriskAuthExpiredException exception)
|
||||||
{
|
{
|
||||||
s_context?.ErrorPresenter?.ShowAuthExpired(exception);
|
NotifyAuthExpired(exception, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void NotifyAuthExpired(BriskAuthExpiredException exception, bool showDefaultPresenter)
|
||||||
|
{
|
||||||
|
if (showDefaultPresenter)
|
||||||
|
{
|
||||||
|
s_context?.ErrorPresenter?.ShowAuthExpired(exception);
|
||||||
|
}
|
||||||
|
|
||||||
OnAuthExpired?.Invoke(exception);
|
OnAuthExpired?.Invoke(exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,6 +250,15 @@ public static class Brisk
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (storedSession.ExpiresAt.HasValue && storedSession.ExpiresAt.Value <= DateTimeOffset.UtcNow)
|
||||||
|
{
|
||||||
|
await HandleExpiredStartupSessionAsync(
|
||||||
|
context,
|
||||||
|
storedSession,
|
||||||
|
new BriskAuthExpiredException("Stored session expired before initialization."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
context.Session.Update(
|
context.Session.Update(
|
||||||
storedSession.AccessToken,
|
storedSession.AccessToken,
|
||||||
storedSession.ExpiresAt,
|
storedSession.ExpiresAt,
|
||||||
@@ -268,9 +286,7 @@ public static class Brisk
|
|||||||
}
|
}
|
||||||
catch (BriskAuthExpiredException exception)
|
catch (BriskAuthExpiredException exception)
|
||||||
{
|
{
|
||||||
context.Session.Clear();
|
await HandleExpiredStartupSessionAsync(context, storedSession, exception);
|
||||||
await context.TokenStore.ClearAsync();
|
|
||||||
NotifyAuthExpired(exception);
|
|
||||||
}
|
}
|
||||||
catch (BriskBlockingException exception)
|
catch (BriskBlockingException exception)
|
||||||
{
|
{
|
||||||
@@ -278,4 +294,36 @@ public static class Brisk
|
|||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static async Task HandleExpiredStartupSessionAsync(BriskContext context, BriskStoredSession storedSession, BriskAuthExpiredException exception)
|
||||||
|
{
|
||||||
|
context.Session.Clear();
|
||||||
|
await context.TokenStore.ClearAsync();
|
||||||
|
NotifyAuthExpired(exception, false);
|
||||||
|
|
||||||
|
if (!context.Options.AutoReloginOnInitialize || !HasStoredLoginIdentity(storedSession))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await Auth.LoginWithUserIdAsync(storedSession.LoginProvider, storedSession.LoginUserId);
|
||||||
|
}
|
||||||
|
catch (BriskBlockingException)
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// 初始化期静默重登失败时,交给业务层后续第三方登录流程重新调用 Brisk.Auth。
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool HasStoredLoginIdentity(BriskStoredSession storedSession)
|
||||||
|
{
|
||||||
|
return storedSession != null
|
||||||
|
&& !string.IsNullOrWhiteSpace(storedSession.LoginProvider)
|
||||||
|
&& !string.IsNullOrWhiteSpace(storedSession.LoginUserId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,74 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using UnityEngine.UIElements;
|
||||||
|
|
||||||
|
[DefaultExecutionOrder(10000)]
|
||||||
internal sealed class BriskDefaultErrorDialog : MonoBehaviour
|
internal sealed class BriskDefaultErrorDialog : MonoBehaviour
|
||||||
{
|
{
|
||||||
private const string HostName = "BriskDefaultErrorDialog";
|
private const string HostName = "BriskDefaultErrorDialog";
|
||||||
|
private const float OpenDuration = 0.24f;
|
||||||
|
private const float CloseDuration = 0.2f;
|
||||||
|
|
||||||
|
private static readonly Color Ink = ColorFromHex(0x05060a);
|
||||||
|
private static readonly Color InkSoft = ColorFromHex(0x1b1d24);
|
||||||
|
private static readonly Color Muted = ColorFromHex(0x5f6572);
|
||||||
|
private static readonly Color Line = ColorFromHex(0xdde2ea);
|
||||||
|
private static readonly Color Paper = ColorFromHex(0xffffff);
|
||||||
|
private static readonly Color Soft = ColorFromHex(0xf7f8fb);
|
||||||
|
private static readonly Color Blue = ColorFromHex(0x2563ff);
|
||||||
|
private static readonly Color Teal = ColorFromHex(0x23d6c8);
|
||||||
|
private static readonly Color Violet = ColorFromHex(0x8b5cff);
|
||||||
|
private static readonly Color Dark = ColorFromHex(0x050506);
|
||||||
|
|
||||||
private static BriskDefaultErrorDialog s_instance;
|
private static BriskDefaultErrorDialog s_instance;
|
||||||
|
|
||||||
private string _title;
|
private UIDocument _document;
|
||||||
private string _message;
|
private PanelSettings _panelSettings;
|
||||||
private string _confirmText;
|
private VisualElement _root;
|
||||||
|
private VisualElement _stage;
|
||||||
|
private VisualElement _panel;
|
||||||
|
private Label _eyebrowLabel;
|
||||||
|
private Label _titleLabel;
|
||||||
|
private Label _messageLabel;
|
||||||
|
private VisualElement _spacer;
|
||||||
|
private VisualElement _divider;
|
||||||
|
private VisualElement _confirmButton;
|
||||||
|
private Label _confirmLabel;
|
||||||
private Action _onConfirm;
|
private Action _onConfirm;
|
||||||
|
private Coroutine _animation;
|
||||||
private bool _visible;
|
private bool _visible;
|
||||||
|
private bool _closeOnBackdrop;
|
||||||
|
private bool _closeOnEscape;
|
||||||
|
private GUIStyle _legacyTitleStyle;
|
||||||
|
private GUIStyle _legacyMessageStyle;
|
||||||
|
private GUIStyle _legacyEyebrowStyle;
|
||||||
|
private GUIStyle _legacyButtonStyle;
|
||||||
|
private Texture2D _legacyButtonTexture;
|
||||||
|
private Texture2D _legacyButtonHoverTexture;
|
||||||
|
|
||||||
public static void Show(string title, string message, string confirmText, Action onConfirm = null)
|
internal static bool IsVisible => s_instance != null && s_instance._visible;
|
||||||
|
|
||||||
|
internal static void RenderTopmostLegacyOverlay()
|
||||||
|
{
|
||||||
|
if (s_instance == null || !s_instance._visible)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
s_instance.DrawLegacyOverlay();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Show(string title, string message, string confirmText, Action onConfirm = null, bool closeOnBackdrop = false, bool closeOnEscape = false)
|
||||||
{
|
{
|
||||||
var host = EnsureInstance();
|
var host = EnsureInstance();
|
||||||
host._title = string.IsNullOrWhiteSpace(title) ? "Brisk" : title;
|
host.ShowInternal(
|
||||||
host._message = string.IsNullOrWhiteSpace(message) ? "Unknown error." : message;
|
string.IsNullOrWhiteSpace(title) ? "Brisk" : title,
|
||||||
host._confirmText = string.IsNullOrWhiteSpace(confirmText) ? "OK" : confirmText;
|
string.IsNullOrWhiteSpace(message) ? "服务暂时不可用,请稍后再试。" : message,
|
||||||
host._onConfirm = onConfirm;
|
string.IsNullOrWhiteSpace(confirmText) ? "知道了" : confirmText,
|
||||||
host._visible = true;
|
onConfirm,
|
||||||
|
closeOnBackdrop,
|
||||||
|
closeOnEscape);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static BriskDefaultErrorDialog EnsureInstance()
|
private static BriskDefaultErrorDialog EnsureInstance()
|
||||||
@@ -46,6 +94,249 @@ internal sealed class BriskDefaultErrorDialog : MonoBehaviour
|
|||||||
return s_instance;
|
return s_instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ShowInternal(string title, string message, string confirmText, Action onConfirm, bool closeOnBackdrop, bool closeOnEscape)
|
||||||
|
{
|
||||||
|
EnsureDocument();
|
||||||
|
ApplySafeAreaPadding();
|
||||||
|
|
||||||
|
_titleLabel.text = title;
|
||||||
|
_messageLabel.text = message;
|
||||||
|
_confirmLabel.text = confirmText;
|
||||||
|
_onConfirm = onConfirm;
|
||||||
|
_closeOnBackdrop = closeOnBackdrop;
|
||||||
|
_closeOnEscape = closeOnEscape;
|
||||||
|
_visible = true;
|
||||||
|
|
||||||
|
StartDialogAnimation(PlayOpenAnimation());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureDocument()
|
||||||
|
{
|
||||||
|
if (_document != null)
|
||||||
|
{
|
||||||
|
ApplyResponsiveLayout();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_panelSettings = ScriptableObject.CreateInstance<PanelSettings>();
|
||||||
|
_panelSettings.name = HostName + "PanelSettings";
|
||||||
|
_panelSettings.scaleMode = PanelScaleMode.ConstantPixelSize;
|
||||||
|
_panelSettings.scale = 1f;
|
||||||
|
_panelSettings.sortingOrder = 32767f;
|
||||||
|
|
||||||
|
_document = gameObject.AddComponent<UIDocument>();
|
||||||
|
_document.panelSettings = _panelSettings;
|
||||||
|
_document.sortingOrder = 32767f;
|
||||||
|
|
||||||
|
BuildVisualTree(_document.rootVisualElement);
|
||||||
|
ApplyResponsiveLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BuildVisualTree(VisualElement documentRoot)
|
||||||
|
{
|
||||||
|
documentRoot.Clear();
|
||||||
|
documentRoot.pickingMode = PickingMode.Ignore;
|
||||||
|
documentRoot.style.position = Position.Absolute;
|
||||||
|
documentRoot.style.left = 0f;
|
||||||
|
documentRoot.style.top = 0f;
|
||||||
|
documentRoot.style.right = 0f;
|
||||||
|
documentRoot.style.bottom = 0f;
|
||||||
|
documentRoot.style.width = Length.Percent(100f);
|
||||||
|
documentRoot.style.height = Length.Percent(100f);
|
||||||
|
documentRoot.style.flexGrow = 1f;
|
||||||
|
|
||||||
|
_root = new VisualElement
|
||||||
|
{
|
||||||
|
pickingMode = PickingMode.Position,
|
||||||
|
focusable = true
|
||||||
|
};
|
||||||
|
_root.style.position = Position.Absolute;
|
||||||
|
_root.style.left = 0;
|
||||||
|
_root.style.top = 0;
|
||||||
|
_root.style.right = 0;
|
||||||
|
_root.style.bottom = 0;
|
||||||
|
_root.style.display = DisplayStyle.None;
|
||||||
|
_root.style.opacity = 0f;
|
||||||
|
_root.style.backgroundColor = new Color(0.02f, 0.02f, 0.024f, 0.58f);
|
||||||
|
_root.style.justifyContent = Justify.Center;
|
||||||
|
_root.style.alignItems = Align.Center;
|
||||||
|
_root.RegisterCallback<KeyDownEvent>(HandleRootKeyDown);
|
||||||
|
_root.RegisterCallback<PointerDownEvent>(evt =>
|
||||||
|
{
|
||||||
|
if (_closeOnBackdrop && evt.target == _root)
|
||||||
|
{
|
||||||
|
BeginClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
evt.StopPropagation();
|
||||||
|
});
|
||||||
|
documentRoot.Add(_root);
|
||||||
|
|
||||||
|
_stage = new VisualElement();
|
||||||
|
_stage.pickingMode = PickingMode.Ignore;
|
||||||
|
_stage.style.position = Position.Relative;
|
||||||
|
_stage.style.width = Length.Percent(82f);
|
||||||
|
_stage.style.maxWidth = 900f;
|
||||||
|
_stage.style.opacity = 0f;
|
||||||
|
_stage.style.marginTop = 14f;
|
||||||
|
_root.Add(_stage);
|
||||||
|
|
||||||
|
var shadow = new VisualElement();
|
||||||
|
shadow.pickingMode = PickingMode.Ignore;
|
||||||
|
shadow.style.position = Position.Absolute;
|
||||||
|
shadow.style.left = 0f;
|
||||||
|
shadow.style.right = 0f;
|
||||||
|
shadow.style.top = 10f;
|
||||||
|
shadow.style.bottom = -10f;
|
||||||
|
shadow.style.backgroundColor = new Color(0f, 0f, 0f, 0.09f);
|
||||||
|
shadow.style.borderTopLeftRadius = 18f;
|
||||||
|
shadow.style.borderTopRightRadius = 18f;
|
||||||
|
shadow.style.borderBottomLeftRadius = 18f;
|
||||||
|
shadow.style.borderBottomRightRadius = 18f;
|
||||||
|
_stage.Add(shadow);
|
||||||
|
|
||||||
|
_panel = new VisualElement
|
||||||
|
{
|
||||||
|
pickingMode = PickingMode.Position
|
||||||
|
};
|
||||||
|
_panel.style.width = Length.Percent(100f);
|
||||||
|
_panel.style.minHeight = 360f;
|
||||||
|
_panel.style.paddingTop = 36f;
|
||||||
|
_panel.style.paddingRight = 36f;
|
||||||
|
_panel.style.paddingBottom = 36f;
|
||||||
|
_panel.style.paddingLeft = 36f;
|
||||||
|
_panel.style.backgroundColor = Paper;
|
||||||
|
_panel.style.borderTopWidth = 1f;
|
||||||
|
_panel.style.borderRightWidth = 1f;
|
||||||
|
_panel.style.borderBottomWidth = 1f;
|
||||||
|
_panel.style.borderLeftWidth = 1f;
|
||||||
|
_panel.style.borderTopColor = Line;
|
||||||
|
_panel.style.borderRightColor = Line;
|
||||||
|
_panel.style.borderBottomColor = Line;
|
||||||
|
_panel.style.borderLeftColor = Line;
|
||||||
|
_panel.style.borderTopLeftRadius = 18f;
|
||||||
|
_panel.style.borderTopRightRadius = 18f;
|
||||||
|
_panel.style.borderBottomLeftRadius = 18f;
|
||||||
|
_panel.style.borderBottomRightRadius = 18f;
|
||||||
|
_panel.style.overflow = Overflow.Hidden;
|
||||||
|
_stage.Add(_panel);
|
||||||
|
|
||||||
|
var accent = new VisualElement();
|
||||||
|
accent.pickingMode = PickingMode.Ignore;
|
||||||
|
accent.style.position = Position.Absolute;
|
||||||
|
accent.style.top = 0f;
|
||||||
|
accent.style.left = 0f;
|
||||||
|
accent.style.right = 0f;
|
||||||
|
accent.style.height = 2f;
|
||||||
|
accent.style.flexDirection = FlexDirection.Row;
|
||||||
|
_panel.Add(accent);
|
||||||
|
AddAccentSegment(accent, Blue);
|
||||||
|
AddAccentSegment(accent, Teal);
|
||||||
|
AddAccentSegment(accent, Violet);
|
||||||
|
|
||||||
|
_eyebrowLabel = new Label("BRISK SERVICE");
|
||||||
|
_eyebrowLabel.style.color = Muted;
|
||||||
|
_eyebrowLabel.style.fontSize = 13f;
|
||||||
|
_eyebrowLabel.style.unityFontStyleAndWeight = FontStyle.Bold;
|
||||||
|
_eyebrowLabel.style.letterSpacing = 1.4f;
|
||||||
|
_eyebrowLabel.style.height = 18f;
|
||||||
|
_eyebrowLabel.style.flexShrink = 0f;
|
||||||
|
_eyebrowLabel.style.marginBottom = 12f;
|
||||||
|
_panel.Add(_eyebrowLabel);
|
||||||
|
|
||||||
|
_titleLabel = new Label();
|
||||||
|
_titleLabel.style.color = Ink;
|
||||||
|
_titleLabel.style.fontSize = 30f;
|
||||||
|
_titleLabel.style.unityFontStyleAndWeight = FontStyle.Bold;
|
||||||
|
_titleLabel.style.whiteSpace = WhiteSpace.Normal;
|
||||||
|
_titleLabel.style.unityTextAlign = TextAnchor.MiddleLeft;
|
||||||
|
_titleLabel.style.minHeight = 40f;
|
||||||
|
_titleLabel.style.flexShrink = 0f;
|
||||||
|
_titleLabel.style.marginBottom = 16f;
|
||||||
|
_panel.Add(_titleLabel);
|
||||||
|
|
||||||
|
_messageLabel = new Label();
|
||||||
|
_messageLabel.style.color = Muted;
|
||||||
|
_messageLabel.style.fontSize = 21f;
|
||||||
|
_messageLabel.style.whiteSpace = WhiteSpace.Normal;
|
||||||
|
_messageLabel.style.unityTextAlign = TextAnchor.UpperLeft;
|
||||||
|
_messageLabel.style.minHeight = 64f;
|
||||||
|
_messageLabel.style.flexShrink = 0f;
|
||||||
|
_messageLabel.style.marginBottom = 22f;
|
||||||
|
_panel.Add(_messageLabel);
|
||||||
|
|
||||||
|
_spacer = new VisualElement();
|
||||||
|
_spacer.pickingMode = PickingMode.Ignore;
|
||||||
|
_spacer.style.flexGrow = 1f;
|
||||||
|
_spacer.style.minHeight = 18f;
|
||||||
|
_panel.Add(_spacer);
|
||||||
|
|
||||||
|
_divider = new VisualElement();
|
||||||
|
_divider.style.height = 1f;
|
||||||
|
_divider.style.flexShrink = 0f;
|
||||||
|
_divider.style.backgroundColor = Line;
|
||||||
|
_divider.style.marginBottom = 22f;
|
||||||
|
_panel.Add(_divider);
|
||||||
|
|
||||||
|
_confirmButton = new VisualElement
|
||||||
|
{
|
||||||
|
focusable = true,
|
||||||
|
pickingMode = PickingMode.Position
|
||||||
|
};
|
||||||
|
_confirmButton.style.height = 58f;
|
||||||
|
_confirmButton.style.minHeight = 58f;
|
||||||
|
_confirmButton.style.flexShrink = 0f;
|
||||||
|
_confirmButton.style.borderTopLeftRadius = 24f;
|
||||||
|
_confirmButton.style.borderTopRightRadius = 24f;
|
||||||
|
_confirmButton.style.borderBottomLeftRadius = 24f;
|
||||||
|
_confirmButton.style.borderBottomRightRadius = 24f;
|
||||||
|
_confirmButton.style.backgroundColor = Dark;
|
||||||
|
_confirmButton.style.justifyContent = Justify.Center;
|
||||||
|
_confirmButton.style.alignItems = Align.Center;
|
||||||
|
_confirmButton.RegisterCallback<PointerEnterEvent>(_ => _confirmButton.style.backgroundColor = InkSoft);
|
||||||
|
_confirmButton.RegisterCallback<PointerLeaveEvent>(_ => _confirmButton.style.backgroundColor = Dark);
|
||||||
|
_confirmButton.RegisterCallback<PointerDownEvent>(_ =>
|
||||||
|
{
|
||||||
|
_confirmButton.style.backgroundColor = Ink;
|
||||||
|
_confirmButton.style.marginTop = 1f;
|
||||||
|
});
|
||||||
|
_confirmButton.RegisterCallback<PointerUpEvent>(_ =>
|
||||||
|
{
|
||||||
|
_confirmButton.style.backgroundColor = InkSoft;
|
||||||
|
_confirmButton.style.marginTop = 0f;
|
||||||
|
});
|
||||||
|
_confirmButton.RegisterCallback<ClickEvent>(_ => BeginClose());
|
||||||
|
_confirmButton.RegisterCallback<KeyDownEvent>(HandleButtonKeyDown);
|
||||||
|
_panel.Add(_confirmButton);
|
||||||
|
|
||||||
|
_confirmLabel = new Label();
|
||||||
|
_confirmLabel.style.color = Color.white;
|
||||||
|
_confirmLabel.style.fontSize = 18f;
|
||||||
|
_confirmLabel.style.unityFontStyleAndWeight = FontStyle.Bold;
|
||||||
|
_confirmLabel.style.unityTextAlign = TextAnchor.MiddleCenter;
|
||||||
|
_confirmLabel.style.height = 58f;
|
||||||
|
_confirmButton.Add(_confirmLabel);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
if (_visible && _root != null)
|
||||||
|
{
|
||||||
|
ApplySafeAreaPadding();
|
||||||
|
ApplyResponsiveLayout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BeginClose()
|
||||||
|
{
|
||||||
|
if (!_visible || _root == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
StartDialogAnimation(PlayCloseAnimation());
|
||||||
|
}
|
||||||
|
|
||||||
private void OnGUI()
|
private void OnGUI()
|
||||||
{
|
{
|
||||||
if (!_visible)
|
if (!_visible)
|
||||||
@@ -53,36 +344,379 @@ internal sealed class BriskDefaultErrorDialog : MonoBehaviour
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var overlayRect = new Rect(0, 0, Screen.width, Screen.height);
|
DrawLegacyOverlay();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawLegacyOverlay()
|
||||||
|
{
|
||||||
|
EnsureLegacyStyles();
|
||||||
|
|
||||||
|
var previousDepth = GUI.depth;
|
||||||
|
var previousMatrix = GUI.matrix;
|
||||||
var previousColor = GUI.color;
|
var previousColor = GUI.color;
|
||||||
GUI.color = new Color(0f, 0f, 0f, 0.65f);
|
GUI.depth = -10000;
|
||||||
GUI.Box(overlayRect, GUIContent.none);
|
GUI.matrix = Matrix4x4.identity;
|
||||||
GUI.color = previousColor;
|
|
||||||
|
|
||||||
var width = Mathf.Min(460f, Screen.width - 40f);
|
DrawLegacyRect(new Rect(0f, 0f, Screen.width, Screen.height), new Color(0.02f, 0.02f, 0.024f, 0.58f));
|
||||||
var height = 220f;
|
|
||||||
var dialogRect = new Rect(
|
|
||||||
(Screen.width - width) * 0.5f,
|
|
||||||
(Screen.height - height) * 0.5f,
|
|
||||||
width,
|
|
||||||
height);
|
|
||||||
|
|
||||||
GUILayout.BeginArea(dialogRect, GUI.skin.window);
|
var scale = GetReadableScale();
|
||||||
GUILayout.Space(8f);
|
ApplyLegacyScale(scale);
|
||||||
GUILayout.Label(_title, GUI.skin.label);
|
|
||||||
GUILayout.Space(12f);
|
|
||||||
GUILayout.Label(_message, GUI.skin.label);
|
|
||||||
GUILayout.FlexibleSpace();
|
|
||||||
|
|
||||||
if (GUILayout.Button(_confirmText, GUILayout.Height(36f)))
|
var margin = GetDialogMargin(scale);
|
||||||
|
var padding = GetDialogPadding(scale);
|
||||||
|
var buttonHeight = GetDialogButtonHeight(scale);
|
||||||
|
var width = GetDialogWidth(margin);
|
||||||
|
var contentWidth = width - padding * 2f;
|
||||||
|
var titleHeight = Mathf.Max(34f, _legacyTitleStyle.CalcHeight(new GUIContent(_titleLabel.text), contentWidth));
|
||||||
|
var messageHeight = Mathf.Max(48f, _legacyMessageStyle.CalcHeight(new GUIContent(_messageLabel.text), contentWidth));
|
||||||
|
var minHeight = GetDialogMinHeight(scale, margin);
|
||||||
|
var contentHeight = padding * 2f + 18f * scale + 12f * scale + titleHeight + 16f * scale + messageHeight + 24f * scale + 1f + 22f * scale + buttonHeight;
|
||||||
|
var height = Mathf.Clamp(contentHeight, minHeight, Mathf.Max(minHeight, Screen.height - margin * 2f));
|
||||||
|
var rect = new Rect((Screen.width - width) * 0.5f, (Screen.height - height) * 0.5f, width, height);
|
||||||
|
|
||||||
|
DrawLegacyPanel(rect);
|
||||||
|
|
||||||
|
var x = rect.x + padding;
|
||||||
|
var y = rect.y + padding;
|
||||||
|
GUI.Label(new Rect(x, y, contentWidth, 18f * scale), "BRISK SERVICE", _legacyEyebrowStyle);
|
||||||
|
y += 30f * scale;
|
||||||
|
GUI.Label(new Rect(x, y, contentWidth, titleHeight), _titleLabel.text, _legacyTitleStyle);
|
||||||
|
y += titleHeight + 16f * scale;
|
||||||
|
GUI.Label(new Rect(x, y, contentWidth, messageHeight), _messageLabel.text, _legacyMessageStyle);
|
||||||
|
|
||||||
|
DrawLegacyRect(new Rect(x, rect.yMax - padding - buttonHeight - 22f * scale, contentWidth, 1f), Line);
|
||||||
|
var buttonRect = new Rect(x, rect.yMax - padding - buttonHeight, contentWidth, buttonHeight);
|
||||||
|
if (GUI.Button(buttonRect, _confirmLabel.text, _legacyButtonStyle))
|
||||||
{
|
{
|
||||||
var callback = _onConfirm;
|
BeginClose();
|
||||||
_visible = false;
|
|
||||||
_onConfirm = null;
|
|
||||||
callback?.Invoke();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GUILayout.Space(8f);
|
ConsumeBlockingEvent();
|
||||||
GUILayout.EndArea();
|
|
||||||
|
GUI.color = previousColor;
|
||||||
|
GUI.matrix = previousMatrix;
|
||||||
|
GUI.depth = previousDepth;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerator PlayOpenAnimation()
|
||||||
|
{
|
||||||
|
_root.style.display = DisplayStyle.Flex;
|
||||||
|
_root.style.opacity = 0f;
|
||||||
|
_stage.style.opacity = 0f;
|
||||||
|
_stage.style.marginTop = 14f;
|
||||||
|
|
||||||
|
yield return null;
|
||||||
|
_root.Focus();
|
||||||
|
|
||||||
|
var elapsed = 0f;
|
||||||
|
while (elapsed < OpenDuration)
|
||||||
|
{
|
||||||
|
elapsed += Time.unscaledDeltaTime;
|
||||||
|
var eased = EaseOutCubic(Mathf.Clamp01(elapsed / OpenDuration));
|
||||||
|
_root.style.opacity = eased;
|
||||||
|
_stage.style.opacity = eased;
|
||||||
|
_stage.style.marginTop = Mathf.Lerp(14f, 0f, eased);
|
||||||
|
yield return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_root.style.opacity = 1f;
|
||||||
|
_stage.style.opacity = 1f;
|
||||||
|
_stage.style.marginTop = 0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerator PlayCloseAnimation()
|
||||||
|
{
|
||||||
|
var callback = _onConfirm;
|
||||||
|
_onConfirm = null;
|
||||||
|
|
||||||
|
var elapsed = 0f;
|
||||||
|
while (elapsed < CloseDuration)
|
||||||
|
{
|
||||||
|
elapsed += Time.unscaledDeltaTime;
|
||||||
|
var eased = EaseInCubic(Mathf.Clamp01(elapsed / CloseDuration));
|
||||||
|
var alpha = 1f - eased;
|
||||||
|
_root.style.opacity = alpha;
|
||||||
|
_stage.style.opacity = alpha;
|
||||||
|
_stage.style.marginTop = Mathf.Lerp(0f, 10f, eased);
|
||||||
|
yield return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_root.style.display = DisplayStyle.None;
|
||||||
|
_visible = false;
|
||||||
|
callback?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StartDialogAnimation(IEnumerator animation)
|
||||||
|
{
|
||||||
|
if (_animation != null)
|
||||||
|
{
|
||||||
|
StopCoroutine(_animation);
|
||||||
|
}
|
||||||
|
|
||||||
|
_animation = StartCoroutine(animation);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplySafeAreaPadding()
|
||||||
|
{
|
||||||
|
var safeArea = Screen.safeArea;
|
||||||
|
var basePadding = Mathf.Max(20f, Mathf.Min(Screen.width, Screen.height) * 0.04f);
|
||||||
|
_root.style.paddingLeft = Mathf.Max(basePadding, safeArea.xMin + basePadding);
|
||||||
|
_root.style.paddingRight = Mathf.Max(basePadding, Screen.width - safeArea.xMax + basePadding);
|
||||||
|
_root.style.paddingTop = Mathf.Max(basePadding, Screen.height - safeArea.yMax + basePadding);
|
||||||
|
_root.style.paddingBottom = Mathf.Max(basePadding, safeArea.yMin + basePadding);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyResponsiveLayout()
|
||||||
|
{
|
||||||
|
if (_panel == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var scale = GetReadableScale();
|
||||||
|
var margin = GetDialogMargin(scale);
|
||||||
|
var maxWidth = Mathf.Min(900f, Mathf.Max(320f, Screen.width - margin * 2f));
|
||||||
|
var minHeight = GetDialogMinHeight(scale, margin);
|
||||||
|
var padding = GetDialogPadding(scale);
|
||||||
|
var buttonHeight = GetDialogButtonHeight(scale);
|
||||||
|
|
||||||
|
_stage.style.width = Length.Percent(82f);
|
||||||
|
_stage.style.maxWidth = maxWidth;
|
||||||
|
_panel.style.minHeight = minHeight;
|
||||||
|
_panel.style.paddingTop = padding;
|
||||||
|
_panel.style.paddingRight = padding;
|
||||||
|
_panel.style.paddingBottom = padding;
|
||||||
|
_panel.style.paddingLeft = padding;
|
||||||
|
_panel.style.borderTopLeftRadius = 20f * scale;
|
||||||
|
_panel.style.borderTopRightRadius = 20f * scale;
|
||||||
|
_panel.style.borderBottomLeftRadius = 20f * scale;
|
||||||
|
_panel.style.borderBottomRightRadius = 20f * scale;
|
||||||
|
|
||||||
|
_eyebrowLabel.style.fontSize = 12f * scale;
|
||||||
|
_eyebrowLabel.style.height = 18f * scale;
|
||||||
|
_eyebrowLabel.style.marginBottom = 12f * scale;
|
||||||
|
_titleLabel.style.fontSize = 28f * scale;
|
||||||
|
_titleLabel.style.minHeight = 40f * scale;
|
||||||
|
_titleLabel.style.marginBottom = 16f * scale;
|
||||||
|
_messageLabel.style.fontSize = 20f * scale;
|
||||||
|
_messageLabel.style.minHeight = 64f * scale;
|
||||||
|
_messageLabel.style.marginBottom = 22f * scale;
|
||||||
|
_spacer.style.minHeight = 18f * scale;
|
||||||
|
_divider.style.marginBottom = 22f * scale;
|
||||||
|
_confirmButton.style.height = buttonHeight;
|
||||||
|
_confirmButton.style.minHeight = buttonHeight;
|
||||||
|
_confirmButton.style.borderTopLeftRadius = buttonHeight * 0.5f;
|
||||||
|
_confirmButton.style.borderTopRightRadius = buttonHeight * 0.5f;
|
||||||
|
_confirmButton.style.borderBottomLeftRadius = buttonHeight * 0.5f;
|
||||||
|
_confirmButton.style.borderBottomRightRadius = buttonHeight * 0.5f;
|
||||||
|
_confirmLabel.style.fontSize = 17f * scale;
|
||||||
|
_confirmLabel.style.height = buttonHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleRootKeyDown(KeyDownEvent evt)
|
||||||
|
{
|
||||||
|
if (_closeOnEscape && evt.keyCode == KeyCode.Escape)
|
||||||
|
{
|
||||||
|
BeginClose();
|
||||||
|
evt.StopPropagation();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
evt.StopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleButtonKeyDown(KeyDownEvent evt)
|
||||||
|
{
|
||||||
|
if (evt.keyCode == KeyCode.Return || evt.keyCode == KeyCode.KeypadEnter || evt.keyCode == KeyCode.Space)
|
||||||
|
{
|
||||||
|
BeginClose();
|
||||||
|
evt.StopPropagation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDestroy()
|
||||||
|
{
|
||||||
|
if (s_instance == this)
|
||||||
|
{
|
||||||
|
s_instance = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_panelSettings != null)
|
||||||
|
{
|
||||||
|
Destroy(_panelSettings);
|
||||||
|
_panelSettings = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureLegacyStyles()
|
||||||
|
{
|
||||||
|
if (_legacyTitleStyle != null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_legacyButtonTexture = CreateTexture(Dark);
|
||||||
|
_legacyButtonHoverTexture = CreateTexture(InkSoft);
|
||||||
|
|
||||||
|
_legacyEyebrowStyle = new GUIStyle
|
||||||
|
{
|
||||||
|
fontStyle = FontStyle.Bold,
|
||||||
|
normal = { textColor = Muted }
|
||||||
|
};
|
||||||
|
|
||||||
|
_legacyTitleStyle = new GUIStyle
|
||||||
|
{
|
||||||
|
fontStyle = FontStyle.Bold,
|
||||||
|
wordWrap = true,
|
||||||
|
normal = { textColor = Ink }
|
||||||
|
};
|
||||||
|
|
||||||
|
_legacyMessageStyle = new GUIStyle
|
||||||
|
{
|
||||||
|
wordWrap = true,
|
||||||
|
normal = { textColor = Muted }
|
||||||
|
};
|
||||||
|
|
||||||
|
_legacyButtonStyle = new GUIStyle
|
||||||
|
{
|
||||||
|
alignment = TextAnchor.MiddleCenter,
|
||||||
|
fontStyle = FontStyle.Bold,
|
||||||
|
border = new RectOffset(24, 24, 24, 24),
|
||||||
|
normal =
|
||||||
|
{
|
||||||
|
background = _legacyButtonTexture,
|
||||||
|
textColor = Color.white
|
||||||
|
},
|
||||||
|
hover =
|
||||||
|
{
|
||||||
|
background = _legacyButtonHoverTexture,
|
||||||
|
textColor = Color.white
|
||||||
|
},
|
||||||
|
active =
|
||||||
|
{
|
||||||
|
background = _legacyButtonHoverTexture,
|
||||||
|
textColor = Color.white
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyLegacyScale(float scale)
|
||||||
|
{
|
||||||
|
_legacyEyebrowStyle.fontSize = Mathf.RoundToInt(12f * scale);
|
||||||
|
_legacyTitleStyle.fontSize = Mathf.RoundToInt(28f * scale);
|
||||||
|
_legacyMessageStyle.fontSize = Mathf.RoundToInt(20f * scale);
|
||||||
|
_legacyButtonStyle.fontSize = Mathf.RoundToInt(17f * scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawLegacyPanel(Rect rect)
|
||||||
|
{
|
||||||
|
DrawLegacyRect(new Rect(rect.x, rect.y + 12f, rect.width, rect.height), new Color(0f, 0f, 0f, 0.08f));
|
||||||
|
DrawLegacyRect(rect, Paper);
|
||||||
|
DrawLegacyRect(new Rect(rect.x, rect.y, rect.width, 1f), Line);
|
||||||
|
DrawLegacyRect(new Rect(rect.x, rect.yMax - 1f, rect.width, 1f), Line);
|
||||||
|
DrawLegacyRect(new Rect(rect.x, rect.y, 1f, rect.height), Line);
|
||||||
|
DrawLegacyRect(new Rect(rect.xMax - 1f, rect.y, 1f, rect.height), Line);
|
||||||
|
|
||||||
|
var segment = rect.width / 3f;
|
||||||
|
DrawLegacyRect(new Rect(rect.x, rect.y, segment, 2f), Blue);
|
||||||
|
DrawLegacyRect(new Rect(rect.x + segment, rect.y, segment, 2f), Teal);
|
||||||
|
DrawLegacyRect(new Rect(rect.x + segment * 2f, rect.y, rect.width - segment * 2f, 2f), Violet);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ConsumeBlockingEvent()
|
||||||
|
{
|
||||||
|
var evt = Event.current;
|
||||||
|
if (evt == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (evt.type)
|
||||||
|
{
|
||||||
|
case EventType.MouseDown:
|
||||||
|
case EventType.MouseUp:
|
||||||
|
case EventType.MouseDrag:
|
||||||
|
case EventType.ScrollWheel:
|
||||||
|
case EventType.KeyDown:
|
||||||
|
case EventType.KeyUp:
|
||||||
|
evt.Use();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Texture2D CreateTexture(Color color)
|
||||||
|
{
|
||||||
|
var texture = new Texture2D(1, 1, TextureFormat.RGBA32, false)
|
||||||
|
{
|
||||||
|
hideFlags = HideFlags.HideAndDontSave
|
||||||
|
};
|
||||||
|
texture.SetPixel(0, 0, color);
|
||||||
|
texture.Apply();
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DrawLegacyRect(Rect rect, Color color)
|
||||||
|
{
|
||||||
|
var previousColor = GUI.color;
|
||||||
|
GUI.color = color;
|
||||||
|
GUI.DrawTexture(rect, Texture2D.whiteTexture);
|
||||||
|
GUI.color = previousColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddAccentSegment(VisualElement parent, Color color)
|
||||||
|
{
|
||||||
|
var segment = new VisualElement();
|
||||||
|
segment.style.flexGrow = 1f;
|
||||||
|
segment.style.backgroundColor = color;
|
||||||
|
parent.Add(segment);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float GetReadableScale()
|
||||||
|
{
|
||||||
|
return Mathf.Clamp(Mathf.Min(Screen.width, Screen.height) / 720f, 1f, 1.22f);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float GetDialogMargin(float scale)
|
||||||
|
{
|
||||||
|
return Mathf.Max(24f * scale, Mathf.Min(Screen.width, Screen.height) * 0.06f);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float GetDialogPadding(float scale)
|
||||||
|
{
|
||||||
|
return 34f * scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float GetDialogButtonHeight(float scale)
|
||||||
|
{
|
||||||
|
return 56f * scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float GetDialogWidth(float margin)
|
||||||
|
{
|
||||||
|
return Mathf.Clamp(Screen.width * 0.82f, 320f, Mathf.Max(320f, Mathf.Min(900f, Screen.width - margin * 2f)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float GetDialogMinHeight(float scale, float margin)
|
||||||
|
{
|
||||||
|
var availableHeight = Mathf.Max(320f, Screen.height - margin * 2f);
|
||||||
|
return Mathf.Min(availableHeight, Mathf.Clamp(Screen.height * 0.24f, 360f * scale, 620f));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float EaseOutCubic(float t)
|
||||||
|
{
|
||||||
|
return 1f - Mathf.Pow(1f - t, 3f);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float EaseInCubic(float t)
|
||||||
|
{
|
||||||
|
return t * t * t;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Color ColorFromHex(int rgb)
|
||||||
|
{
|
||||||
|
var r = ((rgb >> 16) & 0xff) / 255f;
|
||||||
|
var g = ((rgb >> 8) & 0xff) / 255f;
|
||||||
|
var b = (rgb & 0xff) / 255f;
|
||||||
|
return new Color(r, g, b, 1f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,18 +13,22 @@ public sealed class BriskDefaultErrorPresenter : IBriskErrorPresenter
|
|||||||
|
|
||||||
public void ShowBlockingError(BriskBlockingException exception)
|
public void ShowBlockingError(BriskBlockingException exception)
|
||||||
{
|
{
|
||||||
var message = exception != null && !string.IsNullOrWhiteSpace(exception.Message)
|
var message = GetBlockingMessage(exception);
|
||||||
? exception.Message
|
|
||||||
: "A blocking error occurred.";
|
|
||||||
|
|
||||||
BriskDefaultErrorDialog.Show(
|
BriskDefaultErrorDialog.Show(
|
||||||
title: GetBlockingTitle(exception),
|
title: GetBlockingTitle(exception),
|
||||||
message: message,
|
message: message,
|
||||||
confirmText: "OK",
|
confirmText: "知道了",
|
||||||
onConfirm: () =>
|
onConfirm: () =>
|
||||||
{
|
{
|
||||||
var exitHandler = Brisk.IsInitialized ? Brisk.Options?.ExitHandler : null;
|
var exitHandler = Brisk.IsInitialized ? Brisk.GetRequiredContext().ExitHandler : null;
|
||||||
exitHandler?.Invoke();
|
if (exitHandler != null)
|
||||||
|
{
|
||||||
|
exitHandler.Invoke();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Application.Quit();
|
||||||
});
|
});
|
||||||
|
|
||||||
Debug.LogError("[Brisk] Blocking error: " + message);
|
Debug.LogError("[Brisk] Blocking error: " + message);
|
||||||
@@ -32,35 +36,63 @@ public sealed class BriskDefaultErrorPresenter : IBriskErrorPresenter
|
|||||||
|
|
||||||
public void ShowAuthExpired(BriskAuthExpiredException exception)
|
public void ShowAuthExpired(BriskAuthExpiredException exception)
|
||||||
{
|
{
|
||||||
var message = exception != null && !string.IsNullOrWhiteSpace(exception.Message)
|
var message = GetAuthExpiredMessage(exception);
|
||||||
? exception.Message
|
|
||||||
: "Login expired. Please sign in again.";
|
|
||||||
|
|
||||||
BriskDefaultErrorDialog.Show(
|
BriskDefaultErrorDialog.Show(
|
||||||
title: "Login Expired",
|
title: "登录已过期",
|
||||||
message: message,
|
message: message,
|
||||||
confirmText: "OK");
|
confirmText: "知道了");
|
||||||
|
|
||||||
Debug.LogWarning("[Brisk] Auth expired: " + message);
|
Debug.LogWarning("[Brisk] Auth expired: " + (exception != null ? exception.Message : message));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetBlockingTitle(BriskBlockingException exception)
|
private static string GetBlockingTitle(BriskBlockingException exception)
|
||||||
{
|
{
|
||||||
if (exception is BriskMaintenanceException)
|
if (exception is BriskMaintenanceException)
|
||||||
{
|
{
|
||||||
return "Maintenance";
|
return "服务维护中";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (exception is BriskAccountBannedException)
|
if (exception is BriskAccountBannedException)
|
||||||
{
|
{
|
||||||
return "Account Restricted";
|
return "账号受限";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (exception is BriskClientUpdateRequiredException)
|
if (exception is BriskClientUpdateRequiredException)
|
||||||
{
|
{
|
||||||
return "Update Required";
|
return "需要更新";
|
||||||
}
|
}
|
||||||
|
|
||||||
return "Service Unavailable";
|
return "服务暂不可用";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetBlockingMessage(BriskBlockingException exception)
|
||||||
|
{
|
||||||
|
if (exception is BriskMaintenanceException)
|
||||||
|
{
|
||||||
|
return "服务正在维护,请稍后再试。";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exception is BriskAccountBannedException)
|
||||||
|
{
|
||||||
|
return "当前账号暂时无法使用该服务。";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exception is BriskClientUpdateRequiredException)
|
||||||
|
{
|
||||||
|
return "当前版本过低,请更新后继续。";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "服务暂时不可用,请稍后再试。";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetAuthExpiredMessage(BriskAuthExpiredException exception)
|
||||||
|
{
|
||||||
|
if (exception != null && exception.Message != null && exception.Message.IndexOf("missing", StringComparison.OrdinalIgnoreCase) >= 0)
|
||||||
|
{
|
||||||
|
return "当前登录状态不可用,请重新登录。";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "登录状态已失效,请重新登录后继续。";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -249,8 +249,10 @@ internal static class BriskModelMapper
|
|||||||
ContentSizeBytes = BriskValueReader.GetLong(data, "content_size_bytes"),
|
ContentSizeBytes = BriskValueReader.GetLong(data, "content_size_bytes"),
|
||||||
ContentChecksum = BriskValueReader.GetString(data, "content_checksum"),
|
ContentChecksum = BriskValueReader.GetString(data, "content_checksum"),
|
||||||
LikeCount = BriskValueReader.GetLong(data, "like_count"),
|
LikeCount = BriskValueReader.GetLong(data, "like_count"),
|
||||||
|
TodayLikeCount = BriskValueReader.GetLong(data, "today_like_count"),
|
||||||
VisitCount = BriskValueReader.GetLong(data, "visit_count"),
|
VisitCount = BriskValueReader.GetLong(data, "visit_count"),
|
||||||
LikedByMe = BriskValueReader.GetBool(data, "liked_by_me"),
|
LikedByMe = BriskValueReader.GetBool(data, "liked_by_me"),
|
||||||
|
LikeResetAt = BriskValueReader.GetString(data, "like_reset_at"),
|
||||||
UpdatedAt = BriskValueReader.GetString(data, "updated_at")
|
UpdatedAt = BriskValueReader.GetString(data, "updated_at")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -276,7 +278,9 @@ internal static class BriskModelMapper
|
|||||||
ContentSizeBytes = BriskValueReader.GetLong(data, "content_size_bytes"),
|
ContentSizeBytes = BriskValueReader.GetLong(data, "content_size_bytes"),
|
||||||
ContentChecksum = BriskValueReader.GetString(data, "content_checksum"),
|
ContentChecksum = BriskValueReader.GetString(data, "content_checksum"),
|
||||||
LikeCount = BriskValueReader.GetLong(data, "like_count"),
|
LikeCount = BriskValueReader.GetLong(data, "like_count"),
|
||||||
|
TodayLikeCount = BriskValueReader.GetLong(data, "today_like_count"),
|
||||||
VisitCount = BriskValueReader.GetLong(data, "visit_count"),
|
VisitCount = BriskValueReader.GetLong(data, "visit_count"),
|
||||||
|
LikeResetAt = BriskValueReader.GetString(data, "like_reset_at"),
|
||||||
UpdatedAt = BriskValueReader.GetString(data, "updated_at")
|
UpdatedAt = BriskValueReader.GetString(data, "updated_at")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -309,7 +313,11 @@ internal static class BriskModelMapper
|
|||||||
return new BriskSpaceLikeResult
|
return new BriskSpaceLikeResult
|
||||||
{
|
{
|
||||||
Liked = BriskValueReader.GetBool(data, "liked"),
|
Liked = BriskValueReader.GetBool(data, "liked"),
|
||||||
LikeCount = BriskValueReader.GetLong(data, "like_count")
|
Created = BriskValueReader.GetBool(data, "created"),
|
||||||
|
LikedByMe = BriskValueReader.GetBool(data, "liked_by_me"),
|
||||||
|
LikeCount = BriskValueReader.GetLong(data, "like_count"),
|
||||||
|
TodayLikeCount = BriskValueReader.GetLong(data, "today_like_count"),
|
||||||
|
LikeResetAt = BriskValueReader.GetString(data, "like_reset_at")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,10 @@ public sealed class BriskOptions
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool ValidateSessionOnInitialize = true;
|
public bool ValidateSessionOnInitialize = true;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
/// 初始化恢复的本地会话已过期或失效时,是否使用本地保存的登录身份静默换取新会话。
|
||||||
|
/// </summary>
|
||||||
|
public bool AutoReloginOnInitialize = true;
|
||||||
|
/// <summary>
|
||||||
/// 自定义登录态持久化实现。
|
/// 自定义登录态持久化实现。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IBriskTokenStore TokenStore;
|
public IBriskTokenStore TokenStore;
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
public sealed class BriskSpaceLikeResult
|
public sealed class BriskSpaceLikeResult
|
||||||
{
|
{
|
||||||
public bool Liked;
|
public bool Liked;
|
||||||
|
public bool Created;
|
||||||
|
public bool LikedByMe;
|
||||||
public long LikeCount;
|
public long LikeCount;
|
||||||
|
public long TodayLikeCount;
|
||||||
|
public string LikeResetAt;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ public sealed class BriskSpaceStats
|
|||||||
public long ContentSizeBytes;
|
public long ContentSizeBytes;
|
||||||
public string ContentChecksum;
|
public string ContentChecksum;
|
||||||
public long LikeCount;
|
public long LikeCount;
|
||||||
|
public long TodayLikeCount;
|
||||||
public long VisitCount;
|
public long VisitCount;
|
||||||
|
public string LikeResetAt;
|
||||||
public string UpdatedAt;
|
public string UpdatedAt;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ public sealed class BriskSpaceView
|
|||||||
public long ContentSizeBytes;
|
public long ContentSizeBytes;
|
||||||
public string ContentChecksum;
|
public string ContentChecksum;
|
||||||
public long LikeCount;
|
public long LikeCount;
|
||||||
|
public long TodayLikeCount;
|
||||||
public long VisitCount;
|
public long VisitCount;
|
||||||
public bool LikedByMe;
|
public bool LikedByMe;
|
||||||
|
public string LikeResetAt;
|
||||||
public string UpdatedAt;
|
public string UpdatedAt;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,23 +68,23 @@ public sealed class BriskSpaceModule
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 按玩家 ID 获取最近点赞列表。
|
/// 按玩家 ID 获取点赞列表。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public async Task<IReadOnlyList<BriskSpaceLikeItem>> GetLikesByPlayerIdAsync(string playerId, int limit = 20)
|
public async Task<IReadOnlyList<BriskSpaceLikeItem>> GetLikesByPlayerIdAsync(string playerId, int limit = 20, bool currentCycleOnly = false)
|
||||||
{
|
{
|
||||||
ValidatePlayerId(playerId);
|
ValidatePlayerId(playerId);
|
||||||
|
|
||||||
return await ExecuteAsync(async context =>
|
return await ExecuteAsync(async context =>
|
||||||
{
|
{
|
||||||
var data = await context.HttpClient.GetRawDataAsync($"/spaces/{playerId}/likes", CreateLimitQuery(limit), true);
|
var data = await context.HttpClient.GetRawDataAsync($"/spaces/{playerId}/likes", CreateLikesQuery(limit, currentCycleOnly), true);
|
||||||
return (IReadOnlyList<BriskSpaceLikeItem>)BriskModelMapper.ToSpaceLikeItems(data);
|
return (IReadOnlyList<BriskSpaceLikeItem>)BriskModelMapper.ToSpaceLikeItems(data);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 按登录身份获取最近点赞列表。
|
/// 按登录身份获取点赞列表。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public async Task<IReadOnlyList<BriskSpaceLikeItem>> GetLikesByLoginIdentityAsync(string loginProvider, string loginUserId, int limit = 20)
|
public async Task<IReadOnlyList<BriskSpaceLikeItem>> GetLikesByLoginIdentityAsync(string loginProvider, string loginUserId, int limit = 20, bool currentCycleOnly = false)
|
||||||
{
|
{
|
||||||
ValidateLoginIdentity(loginProvider, loginUserId);
|
ValidateLoginIdentity(loginProvider, loginUserId);
|
||||||
|
|
||||||
@@ -92,6 +92,10 @@ public sealed class BriskSpaceModule
|
|||||||
{
|
{
|
||||||
var query = CreateLoginIdentityQuery(loginProvider, loginUserId);
|
var query = CreateLoginIdentityQuery(loginProvider, loginUserId);
|
||||||
query["limit"] = NormalizeLimit(limit);
|
query["limit"] = NormalizeLimit(limit);
|
||||||
|
if (currentCycleOnly)
|
||||||
|
{
|
||||||
|
query["scope"] = "cycle";
|
||||||
|
}
|
||||||
var data = await context.HttpClient.GetRawDataAsync("/spaces/by-login/likes", query, true);
|
var data = await context.HttpClient.GetRawDataAsync("/spaces/by-login/likes", query, true);
|
||||||
return (IReadOnlyList<BriskSpaceLikeItem>)BriskModelMapper.ToSpaceLikeItems(data);
|
return (IReadOnlyList<BriskSpaceLikeItem>)BriskModelMapper.ToSpaceLikeItems(data);
|
||||||
});
|
});
|
||||||
@@ -236,7 +240,7 @@ public sealed class BriskSpaceModule
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取我的访客列表。
|
/// 获取我的访客列表。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public async Task<IReadOnlyList<BriskSpaceVisit>> GetMyVisitsAsync(int limit = 20)
|
public async Task<IReadOnlyList<BriskSpaceVisit>> GetMyVisitsAsync(int limit = 50)
|
||||||
{
|
{
|
||||||
return await ExecuteAsync(async context =>
|
return await ExecuteAsync(async context =>
|
||||||
{
|
{
|
||||||
@@ -262,6 +266,17 @@ public sealed class BriskSpaceModule
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Dictionary<string, string> CreateLikesQuery(int limit, bool currentCycleOnly)
|
||||||
|
{
|
||||||
|
var query = CreateLimitQuery(limit);
|
||||||
|
if (currentCycleOnly)
|
||||||
|
{
|
||||||
|
query["scope"] = "cycle";
|
||||||
|
}
|
||||||
|
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
private static void ValidatePlayerId(string playerId)
|
private static void ValidatePlayerId(string playerId)
|
||||||
{
|
{
|
||||||
RequireNotEmpty(playerId, nameof(playerId));
|
RequireNotEmpty(playerId, nameof(playerId));
|
||||||
|
|||||||
@@ -112,6 +112,8 @@ public sealed class BriskQuickStartSample : MonoBehaviour
|
|||||||
private GUIStyle _statusStyle;
|
private GUIStyle _statusStyle;
|
||||||
private GUIStyle _listButtonStyle;
|
private GUIStyle _listButtonStyle;
|
||||||
private GUIStyle _modalStyle;
|
private GUIStyle _modalStyle;
|
||||||
|
private PropertyInfo _defaultDialogVisibleProperty;
|
||||||
|
private MethodInfo _defaultDialogRenderMethod;
|
||||||
|
|
||||||
private void OnEnable()
|
private void OnEnable()
|
||||||
{
|
{
|
||||||
@@ -142,6 +144,12 @@ public sealed class BriskQuickStartSample : MonoBehaviour
|
|||||||
|
|
||||||
private void OnGUI()
|
private void OnGUI()
|
||||||
{
|
{
|
||||||
|
if (ShouldRouteInputToDefaultDialog())
|
||||||
|
{
|
||||||
|
RenderDefaultErrorDialogTopmost();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
EnsureStyles();
|
EnsureStyles();
|
||||||
|
|
||||||
var scale = Mathf.Min(Screen.width / DesignWidth, Screen.height / DesignHeight);
|
var scale = Mathf.Min(Screen.width / DesignWidth, Screen.height / DesignHeight);
|
||||||
@@ -167,6 +175,54 @@ public sealed class BriskQuickStartSample : MonoBehaviour
|
|||||||
}
|
}
|
||||||
|
|
||||||
GUI.matrix = previousMatrix;
|
GUI.matrix = previousMatrix;
|
||||||
|
|
||||||
|
RenderDefaultErrorDialogTopmost();
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ShouldRouteInputToDefaultDialog()
|
||||||
|
{
|
||||||
|
if (!IsDefaultErrorDialogVisible() || Event.current == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (Event.current.type)
|
||||||
|
{
|
||||||
|
case EventType.Layout:
|
||||||
|
case EventType.Repaint:
|
||||||
|
return false;
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsDefaultErrorDialogVisible()
|
||||||
|
{
|
||||||
|
EnsureDefaultDialogReflection();
|
||||||
|
return _defaultDialogVisibleProperty != null && (bool)_defaultDialogVisibleProperty.GetValue(null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderDefaultErrorDialogTopmost()
|
||||||
|
{
|
||||||
|
EnsureDefaultDialogReflection();
|
||||||
|
_defaultDialogRenderMethod?.Invoke(null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureDefaultDialogReflection()
|
||||||
|
{
|
||||||
|
if (_defaultDialogVisibleProperty != null && _defaultDialogRenderMethod != null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var dialogType = typeof(Brisk).Assembly.GetType("BriskDefaultErrorDialog");
|
||||||
|
if (dialogType == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_defaultDialogVisibleProperty = dialogType.GetProperty("IsVisible", BindingFlags.Static | BindingFlags.NonPublic);
|
||||||
|
_defaultDialogRenderMethod = dialogType.GetMethod("RenderTopmostLegacyOverlay", BindingFlags.Static | BindingFlags.NonPublic);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawCurrentPage()
|
private void DrawCurrentPage()
|
||||||
@@ -219,6 +275,19 @@ public sealed class BriskQuickStartSample : MonoBehaviour
|
|||||||
GUILayout.Label("失败会停留在当前页,并把错误展示在底部状态栏。", _bodyStyle);
|
GUILayout.Label("失败会停留在当前页,并把错误展示在底部状态栏。", _bodyStyle);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
DrawCard("默认弹窗外观预览", () =>
|
||||||
|
{
|
||||||
|
GUILayout.Label("这些按钮只展示 SDK 默认弹窗外观,不会修改登录状态,也不会请求服务器。", _bodyStyle);
|
||||||
|
DrawInlineButtons(
|
||||||
|
new ButtonSpec("登录过期", PreviewAuthExpiredDialogAsync, false, true),
|
||||||
|
new ButtonSpec("缺少 Token", PreviewMissingTokenDialogAsync, false, true));
|
||||||
|
GUILayout.Space(8f);
|
||||||
|
DrawInlineButtons(
|
||||||
|
new ButtonSpec("服务维护", PreviewMaintenanceDialogAsync, false, true),
|
||||||
|
new ButtonSpec("账号受限", PreviewAccountBannedDialogAsync, false, true),
|
||||||
|
new ButtonSpec("需要更新", PreviewUpdateRequiredDialogAsync, false, true));
|
||||||
|
});
|
||||||
|
|
||||||
GUILayout.FlexibleSpace();
|
GUILayout.FlexibleSpace();
|
||||||
GUILayout.EndScrollView();
|
GUILayout.EndScrollView();
|
||||||
GUILayout.EndArea();
|
GUILayout.EndArea();
|
||||||
@@ -475,14 +544,16 @@ public sealed class BriskQuickStartSample : MonoBehaviour
|
|||||||
DrawValueRow("内容版本", _mySpaceView == null ? "-" : _mySpaceView.ContentVersion.ToString());
|
DrawValueRow("内容版本", _mySpaceView == null ? "-" : _mySpaceView.ContentVersion.ToString());
|
||||||
DrawValueRow("内容类型", _mySpaceView == null ? "-" : NullToDash(_mySpaceView.ContentType));
|
DrawValueRow("内容类型", _mySpaceView == null ? "-" : NullToDash(_mySpaceView.ContentType));
|
||||||
DrawValueRow("内容大小", _mySpaceView == null ? "-" : _mySpaceView.ContentSizeBytes + " bytes");
|
DrawValueRow("内容大小", _mySpaceView == null ? "-" : _mySpaceView.ContentSizeBytes + " bytes");
|
||||||
DrawValueRow("点赞数", _mySpaceStats == null ? "-" : _mySpaceStats.LikeCount.ToString());
|
DrawValueRow("累计点赞数", _mySpaceStats == null ? "-" : _mySpaceStats.LikeCount.ToString());
|
||||||
|
DrawValueRow("今日点赞数", _mySpaceStats == null ? "-" : _mySpaceStats.TodayLikeCount.ToString());
|
||||||
DrawValueRow("访问数", _mySpaceStats == null ? "-" : _mySpaceStats.VisitCount.ToString());
|
DrawValueRow("访问数", _mySpaceStats == null ? "-" : _mySpaceStats.VisitCount.ToString());
|
||||||
|
DrawValueRow("今日点赞重置", _mySpaceStats == null ? "-" : FormatLikeResetAt(_mySpaceStats.LikeResetAt));
|
||||||
DrawValueRow("更新时间", _mySpaceView == null ? "-" : NullToDash(_mySpaceView.UpdatedAt));
|
DrawValueRow("更新时间", _mySpaceView == null ? "-" : NullToDash(_mySpaceView.UpdatedAt));
|
||||||
GUILayout.Space(8f);
|
GUILayout.Space(8f);
|
||||||
GUILayout.Label("最近给我点赞的用户", _sectionTitleStyle);
|
GUILayout.Label("今天给我点赞的用户", _sectionTitleStyle);
|
||||||
if (_mySpaceLikes.Count == 0)
|
if (_mySpaceLikes.Count == 0)
|
||||||
{
|
{
|
||||||
GUILayout.Label("暂无点赞记录。", _bodyStyle);
|
GUILayout.Label("当前周期暂无点赞记录。", _bodyStyle);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -509,9 +580,11 @@ public sealed class BriskQuickStartSample : MonoBehaviour
|
|||||||
DrawValueRow("PlayerId", _targetSpaceView == null ? "-" : NullToDash(_targetSpaceView.PlayerId));
|
DrawValueRow("PlayerId", _targetSpaceView == null ? "-" : NullToDash(_targetSpaceView.PlayerId));
|
||||||
DrawValueRow("内容类型", _targetSpaceView == null ? "-" : NullToDash(_targetSpaceView.ContentType));
|
DrawValueRow("内容类型", _targetSpaceView == null ? "-" : NullToDash(_targetSpaceView.ContentType));
|
||||||
DrawValueRow("内容大小", _targetSpaceView == null ? "-" : _targetSpaceView.ContentSizeBytes + " bytes");
|
DrawValueRow("内容大小", _targetSpaceView == null ? "-" : _targetSpaceView.ContentSizeBytes + " bytes");
|
||||||
DrawValueRow("点赞数", _targetSpaceStats == null ? "-" : _targetSpaceStats.LikeCount.ToString());
|
DrawValueRow("累计点赞数", _targetSpaceStats == null ? "-" : _targetSpaceStats.LikeCount.ToString());
|
||||||
|
DrawValueRow("今日点赞数", _targetSpaceStats == null ? "-" : _targetSpaceStats.TodayLikeCount.ToString());
|
||||||
DrawValueRow("访问数", _targetSpaceStats == null ? "-" : _targetSpaceStats.VisitCount.ToString());
|
DrawValueRow("访问数", _targetSpaceStats == null ? "-" : _targetSpaceStats.VisitCount.ToString());
|
||||||
DrawValueRow("我的点赞状态", _targetSpaceView != null && _targetSpaceView.LikedByMe ? "已点赞" : "未点赞");
|
DrawValueRow("今日点赞状态", _targetSpaceView != null && _targetSpaceView.LikedByMe ? "今日已点赞" : "今日未点赞");
|
||||||
|
DrawValueRow("今日点赞重置", _targetSpaceView == null ? "-" : FormatLikeResetAt(_targetSpaceView.LikeResetAt));
|
||||||
GUILayout.Space(8f);
|
GUILayout.Space(8f);
|
||||||
GUILayout.Label("空间内容", _sectionTitleStyle);
|
GUILayout.Label("空间内容", _sectionTitleStyle);
|
||||||
GUILayout.TextArea(_targetSpaceContentText ?? string.Empty, _textAreaStyle, GUILayout.Height(240f));
|
GUILayout.TextArea(_targetSpaceContentText ?? string.Empty, _textAreaStyle, GUILayout.Height(240f));
|
||||||
@@ -520,8 +593,8 @@ public sealed class BriskQuickStartSample : MonoBehaviour
|
|||||||
DrawCard("操作", () =>
|
DrawCard("操作", () =>
|
||||||
{
|
{
|
||||||
DrawInlineButtons(
|
DrawInlineButtons(
|
||||||
new ButtonSpec("点赞", LikeTargetSpaceAsync, false, true),
|
new ButtonSpec("今日点赞", LikeTargetSpaceAsync, false, _targetSpaceView != null && !_targetSpaceView.LikedByMe),
|
||||||
new ButtonSpec("取消点赞", UnlikeTargetSpaceAsync, false, true));
|
new ButtonSpec("撤销今日点赞", UnlikeTargetSpaceAsync, false, _targetSpaceView != null && _targetSpaceView.LikedByMe));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -559,6 +632,36 @@ public sealed class BriskQuickStartSample : MonoBehaviour
|
|||||||
_page = SamplePage.Home;
|
_page = SamplePage.Home;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Task PreviewAuthExpiredDialogAsync()
|
||||||
|
{
|
||||||
|
BriskDefaultErrorPresenter.Instance.ShowAuthExpired(new BriskAuthExpiredException("invalid token"));
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task PreviewMissingTokenDialogAsync()
|
||||||
|
{
|
||||||
|
BriskDefaultErrorPresenter.Instance.ShowAuthExpired(new BriskAuthExpiredException("Missing access token."));
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task PreviewMaintenanceDialogAsync()
|
||||||
|
{
|
||||||
|
BriskDefaultErrorPresenter.Instance.ShowBlockingError(new BriskMaintenanceException("maintenance"));
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task PreviewAccountBannedDialogAsync()
|
||||||
|
{
|
||||||
|
BriskDefaultErrorPresenter.Instance.ShowBlockingError(new BriskAccountBannedException("account banned"));
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task PreviewUpdateRequiredDialogAsync()
|
||||||
|
{
|
||||||
|
BriskDefaultErrorPresenter.Instance.ShowBlockingError(new BriskClientUpdateRequiredException("client update required"));
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
private async Task RefreshHomeAsync()
|
private async Task RefreshHomeAsync()
|
||||||
{
|
{
|
||||||
_me = await Brisk.Player.GetMeAsync();
|
_me = await Brisk.Player.GetMeAsync();
|
||||||
@@ -636,7 +739,7 @@ public sealed class BriskQuickStartSample : MonoBehaviour
|
|||||||
|
|
||||||
_mySpaceView = await Brisk.Space.GetByPlayerIdAsync(Brisk.PlayerId);
|
_mySpaceView = await Brisk.Space.GetByPlayerIdAsync(Brisk.PlayerId);
|
||||||
_mySpaceStats = await Brisk.Space.GetStatsByPlayerIdAsync(Brisk.PlayerId);
|
_mySpaceStats = await Brisk.Space.GetStatsByPlayerIdAsync(Brisk.PlayerId);
|
||||||
_mySpaceLikes = await Brisk.Space.GetLikesByPlayerIdAsync(Brisk.PlayerId, 10);
|
_mySpaceLikes = await Brisk.Space.GetLikesByPlayerIdAsync(Brisk.PlayerId, 10, true);
|
||||||
SpacePayloadText = await DownloadSpaceTextAsync(_mySpaceView, () => Brisk.Space.DownloadContentByPlayerIdAsync(Brisk.PlayerId));
|
SpacePayloadText = await DownloadSpaceTextAsync(_mySpaceView, () => Brisk.Space.DownloadContentByPlayerIdAsync(Brisk.PlayerId));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -673,13 +776,15 @@ public sealed class BriskQuickStartSample : MonoBehaviour
|
|||||||
|
|
||||||
private async Task LikeTargetSpaceAsync()
|
private async Task LikeTargetSpaceAsync()
|
||||||
{
|
{
|
||||||
await Brisk.Space.LikeByPlayerIdAsync(SpacePlayerId);
|
var result = await Brisk.Space.LikeByPlayerIdAsync(SpacePlayerId);
|
||||||
|
Log("空间今日点赞结果: created=" + result.Created + ", total=" + result.LikeCount + ", today=" + result.TodayLikeCount);
|
||||||
await LoadTargetSpaceAsync();
|
await LoadTargetSpaceAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task UnlikeTargetSpaceAsync()
|
private async Task UnlikeTargetSpaceAsync()
|
||||||
{
|
{
|
||||||
await Brisk.Space.UnlikeByPlayerIdAsync(SpacePlayerId);
|
var result = await Brisk.Space.UnlikeByPlayerIdAsync(SpacePlayerId);
|
||||||
|
Log("空间撤销今日点赞结果: total=" + result.LikeCount + ", today=" + result.TodayLikeCount);
|
||||||
await LoadTargetSpaceAsync();
|
await LoadTargetSpaceAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1120,6 +1225,22 @@ public sealed class BriskQuickStartSample : MonoBehaviour
|
|||||||
return string.IsNullOrWhiteSpace(value) ? "-" : value;
|
return string.IsNullOrWhiteSpace(value) ? "-" : value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string FormatLikeResetAt(string value)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
|
{
|
||||||
|
return "-";
|
||||||
|
}
|
||||||
|
|
||||||
|
DateTimeOffset parsed;
|
||||||
|
if (!DateTimeOffset.TryParse(value, out parsed))
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsed.LocalDateTime.ToString("yyyy-MM-dd HH:mm:ss");
|
||||||
|
}
|
||||||
|
|
||||||
private static async Task<string> DownloadSpaceTextAsync(BriskSpaceView view, Func<Task<BriskSpaceContentDownloadResult>> downloadContent)
|
private static async Task<string> DownloadSpaceTextAsync(BriskSpaceView view, Func<Task<BriskSpaceContentDownloadResult>> downloadContent)
|
||||||
{
|
{
|
||||||
if (view == null || !view.ContentExists || downloadContent == null)
|
if (view == null || !view.ContentExists || downloadContent == null)
|
||||||
|
|||||||
@@ -259,7 +259,7 @@ MonoBehaviour:
|
|||||||
m_Name:
|
m_Name:
|
||||||
m_EditorClassIdentifier:
|
m_EditorClassIdentifier:
|
||||||
BaseUrl: https://brisk.lightyears.ltd
|
BaseUrl: https://brisk.lightyears.ltd
|
||||||
GameKey: briskh5verify
|
GameKey: game_a1faf5ee93d0
|
||||||
ClientVersion: 1.0.0
|
ClientVersion: 1.0.0
|
||||||
DeviceId: editor-device
|
DeviceId: editor-device
|
||||||
ValidateSessionOnInitialize: 1
|
ValidateSessionOnInitialize: 1
|
||||||
@@ -268,7 +268,7 @@ MonoBehaviour:
|
|||||||
LoginCode:
|
LoginCode:
|
||||||
Nickname: "Unity\u793A\u4F8B\u73A9\u5BB6"
|
Nickname: "Unity\u793A\u4F8B\u73A9\u5BB6"
|
||||||
AvatarUrl:
|
AvatarUrl:
|
||||||
RankKey: h5-verify-rank-20260410034312-5cdd
|
RankKey: rank-20260411062004-2bce
|
||||||
SubmitScoreValue: 128
|
SubmitScoreValue: 128
|
||||||
LeaderboardLimit: 10
|
LeaderboardLimit: 10
|
||||||
AroundMeRange: 5
|
AroundMeRange: 5
|
||||||
|
|||||||
@@ -1,5 +1,21 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.4.1
|
||||||
|
|
||||||
|
- Startup restore now silently clears locally expired or server-rejected sessions without showing the default auth-expired dialog
|
||||||
|
- Added silent initialization relogin from the stored `login_provider` and `login_user_id` when available
|
||||||
|
- Localized the default blocking/auth-expired dialogs to Chinese and stopped showing raw server messages to players
|
||||||
|
- Rebuilt the default dialog with a larger mobile-readable Runtime UI Toolkit layout and topmost legacy IMGUI bridge
|
||||||
|
- Default dialogs no longer close from backdrop or Escape; blocking dialogs now invoke the configured exit handler or fall back to `Application.Quit()`
|
||||||
|
|
||||||
|
## 0.4.0
|
||||||
|
|
||||||
|
- Added daily-cycle space like fields and result semantics to the Unity SDK models
|
||||||
|
- Added current-cycle filtering support for space like list APIs
|
||||||
|
- Updated the quick start sample to display total likes, today likes, like reset time, and current-cycle like actions
|
||||||
|
- Changed `GetMyVisitsAsync()` default page size to `50` while preserving the optional `limit` parameter
|
||||||
|
- Refreshed package and integration documentation to match the latest production API behavior
|
||||||
|
|
||||||
## 0.3.0
|
## 0.3.0
|
||||||
|
|
||||||
- Renamed the UPM package identifier to `com.foldcc.cc-framework.brisk-game-server`
|
- Renamed the UPM package identifier to `com.foldcc.cc-framework.brisk-game-server`
|
||||||
|
|||||||
@@ -64,6 +64,12 @@ await Brisk.Space.UpdateMyAsync(new
|
|||||||
var mySpace = await Brisk.Space.GetByPlayerIdAsync(Brisk.PlayerId);
|
var mySpace = await Brisk.Space.GetByPlayerIdAsync(Brisk.PlayerId);
|
||||||
var myContent = await Brisk.Space.DownloadContentByPlayerIdAsync(Brisk.PlayerId);
|
var myContent = await Brisk.Space.DownloadContentByPlayerIdAsync(Brisk.PlayerId);
|
||||||
var text = Encoding.UTF8.GetString(myContent.Bytes);
|
var text = Encoding.UTF8.GetString(myContent.Bytes);
|
||||||
|
|
||||||
|
var likeResult = await Brisk.Space.LikeByPlayerIdAsync("target-player-id");
|
||||||
|
Debug.Log($"total={likeResult.LikeCount}, today={likeResult.TodayLikeCount}, created={likeResult.Created}");
|
||||||
|
|
||||||
|
var todayLikes = await Brisk.Space.GetLikesByPlayerIdAsync(Brisk.PlayerId, 20, true);
|
||||||
|
var visits = await Brisk.Space.GetMyVisitsAsync();
|
||||||
```
|
```
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
@@ -72,6 +78,11 @@ Notes:
|
|||||||
- `GetByPlayerIdAsync(...)` returns metadata only
|
- `GetByPlayerIdAsync(...)` returns metadata only
|
||||||
- use `DownloadContentByPlayerIdAsync(...)` to read the actual content bytes
|
- use `DownloadContentByPlayerIdAsync(...)` to read the actual content bytes
|
||||||
- `UpdateMyAsync(...)` automatically picks text / binary / json behavior from the payload type
|
- `UpdateMyAsync(...)` automatically picks text / binary / json behavior from the payload type
|
||||||
|
- space likes are now tracked by natural day in `Asia/Shanghai`
|
||||||
|
- `BriskSpaceView` / `BriskSpaceStats` include both `LikeCount` and `TodayLikeCount`
|
||||||
|
- `BriskSpaceView` also includes `LikedByMe` and `LikeResetAt`
|
||||||
|
- `GetLikesByPlayerIdAsync(..., currentCycleOnly: true)` returns current-cycle likes only
|
||||||
|
- `GetMyVisitsAsync()` now defaults to the latest `50` visits, with `100` as the server-side max
|
||||||
|
|
||||||
## Sample
|
## Sample
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ Brisk Unity SDK 的 UPM 发布目录。
|
|||||||
4. 输入:
|
4. 输入:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
http://private.lightyears.ltd:18650/foldcc/CC-Framework.BriskGameServer.git?path=/PackageSource/com.foldcc.cc-framework.brisk-game-server#v0.3.0
|
http://private.lightyears.ltd:18650/foldcc/CC-Framework.BriskGameServer.git?path=/PackageSource/com.foldcc.cc-framework.brisk-game-server#v0.4.1
|
||||||
```
|
```
|
||||||
|
|
||||||
### 方式二:修改 `Packages/manifest.json`
|
### 方式二:修改 `Packages/manifest.json`
|
||||||
@@ -30,12 +30,12 @@ http://private.lightyears.ltd:18650/foldcc/CC-Framework.BriskGameServer.git?path
|
|||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"com.foldcc.cc-framework.brisk-game-server": "http://private.lightyears.ltd:18650/foldcc/CC-Framework.BriskGameServer.git?path=/PackageSource/com.foldcc.cc-framework.brisk-game-server#v0.3.0"
|
"com.foldcc.cc-framework.brisk-game-server": "http://private.lightyears.ltd:18650/foldcc/CC-Framework.BriskGameServer.git?path=/PackageSource/com.foldcc.cc-framework.brisk-game-server#v0.4.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
如果需要跟随主分支最新代码,可将末尾的 `#v0.3.0` 改成 `#main`;正式环境建议固定到发布 tag。
|
如果需要跟随主分支最新代码,可将末尾的 `#v0.4.1` 改成 `#main`;正式环境建议固定到发布 tag。
|
||||||
|
|
||||||
## 开发态源码位置
|
## 开发态源码位置
|
||||||
|
|
||||||
@@ -87,6 +87,9 @@ http://private.lightyears.ltd:18650/foldcc/CC-Framework.BriskGameServer.git?path
|
|||||||
- `UpdateMyAsync(string)` 直接上传文本
|
- `UpdateMyAsync(string)` 直接上传文本
|
||||||
- `UpdateMyAsync(byte[])` 直接上传二进制
|
- `UpdateMyAsync(byte[])` 直接上传二进制
|
||||||
- `UpdateMyAsync(object)` 自动序列化为 JSON
|
- `UpdateMyAsync(object)` 自动序列化为 JSON
|
||||||
|
- `LikeByPlayerIdAsync(...)` / `UnlikeByPlayerIdAsync(...)` 返回累计点赞数、今日点赞数、当前周期是否创建新点赞、重置时间
|
||||||
|
- `GetLikesByPlayerIdAsync(playerId, limit, true)` 和 `GetLikesByLoginIdentityAsync(..., true)` 可只读取当前周期点赞记录
|
||||||
|
- `GetMyVisitsAsync()` 默认读取最近 `50` 条访问记录,也可手动传入 `limit`
|
||||||
|
|
||||||
## 目录结构
|
## 目录结构
|
||||||
|
|
||||||
|
|||||||
@@ -198,7 +198,16 @@ public static class Brisk
|
|||||||
|
|
||||||
internal static void NotifyAuthExpired(BriskAuthExpiredException exception)
|
internal static void NotifyAuthExpired(BriskAuthExpiredException exception)
|
||||||
{
|
{
|
||||||
s_context?.ErrorPresenter?.ShowAuthExpired(exception);
|
NotifyAuthExpired(exception, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void NotifyAuthExpired(BriskAuthExpiredException exception, bool showDefaultPresenter)
|
||||||
|
{
|
||||||
|
if (showDefaultPresenter)
|
||||||
|
{
|
||||||
|
s_context?.ErrorPresenter?.ShowAuthExpired(exception);
|
||||||
|
}
|
||||||
|
|
||||||
OnAuthExpired?.Invoke(exception);
|
OnAuthExpired?.Invoke(exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,6 +250,15 @@ public static class Brisk
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (storedSession.ExpiresAt.HasValue && storedSession.ExpiresAt.Value <= DateTimeOffset.UtcNow)
|
||||||
|
{
|
||||||
|
await HandleExpiredStartupSessionAsync(
|
||||||
|
context,
|
||||||
|
storedSession,
|
||||||
|
new BriskAuthExpiredException("Stored session expired before initialization."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
context.Session.Update(
|
context.Session.Update(
|
||||||
storedSession.AccessToken,
|
storedSession.AccessToken,
|
||||||
storedSession.ExpiresAt,
|
storedSession.ExpiresAt,
|
||||||
@@ -268,9 +286,7 @@ public static class Brisk
|
|||||||
}
|
}
|
||||||
catch (BriskAuthExpiredException exception)
|
catch (BriskAuthExpiredException exception)
|
||||||
{
|
{
|
||||||
context.Session.Clear();
|
await HandleExpiredStartupSessionAsync(context, storedSession, exception);
|
||||||
await context.TokenStore.ClearAsync();
|
|
||||||
NotifyAuthExpired(exception);
|
|
||||||
}
|
}
|
||||||
catch (BriskBlockingException exception)
|
catch (BriskBlockingException exception)
|
||||||
{
|
{
|
||||||
@@ -278,4 +294,36 @@ public static class Brisk
|
|||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static async Task HandleExpiredStartupSessionAsync(BriskContext context, BriskStoredSession storedSession, BriskAuthExpiredException exception)
|
||||||
|
{
|
||||||
|
context.Session.Clear();
|
||||||
|
await context.TokenStore.ClearAsync();
|
||||||
|
NotifyAuthExpired(exception, false);
|
||||||
|
|
||||||
|
if (!context.Options.AutoReloginOnInitialize || !HasStoredLoginIdentity(storedSession))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await Auth.LoginWithUserIdAsync(storedSession.LoginProvider, storedSession.LoginUserId);
|
||||||
|
}
|
||||||
|
catch (BriskBlockingException)
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// 初始化期静默重登失败时,交给业务层后续第三方登录流程重新调用 Brisk.Auth。
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool HasStoredLoginIdentity(BriskStoredSession storedSession)
|
||||||
|
{
|
||||||
|
return storedSession != null
|
||||||
|
&& !string.IsNullOrWhiteSpace(storedSession.LoginProvider)
|
||||||
|
&& !string.IsNullOrWhiteSpace(storedSession.LoginUserId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,74 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using UnityEngine.UIElements;
|
||||||
|
|
||||||
|
[DefaultExecutionOrder(10000)]
|
||||||
internal sealed class BriskDefaultErrorDialog : MonoBehaviour
|
internal sealed class BriskDefaultErrorDialog : MonoBehaviour
|
||||||
{
|
{
|
||||||
private const string HostName = "BriskDefaultErrorDialog";
|
private const string HostName = "BriskDefaultErrorDialog";
|
||||||
|
private const float OpenDuration = 0.24f;
|
||||||
|
private const float CloseDuration = 0.2f;
|
||||||
|
|
||||||
|
private static readonly Color Ink = ColorFromHex(0x05060a);
|
||||||
|
private static readonly Color InkSoft = ColorFromHex(0x1b1d24);
|
||||||
|
private static readonly Color Muted = ColorFromHex(0x5f6572);
|
||||||
|
private static readonly Color Line = ColorFromHex(0xdde2ea);
|
||||||
|
private static readonly Color Paper = ColorFromHex(0xffffff);
|
||||||
|
private static readonly Color Soft = ColorFromHex(0xf7f8fb);
|
||||||
|
private static readonly Color Blue = ColorFromHex(0x2563ff);
|
||||||
|
private static readonly Color Teal = ColorFromHex(0x23d6c8);
|
||||||
|
private static readonly Color Violet = ColorFromHex(0x8b5cff);
|
||||||
|
private static readonly Color Dark = ColorFromHex(0x050506);
|
||||||
|
|
||||||
private static BriskDefaultErrorDialog s_instance;
|
private static BriskDefaultErrorDialog s_instance;
|
||||||
|
|
||||||
private string _title;
|
private UIDocument _document;
|
||||||
private string _message;
|
private PanelSettings _panelSettings;
|
||||||
private string _confirmText;
|
private VisualElement _root;
|
||||||
|
private VisualElement _stage;
|
||||||
|
private VisualElement _panel;
|
||||||
|
private Label _eyebrowLabel;
|
||||||
|
private Label _titleLabel;
|
||||||
|
private Label _messageLabel;
|
||||||
|
private VisualElement _spacer;
|
||||||
|
private VisualElement _divider;
|
||||||
|
private VisualElement _confirmButton;
|
||||||
|
private Label _confirmLabel;
|
||||||
private Action _onConfirm;
|
private Action _onConfirm;
|
||||||
|
private Coroutine _animation;
|
||||||
private bool _visible;
|
private bool _visible;
|
||||||
|
private bool _closeOnBackdrop;
|
||||||
|
private bool _closeOnEscape;
|
||||||
|
private GUIStyle _legacyTitleStyle;
|
||||||
|
private GUIStyle _legacyMessageStyle;
|
||||||
|
private GUIStyle _legacyEyebrowStyle;
|
||||||
|
private GUIStyle _legacyButtonStyle;
|
||||||
|
private Texture2D _legacyButtonTexture;
|
||||||
|
private Texture2D _legacyButtonHoverTexture;
|
||||||
|
|
||||||
public static void Show(string title, string message, string confirmText, Action onConfirm = null)
|
internal static bool IsVisible => s_instance != null && s_instance._visible;
|
||||||
|
|
||||||
|
internal static void RenderTopmostLegacyOverlay()
|
||||||
|
{
|
||||||
|
if (s_instance == null || !s_instance._visible)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
s_instance.DrawLegacyOverlay();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Show(string title, string message, string confirmText, Action onConfirm = null, bool closeOnBackdrop = false, bool closeOnEscape = false)
|
||||||
{
|
{
|
||||||
var host = EnsureInstance();
|
var host = EnsureInstance();
|
||||||
host._title = string.IsNullOrWhiteSpace(title) ? "Brisk" : title;
|
host.ShowInternal(
|
||||||
host._message = string.IsNullOrWhiteSpace(message) ? "Unknown error." : message;
|
string.IsNullOrWhiteSpace(title) ? "Brisk" : title,
|
||||||
host._confirmText = string.IsNullOrWhiteSpace(confirmText) ? "OK" : confirmText;
|
string.IsNullOrWhiteSpace(message) ? "服务暂时不可用,请稍后再试。" : message,
|
||||||
host._onConfirm = onConfirm;
|
string.IsNullOrWhiteSpace(confirmText) ? "知道了" : confirmText,
|
||||||
host._visible = true;
|
onConfirm,
|
||||||
|
closeOnBackdrop,
|
||||||
|
closeOnEscape);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static BriskDefaultErrorDialog EnsureInstance()
|
private static BriskDefaultErrorDialog EnsureInstance()
|
||||||
@@ -46,6 +94,249 @@ internal sealed class BriskDefaultErrorDialog : MonoBehaviour
|
|||||||
return s_instance;
|
return s_instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ShowInternal(string title, string message, string confirmText, Action onConfirm, bool closeOnBackdrop, bool closeOnEscape)
|
||||||
|
{
|
||||||
|
EnsureDocument();
|
||||||
|
ApplySafeAreaPadding();
|
||||||
|
|
||||||
|
_titleLabel.text = title;
|
||||||
|
_messageLabel.text = message;
|
||||||
|
_confirmLabel.text = confirmText;
|
||||||
|
_onConfirm = onConfirm;
|
||||||
|
_closeOnBackdrop = closeOnBackdrop;
|
||||||
|
_closeOnEscape = closeOnEscape;
|
||||||
|
_visible = true;
|
||||||
|
|
||||||
|
StartDialogAnimation(PlayOpenAnimation());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureDocument()
|
||||||
|
{
|
||||||
|
if (_document != null)
|
||||||
|
{
|
||||||
|
ApplyResponsiveLayout();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_panelSettings = ScriptableObject.CreateInstance<PanelSettings>();
|
||||||
|
_panelSettings.name = HostName + "PanelSettings";
|
||||||
|
_panelSettings.scaleMode = PanelScaleMode.ConstantPixelSize;
|
||||||
|
_panelSettings.scale = 1f;
|
||||||
|
_panelSettings.sortingOrder = 32767f;
|
||||||
|
|
||||||
|
_document = gameObject.AddComponent<UIDocument>();
|
||||||
|
_document.panelSettings = _panelSettings;
|
||||||
|
_document.sortingOrder = 32767f;
|
||||||
|
|
||||||
|
BuildVisualTree(_document.rootVisualElement);
|
||||||
|
ApplyResponsiveLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BuildVisualTree(VisualElement documentRoot)
|
||||||
|
{
|
||||||
|
documentRoot.Clear();
|
||||||
|
documentRoot.pickingMode = PickingMode.Ignore;
|
||||||
|
documentRoot.style.position = Position.Absolute;
|
||||||
|
documentRoot.style.left = 0f;
|
||||||
|
documentRoot.style.top = 0f;
|
||||||
|
documentRoot.style.right = 0f;
|
||||||
|
documentRoot.style.bottom = 0f;
|
||||||
|
documentRoot.style.width = Length.Percent(100f);
|
||||||
|
documentRoot.style.height = Length.Percent(100f);
|
||||||
|
documentRoot.style.flexGrow = 1f;
|
||||||
|
|
||||||
|
_root = new VisualElement
|
||||||
|
{
|
||||||
|
pickingMode = PickingMode.Position,
|
||||||
|
focusable = true
|
||||||
|
};
|
||||||
|
_root.style.position = Position.Absolute;
|
||||||
|
_root.style.left = 0;
|
||||||
|
_root.style.top = 0;
|
||||||
|
_root.style.right = 0;
|
||||||
|
_root.style.bottom = 0;
|
||||||
|
_root.style.display = DisplayStyle.None;
|
||||||
|
_root.style.opacity = 0f;
|
||||||
|
_root.style.backgroundColor = new Color(0.02f, 0.02f, 0.024f, 0.58f);
|
||||||
|
_root.style.justifyContent = Justify.Center;
|
||||||
|
_root.style.alignItems = Align.Center;
|
||||||
|
_root.RegisterCallback<KeyDownEvent>(HandleRootKeyDown);
|
||||||
|
_root.RegisterCallback<PointerDownEvent>(evt =>
|
||||||
|
{
|
||||||
|
if (_closeOnBackdrop && evt.target == _root)
|
||||||
|
{
|
||||||
|
BeginClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
evt.StopPropagation();
|
||||||
|
});
|
||||||
|
documentRoot.Add(_root);
|
||||||
|
|
||||||
|
_stage = new VisualElement();
|
||||||
|
_stage.pickingMode = PickingMode.Ignore;
|
||||||
|
_stage.style.position = Position.Relative;
|
||||||
|
_stage.style.width = Length.Percent(82f);
|
||||||
|
_stage.style.maxWidth = 900f;
|
||||||
|
_stage.style.opacity = 0f;
|
||||||
|
_stage.style.marginTop = 14f;
|
||||||
|
_root.Add(_stage);
|
||||||
|
|
||||||
|
var shadow = new VisualElement();
|
||||||
|
shadow.pickingMode = PickingMode.Ignore;
|
||||||
|
shadow.style.position = Position.Absolute;
|
||||||
|
shadow.style.left = 0f;
|
||||||
|
shadow.style.right = 0f;
|
||||||
|
shadow.style.top = 10f;
|
||||||
|
shadow.style.bottom = -10f;
|
||||||
|
shadow.style.backgroundColor = new Color(0f, 0f, 0f, 0.09f);
|
||||||
|
shadow.style.borderTopLeftRadius = 18f;
|
||||||
|
shadow.style.borderTopRightRadius = 18f;
|
||||||
|
shadow.style.borderBottomLeftRadius = 18f;
|
||||||
|
shadow.style.borderBottomRightRadius = 18f;
|
||||||
|
_stage.Add(shadow);
|
||||||
|
|
||||||
|
_panel = new VisualElement
|
||||||
|
{
|
||||||
|
pickingMode = PickingMode.Position
|
||||||
|
};
|
||||||
|
_panel.style.width = Length.Percent(100f);
|
||||||
|
_panel.style.minHeight = 360f;
|
||||||
|
_panel.style.paddingTop = 36f;
|
||||||
|
_panel.style.paddingRight = 36f;
|
||||||
|
_panel.style.paddingBottom = 36f;
|
||||||
|
_panel.style.paddingLeft = 36f;
|
||||||
|
_panel.style.backgroundColor = Paper;
|
||||||
|
_panel.style.borderTopWidth = 1f;
|
||||||
|
_panel.style.borderRightWidth = 1f;
|
||||||
|
_panel.style.borderBottomWidth = 1f;
|
||||||
|
_panel.style.borderLeftWidth = 1f;
|
||||||
|
_panel.style.borderTopColor = Line;
|
||||||
|
_panel.style.borderRightColor = Line;
|
||||||
|
_panel.style.borderBottomColor = Line;
|
||||||
|
_panel.style.borderLeftColor = Line;
|
||||||
|
_panel.style.borderTopLeftRadius = 18f;
|
||||||
|
_panel.style.borderTopRightRadius = 18f;
|
||||||
|
_panel.style.borderBottomLeftRadius = 18f;
|
||||||
|
_panel.style.borderBottomRightRadius = 18f;
|
||||||
|
_panel.style.overflow = Overflow.Hidden;
|
||||||
|
_stage.Add(_panel);
|
||||||
|
|
||||||
|
var accent = new VisualElement();
|
||||||
|
accent.pickingMode = PickingMode.Ignore;
|
||||||
|
accent.style.position = Position.Absolute;
|
||||||
|
accent.style.top = 0f;
|
||||||
|
accent.style.left = 0f;
|
||||||
|
accent.style.right = 0f;
|
||||||
|
accent.style.height = 2f;
|
||||||
|
accent.style.flexDirection = FlexDirection.Row;
|
||||||
|
_panel.Add(accent);
|
||||||
|
AddAccentSegment(accent, Blue);
|
||||||
|
AddAccentSegment(accent, Teal);
|
||||||
|
AddAccentSegment(accent, Violet);
|
||||||
|
|
||||||
|
_eyebrowLabel = new Label("BRISK SERVICE");
|
||||||
|
_eyebrowLabel.style.color = Muted;
|
||||||
|
_eyebrowLabel.style.fontSize = 13f;
|
||||||
|
_eyebrowLabel.style.unityFontStyleAndWeight = FontStyle.Bold;
|
||||||
|
_eyebrowLabel.style.letterSpacing = 1.4f;
|
||||||
|
_eyebrowLabel.style.height = 18f;
|
||||||
|
_eyebrowLabel.style.flexShrink = 0f;
|
||||||
|
_eyebrowLabel.style.marginBottom = 12f;
|
||||||
|
_panel.Add(_eyebrowLabel);
|
||||||
|
|
||||||
|
_titleLabel = new Label();
|
||||||
|
_titleLabel.style.color = Ink;
|
||||||
|
_titleLabel.style.fontSize = 30f;
|
||||||
|
_titleLabel.style.unityFontStyleAndWeight = FontStyle.Bold;
|
||||||
|
_titleLabel.style.whiteSpace = WhiteSpace.Normal;
|
||||||
|
_titleLabel.style.unityTextAlign = TextAnchor.MiddleLeft;
|
||||||
|
_titleLabel.style.minHeight = 40f;
|
||||||
|
_titleLabel.style.flexShrink = 0f;
|
||||||
|
_titleLabel.style.marginBottom = 16f;
|
||||||
|
_panel.Add(_titleLabel);
|
||||||
|
|
||||||
|
_messageLabel = new Label();
|
||||||
|
_messageLabel.style.color = Muted;
|
||||||
|
_messageLabel.style.fontSize = 21f;
|
||||||
|
_messageLabel.style.whiteSpace = WhiteSpace.Normal;
|
||||||
|
_messageLabel.style.unityTextAlign = TextAnchor.UpperLeft;
|
||||||
|
_messageLabel.style.minHeight = 64f;
|
||||||
|
_messageLabel.style.flexShrink = 0f;
|
||||||
|
_messageLabel.style.marginBottom = 22f;
|
||||||
|
_panel.Add(_messageLabel);
|
||||||
|
|
||||||
|
_spacer = new VisualElement();
|
||||||
|
_spacer.pickingMode = PickingMode.Ignore;
|
||||||
|
_spacer.style.flexGrow = 1f;
|
||||||
|
_spacer.style.minHeight = 18f;
|
||||||
|
_panel.Add(_spacer);
|
||||||
|
|
||||||
|
_divider = new VisualElement();
|
||||||
|
_divider.style.height = 1f;
|
||||||
|
_divider.style.flexShrink = 0f;
|
||||||
|
_divider.style.backgroundColor = Line;
|
||||||
|
_divider.style.marginBottom = 22f;
|
||||||
|
_panel.Add(_divider);
|
||||||
|
|
||||||
|
_confirmButton = new VisualElement
|
||||||
|
{
|
||||||
|
focusable = true,
|
||||||
|
pickingMode = PickingMode.Position
|
||||||
|
};
|
||||||
|
_confirmButton.style.height = 58f;
|
||||||
|
_confirmButton.style.minHeight = 58f;
|
||||||
|
_confirmButton.style.flexShrink = 0f;
|
||||||
|
_confirmButton.style.borderTopLeftRadius = 24f;
|
||||||
|
_confirmButton.style.borderTopRightRadius = 24f;
|
||||||
|
_confirmButton.style.borderBottomLeftRadius = 24f;
|
||||||
|
_confirmButton.style.borderBottomRightRadius = 24f;
|
||||||
|
_confirmButton.style.backgroundColor = Dark;
|
||||||
|
_confirmButton.style.justifyContent = Justify.Center;
|
||||||
|
_confirmButton.style.alignItems = Align.Center;
|
||||||
|
_confirmButton.RegisterCallback<PointerEnterEvent>(_ => _confirmButton.style.backgroundColor = InkSoft);
|
||||||
|
_confirmButton.RegisterCallback<PointerLeaveEvent>(_ => _confirmButton.style.backgroundColor = Dark);
|
||||||
|
_confirmButton.RegisterCallback<PointerDownEvent>(_ =>
|
||||||
|
{
|
||||||
|
_confirmButton.style.backgroundColor = Ink;
|
||||||
|
_confirmButton.style.marginTop = 1f;
|
||||||
|
});
|
||||||
|
_confirmButton.RegisterCallback<PointerUpEvent>(_ =>
|
||||||
|
{
|
||||||
|
_confirmButton.style.backgroundColor = InkSoft;
|
||||||
|
_confirmButton.style.marginTop = 0f;
|
||||||
|
});
|
||||||
|
_confirmButton.RegisterCallback<ClickEvent>(_ => BeginClose());
|
||||||
|
_confirmButton.RegisterCallback<KeyDownEvent>(HandleButtonKeyDown);
|
||||||
|
_panel.Add(_confirmButton);
|
||||||
|
|
||||||
|
_confirmLabel = new Label();
|
||||||
|
_confirmLabel.style.color = Color.white;
|
||||||
|
_confirmLabel.style.fontSize = 18f;
|
||||||
|
_confirmLabel.style.unityFontStyleAndWeight = FontStyle.Bold;
|
||||||
|
_confirmLabel.style.unityTextAlign = TextAnchor.MiddleCenter;
|
||||||
|
_confirmLabel.style.height = 58f;
|
||||||
|
_confirmButton.Add(_confirmLabel);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
if (_visible && _root != null)
|
||||||
|
{
|
||||||
|
ApplySafeAreaPadding();
|
||||||
|
ApplyResponsiveLayout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BeginClose()
|
||||||
|
{
|
||||||
|
if (!_visible || _root == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
StartDialogAnimation(PlayCloseAnimation());
|
||||||
|
}
|
||||||
|
|
||||||
private void OnGUI()
|
private void OnGUI()
|
||||||
{
|
{
|
||||||
if (!_visible)
|
if (!_visible)
|
||||||
@@ -53,36 +344,379 @@ internal sealed class BriskDefaultErrorDialog : MonoBehaviour
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var overlayRect = new Rect(0, 0, Screen.width, Screen.height);
|
DrawLegacyOverlay();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawLegacyOverlay()
|
||||||
|
{
|
||||||
|
EnsureLegacyStyles();
|
||||||
|
|
||||||
|
var previousDepth = GUI.depth;
|
||||||
|
var previousMatrix = GUI.matrix;
|
||||||
var previousColor = GUI.color;
|
var previousColor = GUI.color;
|
||||||
GUI.color = new Color(0f, 0f, 0f, 0.65f);
|
GUI.depth = -10000;
|
||||||
GUI.Box(overlayRect, GUIContent.none);
|
GUI.matrix = Matrix4x4.identity;
|
||||||
GUI.color = previousColor;
|
|
||||||
|
|
||||||
var width = Mathf.Min(460f, Screen.width - 40f);
|
DrawLegacyRect(new Rect(0f, 0f, Screen.width, Screen.height), new Color(0.02f, 0.02f, 0.024f, 0.58f));
|
||||||
var height = 220f;
|
|
||||||
var dialogRect = new Rect(
|
|
||||||
(Screen.width - width) * 0.5f,
|
|
||||||
(Screen.height - height) * 0.5f,
|
|
||||||
width,
|
|
||||||
height);
|
|
||||||
|
|
||||||
GUILayout.BeginArea(dialogRect, GUI.skin.window);
|
var scale = GetReadableScale();
|
||||||
GUILayout.Space(8f);
|
ApplyLegacyScale(scale);
|
||||||
GUILayout.Label(_title, GUI.skin.label);
|
|
||||||
GUILayout.Space(12f);
|
|
||||||
GUILayout.Label(_message, GUI.skin.label);
|
|
||||||
GUILayout.FlexibleSpace();
|
|
||||||
|
|
||||||
if (GUILayout.Button(_confirmText, GUILayout.Height(36f)))
|
var margin = GetDialogMargin(scale);
|
||||||
|
var padding = GetDialogPadding(scale);
|
||||||
|
var buttonHeight = GetDialogButtonHeight(scale);
|
||||||
|
var width = GetDialogWidth(margin);
|
||||||
|
var contentWidth = width - padding * 2f;
|
||||||
|
var titleHeight = Mathf.Max(34f, _legacyTitleStyle.CalcHeight(new GUIContent(_titleLabel.text), contentWidth));
|
||||||
|
var messageHeight = Mathf.Max(48f, _legacyMessageStyle.CalcHeight(new GUIContent(_messageLabel.text), contentWidth));
|
||||||
|
var minHeight = GetDialogMinHeight(scale, margin);
|
||||||
|
var contentHeight = padding * 2f + 18f * scale + 12f * scale + titleHeight + 16f * scale + messageHeight + 24f * scale + 1f + 22f * scale + buttonHeight;
|
||||||
|
var height = Mathf.Clamp(contentHeight, minHeight, Mathf.Max(minHeight, Screen.height - margin * 2f));
|
||||||
|
var rect = new Rect((Screen.width - width) * 0.5f, (Screen.height - height) * 0.5f, width, height);
|
||||||
|
|
||||||
|
DrawLegacyPanel(rect);
|
||||||
|
|
||||||
|
var x = rect.x + padding;
|
||||||
|
var y = rect.y + padding;
|
||||||
|
GUI.Label(new Rect(x, y, contentWidth, 18f * scale), "BRISK SERVICE", _legacyEyebrowStyle);
|
||||||
|
y += 30f * scale;
|
||||||
|
GUI.Label(new Rect(x, y, contentWidth, titleHeight), _titleLabel.text, _legacyTitleStyle);
|
||||||
|
y += titleHeight + 16f * scale;
|
||||||
|
GUI.Label(new Rect(x, y, contentWidth, messageHeight), _messageLabel.text, _legacyMessageStyle);
|
||||||
|
|
||||||
|
DrawLegacyRect(new Rect(x, rect.yMax - padding - buttonHeight - 22f * scale, contentWidth, 1f), Line);
|
||||||
|
var buttonRect = new Rect(x, rect.yMax - padding - buttonHeight, contentWidth, buttonHeight);
|
||||||
|
if (GUI.Button(buttonRect, _confirmLabel.text, _legacyButtonStyle))
|
||||||
{
|
{
|
||||||
var callback = _onConfirm;
|
BeginClose();
|
||||||
_visible = false;
|
|
||||||
_onConfirm = null;
|
|
||||||
callback?.Invoke();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GUILayout.Space(8f);
|
ConsumeBlockingEvent();
|
||||||
GUILayout.EndArea();
|
|
||||||
|
GUI.color = previousColor;
|
||||||
|
GUI.matrix = previousMatrix;
|
||||||
|
GUI.depth = previousDepth;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerator PlayOpenAnimation()
|
||||||
|
{
|
||||||
|
_root.style.display = DisplayStyle.Flex;
|
||||||
|
_root.style.opacity = 0f;
|
||||||
|
_stage.style.opacity = 0f;
|
||||||
|
_stage.style.marginTop = 14f;
|
||||||
|
|
||||||
|
yield return null;
|
||||||
|
_root.Focus();
|
||||||
|
|
||||||
|
var elapsed = 0f;
|
||||||
|
while (elapsed < OpenDuration)
|
||||||
|
{
|
||||||
|
elapsed += Time.unscaledDeltaTime;
|
||||||
|
var eased = EaseOutCubic(Mathf.Clamp01(elapsed / OpenDuration));
|
||||||
|
_root.style.opacity = eased;
|
||||||
|
_stage.style.opacity = eased;
|
||||||
|
_stage.style.marginTop = Mathf.Lerp(14f, 0f, eased);
|
||||||
|
yield return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_root.style.opacity = 1f;
|
||||||
|
_stage.style.opacity = 1f;
|
||||||
|
_stage.style.marginTop = 0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerator PlayCloseAnimation()
|
||||||
|
{
|
||||||
|
var callback = _onConfirm;
|
||||||
|
_onConfirm = null;
|
||||||
|
|
||||||
|
var elapsed = 0f;
|
||||||
|
while (elapsed < CloseDuration)
|
||||||
|
{
|
||||||
|
elapsed += Time.unscaledDeltaTime;
|
||||||
|
var eased = EaseInCubic(Mathf.Clamp01(elapsed / CloseDuration));
|
||||||
|
var alpha = 1f - eased;
|
||||||
|
_root.style.opacity = alpha;
|
||||||
|
_stage.style.opacity = alpha;
|
||||||
|
_stage.style.marginTop = Mathf.Lerp(0f, 10f, eased);
|
||||||
|
yield return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_root.style.display = DisplayStyle.None;
|
||||||
|
_visible = false;
|
||||||
|
callback?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StartDialogAnimation(IEnumerator animation)
|
||||||
|
{
|
||||||
|
if (_animation != null)
|
||||||
|
{
|
||||||
|
StopCoroutine(_animation);
|
||||||
|
}
|
||||||
|
|
||||||
|
_animation = StartCoroutine(animation);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplySafeAreaPadding()
|
||||||
|
{
|
||||||
|
var safeArea = Screen.safeArea;
|
||||||
|
var basePadding = Mathf.Max(20f, Mathf.Min(Screen.width, Screen.height) * 0.04f);
|
||||||
|
_root.style.paddingLeft = Mathf.Max(basePadding, safeArea.xMin + basePadding);
|
||||||
|
_root.style.paddingRight = Mathf.Max(basePadding, Screen.width - safeArea.xMax + basePadding);
|
||||||
|
_root.style.paddingTop = Mathf.Max(basePadding, Screen.height - safeArea.yMax + basePadding);
|
||||||
|
_root.style.paddingBottom = Mathf.Max(basePadding, safeArea.yMin + basePadding);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyResponsiveLayout()
|
||||||
|
{
|
||||||
|
if (_panel == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var scale = GetReadableScale();
|
||||||
|
var margin = GetDialogMargin(scale);
|
||||||
|
var maxWidth = Mathf.Min(900f, Mathf.Max(320f, Screen.width - margin * 2f));
|
||||||
|
var minHeight = GetDialogMinHeight(scale, margin);
|
||||||
|
var padding = GetDialogPadding(scale);
|
||||||
|
var buttonHeight = GetDialogButtonHeight(scale);
|
||||||
|
|
||||||
|
_stage.style.width = Length.Percent(82f);
|
||||||
|
_stage.style.maxWidth = maxWidth;
|
||||||
|
_panel.style.minHeight = minHeight;
|
||||||
|
_panel.style.paddingTop = padding;
|
||||||
|
_panel.style.paddingRight = padding;
|
||||||
|
_panel.style.paddingBottom = padding;
|
||||||
|
_panel.style.paddingLeft = padding;
|
||||||
|
_panel.style.borderTopLeftRadius = 20f * scale;
|
||||||
|
_panel.style.borderTopRightRadius = 20f * scale;
|
||||||
|
_panel.style.borderBottomLeftRadius = 20f * scale;
|
||||||
|
_panel.style.borderBottomRightRadius = 20f * scale;
|
||||||
|
|
||||||
|
_eyebrowLabel.style.fontSize = 12f * scale;
|
||||||
|
_eyebrowLabel.style.height = 18f * scale;
|
||||||
|
_eyebrowLabel.style.marginBottom = 12f * scale;
|
||||||
|
_titleLabel.style.fontSize = 28f * scale;
|
||||||
|
_titleLabel.style.minHeight = 40f * scale;
|
||||||
|
_titleLabel.style.marginBottom = 16f * scale;
|
||||||
|
_messageLabel.style.fontSize = 20f * scale;
|
||||||
|
_messageLabel.style.minHeight = 64f * scale;
|
||||||
|
_messageLabel.style.marginBottom = 22f * scale;
|
||||||
|
_spacer.style.minHeight = 18f * scale;
|
||||||
|
_divider.style.marginBottom = 22f * scale;
|
||||||
|
_confirmButton.style.height = buttonHeight;
|
||||||
|
_confirmButton.style.minHeight = buttonHeight;
|
||||||
|
_confirmButton.style.borderTopLeftRadius = buttonHeight * 0.5f;
|
||||||
|
_confirmButton.style.borderTopRightRadius = buttonHeight * 0.5f;
|
||||||
|
_confirmButton.style.borderBottomLeftRadius = buttonHeight * 0.5f;
|
||||||
|
_confirmButton.style.borderBottomRightRadius = buttonHeight * 0.5f;
|
||||||
|
_confirmLabel.style.fontSize = 17f * scale;
|
||||||
|
_confirmLabel.style.height = buttonHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleRootKeyDown(KeyDownEvent evt)
|
||||||
|
{
|
||||||
|
if (_closeOnEscape && evt.keyCode == KeyCode.Escape)
|
||||||
|
{
|
||||||
|
BeginClose();
|
||||||
|
evt.StopPropagation();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
evt.StopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleButtonKeyDown(KeyDownEvent evt)
|
||||||
|
{
|
||||||
|
if (evt.keyCode == KeyCode.Return || evt.keyCode == KeyCode.KeypadEnter || evt.keyCode == KeyCode.Space)
|
||||||
|
{
|
||||||
|
BeginClose();
|
||||||
|
evt.StopPropagation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDestroy()
|
||||||
|
{
|
||||||
|
if (s_instance == this)
|
||||||
|
{
|
||||||
|
s_instance = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_panelSettings != null)
|
||||||
|
{
|
||||||
|
Destroy(_panelSettings);
|
||||||
|
_panelSettings = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureLegacyStyles()
|
||||||
|
{
|
||||||
|
if (_legacyTitleStyle != null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_legacyButtonTexture = CreateTexture(Dark);
|
||||||
|
_legacyButtonHoverTexture = CreateTexture(InkSoft);
|
||||||
|
|
||||||
|
_legacyEyebrowStyle = new GUIStyle
|
||||||
|
{
|
||||||
|
fontStyle = FontStyle.Bold,
|
||||||
|
normal = { textColor = Muted }
|
||||||
|
};
|
||||||
|
|
||||||
|
_legacyTitleStyle = new GUIStyle
|
||||||
|
{
|
||||||
|
fontStyle = FontStyle.Bold,
|
||||||
|
wordWrap = true,
|
||||||
|
normal = { textColor = Ink }
|
||||||
|
};
|
||||||
|
|
||||||
|
_legacyMessageStyle = new GUIStyle
|
||||||
|
{
|
||||||
|
wordWrap = true,
|
||||||
|
normal = { textColor = Muted }
|
||||||
|
};
|
||||||
|
|
||||||
|
_legacyButtonStyle = new GUIStyle
|
||||||
|
{
|
||||||
|
alignment = TextAnchor.MiddleCenter,
|
||||||
|
fontStyle = FontStyle.Bold,
|
||||||
|
border = new RectOffset(24, 24, 24, 24),
|
||||||
|
normal =
|
||||||
|
{
|
||||||
|
background = _legacyButtonTexture,
|
||||||
|
textColor = Color.white
|
||||||
|
},
|
||||||
|
hover =
|
||||||
|
{
|
||||||
|
background = _legacyButtonHoverTexture,
|
||||||
|
textColor = Color.white
|
||||||
|
},
|
||||||
|
active =
|
||||||
|
{
|
||||||
|
background = _legacyButtonHoverTexture,
|
||||||
|
textColor = Color.white
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyLegacyScale(float scale)
|
||||||
|
{
|
||||||
|
_legacyEyebrowStyle.fontSize = Mathf.RoundToInt(12f * scale);
|
||||||
|
_legacyTitleStyle.fontSize = Mathf.RoundToInt(28f * scale);
|
||||||
|
_legacyMessageStyle.fontSize = Mathf.RoundToInt(20f * scale);
|
||||||
|
_legacyButtonStyle.fontSize = Mathf.RoundToInt(17f * scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawLegacyPanel(Rect rect)
|
||||||
|
{
|
||||||
|
DrawLegacyRect(new Rect(rect.x, rect.y + 12f, rect.width, rect.height), new Color(0f, 0f, 0f, 0.08f));
|
||||||
|
DrawLegacyRect(rect, Paper);
|
||||||
|
DrawLegacyRect(new Rect(rect.x, rect.y, rect.width, 1f), Line);
|
||||||
|
DrawLegacyRect(new Rect(rect.x, rect.yMax - 1f, rect.width, 1f), Line);
|
||||||
|
DrawLegacyRect(new Rect(rect.x, rect.y, 1f, rect.height), Line);
|
||||||
|
DrawLegacyRect(new Rect(rect.xMax - 1f, rect.y, 1f, rect.height), Line);
|
||||||
|
|
||||||
|
var segment = rect.width / 3f;
|
||||||
|
DrawLegacyRect(new Rect(rect.x, rect.y, segment, 2f), Blue);
|
||||||
|
DrawLegacyRect(new Rect(rect.x + segment, rect.y, segment, 2f), Teal);
|
||||||
|
DrawLegacyRect(new Rect(rect.x + segment * 2f, rect.y, rect.width - segment * 2f, 2f), Violet);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ConsumeBlockingEvent()
|
||||||
|
{
|
||||||
|
var evt = Event.current;
|
||||||
|
if (evt == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (evt.type)
|
||||||
|
{
|
||||||
|
case EventType.MouseDown:
|
||||||
|
case EventType.MouseUp:
|
||||||
|
case EventType.MouseDrag:
|
||||||
|
case EventType.ScrollWheel:
|
||||||
|
case EventType.KeyDown:
|
||||||
|
case EventType.KeyUp:
|
||||||
|
evt.Use();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Texture2D CreateTexture(Color color)
|
||||||
|
{
|
||||||
|
var texture = new Texture2D(1, 1, TextureFormat.RGBA32, false)
|
||||||
|
{
|
||||||
|
hideFlags = HideFlags.HideAndDontSave
|
||||||
|
};
|
||||||
|
texture.SetPixel(0, 0, color);
|
||||||
|
texture.Apply();
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DrawLegacyRect(Rect rect, Color color)
|
||||||
|
{
|
||||||
|
var previousColor = GUI.color;
|
||||||
|
GUI.color = color;
|
||||||
|
GUI.DrawTexture(rect, Texture2D.whiteTexture);
|
||||||
|
GUI.color = previousColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddAccentSegment(VisualElement parent, Color color)
|
||||||
|
{
|
||||||
|
var segment = new VisualElement();
|
||||||
|
segment.style.flexGrow = 1f;
|
||||||
|
segment.style.backgroundColor = color;
|
||||||
|
parent.Add(segment);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float GetReadableScale()
|
||||||
|
{
|
||||||
|
return Mathf.Clamp(Mathf.Min(Screen.width, Screen.height) / 720f, 1f, 1.22f);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float GetDialogMargin(float scale)
|
||||||
|
{
|
||||||
|
return Mathf.Max(24f * scale, Mathf.Min(Screen.width, Screen.height) * 0.06f);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float GetDialogPadding(float scale)
|
||||||
|
{
|
||||||
|
return 34f * scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float GetDialogButtonHeight(float scale)
|
||||||
|
{
|
||||||
|
return 56f * scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float GetDialogWidth(float margin)
|
||||||
|
{
|
||||||
|
return Mathf.Clamp(Screen.width * 0.82f, 320f, Mathf.Max(320f, Mathf.Min(900f, Screen.width - margin * 2f)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float GetDialogMinHeight(float scale, float margin)
|
||||||
|
{
|
||||||
|
var availableHeight = Mathf.Max(320f, Screen.height - margin * 2f);
|
||||||
|
return Mathf.Min(availableHeight, Mathf.Clamp(Screen.height * 0.24f, 360f * scale, 620f));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float EaseOutCubic(float t)
|
||||||
|
{
|
||||||
|
return 1f - Mathf.Pow(1f - t, 3f);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float EaseInCubic(float t)
|
||||||
|
{
|
||||||
|
return t * t * t;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Color ColorFromHex(int rgb)
|
||||||
|
{
|
||||||
|
var r = ((rgb >> 16) & 0xff) / 255f;
|
||||||
|
var g = ((rgb >> 8) & 0xff) / 255f;
|
||||||
|
var b = (rgb & 0xff) / 255f;
|
||||||
|
return new Color(r, g, b, 1f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,18 +13,22 @@ public sealed class BriskDefaultErrorPresenter : IBriskErrorPresenter
|
|||||||
|
|
||||||
public void ShowBlockingError(BriskBlockingException exception)
|
public void ShowBlockingError(BriskBlockingException exception)
|
||||||
{
|
{
|
||||||
var message = exception != null && !string.IsNullOrWhiteSpace(exception.Message)
|
var message = GetBlockingMessage(exception);
|
||||||
? exception.Message
|
|
||||||
: "A blocking error occurred.";
|
|
||||||
|
|
||||||
BriskDefaultErrorDialog.Show(
|
BriskDefaultErrorDialog.Show(
|
||||||
title: GetBlockingTitle(exception),
|
title: GetBlockingTitle(exception),
|
||||||
message: message,
|
message: message,
|
||||||
confirmText: "OK",
|
confirmText: "知道了",
|
||||||
onConfirm: () =>
|
onConfirm: () =>
|
||||||
{
|
{
|
||||||
var exitHandler = Brisk.IsInitialized ? Brisk.Options?.ExitHandler : null;
|
var exitHandler = Brisk.IsInitialized ? Brisk.GetRequiredContext().ExitHandler : null;
|
||||||
exitHandler?.Invoke();
|
if (exitHandler != null)
|
||||||
|
{
|
||||||
|
exitHandler.Invoke();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Application.Quit();
|
||||||
});
|
});
|
||||||
|
|
||||||
Debug.LogError("[Brisk] Blocking error: " + message);
|
Debug.LogError("[Brisk] Blocking error: " + message);
|
||||||
@@ -32,35 +36,63 @@ public sealed class BriskDefaultErrorPresenter : IBriskErrorPresenter
|
|||||||
|
|
||||||
public void ShowAuthExpired(BriskAuthExpiredException exception)
|
public void ShowAuthExpired(BriskAuthExpiredException exception)
|
||||||
{
|
{
|
||||||
var message = exception != null && !string.IsNullOrWhiteSpace(exception.Message)
|
var message = GetAuthExpiredMessage(exception);
|
||||||
? exception.Message
|
|
||||||
: "Login expired. Please sign in again.";
|
|
||||||
|
|
||||||
BriskDefaultErrorDialog.Show(
|
BriskDefaultErrorDialog.Show(
|
||||||
title: "Login Expired",
|
title: "登录已过期",
|
||||||
message: message,
|
message: message,
|
||||||
confirmText: "OK");
|
confirmText: "知道了");
|
||||||
|
|
||||||
Debug.LogWarning("[Brisk] Auth expired: " + message);
|
Debug.LogWarning("[Brisk] Auth expired: " + (exception != null ? exception.Message : message));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetBlockingTitle(BriskBlockingException exception)
|
private static string GetBlockingTitle(BriskBlockingException exception)
|
||||||
{
|
{
|
||||||
if (exception is BriskMaintenanceException)
|
if (exception is BriskMaintenanceException)
|
||||||
{
|
{
|
||||||
return "Maintenance";
|
return "服务维护中";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (exception is BriskAccountBannedException)
|
if (exception is BriskAccountBannedException)
|
||||||
{
|
{
|
||||||
return "Account Restricted";
|
return "账号受限";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (exception is BriskClientUpdateRequiredException)
|
if (exception is BriskClientUpdateRequiredException)
|
||||||
{
|
{
|
||||||
return "Update Required";
|
return "需要更新";
|
||||||
}
|
}
|
||||||
|
|
||||||
return "Service Unavailable";
|
return "服务暂不可用";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetBlockingMessage(BriskBlockingException exception)
|
||||||
|
{
|
||||||
|
if (exception is BriskMaintenanceException)
|
||||||
|
{
|
||||||
|
return "服务正在维护,请稍后再试。";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exception is BriskAccountBannedException)
|
||||||
|
{
|
||||||
|
return "当前账号暂时无法使用该服务。";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exception is BriskClientUpdateRequiredException)
|
||||||
|
{
|
||||||
|
return "当前版本过低,请更新后继续。";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "服务暂时不可用,请稍后再试。";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetAuthExpiredMessage(BriskAuthExpiredException exception)
|
||||||
|
{
|
||||||
|
if (exception != null && exception.Message != null && exception.Message.IndexOf("missing", StringComparison.OrdinalIgnoreCase) >= 0)
|
||||||
|
{
|
||||||
|
return "当前登录状态不可用,请重新登录。";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "登录状态已失效,请重新登录后继续。";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -249,8 +249,10 @@ internal static class BriskModelMapper
|
|||||||
ContentSizeBytes = BriskValueReader.GetLong(data, "content_size_bytes"),
|
ContentSizeBytes = BriskValueReader.GetLong(data, "content_size_bytes"),
|
||||||
ContentChecksum = BriskValueReader.GetString(data, "content_checksum"),
|
ContentChecksum = BriskValueReader.GetString(data, "content_checksum"),
|
||||||
LikeCount = BriskValueReader.GetLong(data, "like_count"),
|
LikeCount = BriskValueReader.GetLong(data, "like_count"),
|
||||||
|
TodayLikeCount = BriskValueReader.GetLong(data, "today_like_count"),
|
||||||
VisitCount = BriskValueReader.GetLong(data, "visit_count"),
|
VisitCount = BriskValueReader.GetLong(data, "visit_count"),
|
||||||
LikedByMe = BriskValueReader.GetBool(data, "liked_by_me"),
|
LikedByMe = BriskValueReader.GetBool(data, "liked_by_me"),
|
||||||
|
LikeResetAt = BriskValueReader.GetString(data, "like_reset_at"),
|
||||||
UpdatedAt = BriskValueReader.GetString(data, "updated_at")
|
UpdatedAt = BriskValueReader.GetString(data, "updated_at")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -276,7 +278,9 @@ internal static class BriskModelMapper
|
|||||||
ContentSizeBytes = BriskValueReader.GetLong(data, "content_size_bytes"),
|
ContentSizeBytes = BriskValueReader.GetLong(data, "content_size_bytes"),
|
||||||
ContentChecksum = BriskValueReader.GetString(data, "content_checksum"),
|
ContentChecksum = BriskValueReader.GetString(data, "content_checksum"),
|
||||||
LikeCount = BriskValueReader.GetLong(data, "like_count"),
|
LikeCount = BriskValueReader.GetLong(data, "like_count"),
|
||||||
|
TodayLikeCount = BriskValueReader.GetLong(data, "today_like_count"),
|
||||||
VisitCount = BriskValueReader.GetLong(data, "visit_count"),
|
VisitCount = BriskValueReader.GetLong(data, "visit_count"),
|
||||||
|
LikeResetAt = BriskValueReader.GetString(data, "like_reset_at"),
|
||||||
UpdatedAt = BriskValueReader.GetString(data, "updated_at")
|
UpdatedAt = BriskValueReader.GetString(data, "updated_at")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -309,7 +313,11 @@ internal static class BriskModelMapper
|
|||||||
return new BriskSpaceLikeResult
|
return new BriskSpaceLikeResult
|
||||||
{
|
{
|
||||||
Liked = BriskValueReader.GetBool(data, "liked"),
|
Liked = BriskValueReader.GetBool(data, "liked"),
|
||||||
LikeCount = BriskValueReader.GetLong(data, "like_count")
|
Created = BriskValueReader.GetBool(data, "created"),
|
||||||
|
LikedByMe = BriskValueReader.GetBool(data, "liked_by_me"),
|
||||||
|
LikeCount = BriskValueReader.GetLong(data, "like_count"),
|
||||||
|
TodayLikeCount = BriskValueReader.GetLong(data, "today_like_count"),
|
||||||
|
LikeResetAt = BriskValueReader.GetString(data, "like_reset_at")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,10 @@ public sealed class BriskOptions
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool ValidateSessionOnInitialize = true;
|
public bool ValidateSessionOnInitialize = true;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
/// 初始化恢复的本地会话已过期或失效时,是否使用本地保存的登录身份静默换取新会话。
|
||||||
|
/// </summary>
|
||||||
|
public bool AutoReloginOnInitialize = true;
|
||||||
|
/// <summary>
|
||||||
/// 自定义登录态持久化实现。
|
/// 自定义登录态持久化实现。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IBriskTokenStore TokenStore;
|
public IBriskTokenStore TokenStore;
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
public sealed class BriskSpaceLikeResult
|
public sealed class BriskSpaceLikeResult
|
||||||
{
|
{
|
||||||
public bool Liked;
|
public bool Liked;
|
||||||
|
public bool Created;
|
||||||
|
public bool LikedByMe;
|
||||||
public long LikeCount;
|
public long LikeCount;
|
||||||
|
public long TodayLikeCount;
|
||||||
|
public string LikeResetAt;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ public sealed class BriskSpaceStats
|
|||||||
public long ContentSizeBytes;
|
public long ContentSizeBytes;
|
||||||
public string ContentChecksum;
|
public string ContentChecksum;
|
||||||
public long LikeCount;
|
public long LikeCount;
|
||||||
|
public long TodayLikeCount;
|
||||||
public long VisitCount;
|
public long VisitCount;
|
||||||
|
public string LikeResetAt;
|
||||||
public string UpdatedAt;
|
public string UpdatedAt;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ public sealed class BriskSpaceView
|
|||||||
public long ContentSizeBytes;
|
public long ContentSizeBytes;
|
||||||
public string ContentChecksum;
|
public string ContentChecksum;
|
||||||
public long LikeCount;
|
public long LikeCount;
|
||||||
|
public long TodayLikeCount;
|
||||||
public long VisitCount;
|
public long VisitCount;
|
||||||
public bool LikedByMe;
|
public bool LikedByMe;
|
||||||
|
public string LikeResetAt;
|
||||||
public string UpdatedAt;
|
public string UpdatedAt;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,23 +68,23 @@ public sealed class BriskSpaceModule
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 按玩家 ID 获取最近点赞列表。
|
/// 按玩家 ID 获取点赞列表。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public async Task<IReadOnlyList<BriskSpaceLikeItem>> GetLikesByPlayerIdAsync(string playerId, int limit = 20)
|
public async Task<IReadOnlyList<BriskSpaceLikeItem>> GetLikesByPlayerIdAsync(string playerId, int limit = 20, bool currentCycleOnly = false)
|
||||||
{
|
{
|
||||||
ValidatePlayerId(playerId);
|
ValidatePlayerId(playerId);
|
||||||
|
|
||||||
return await ExecuteAsync(async context =>
|
return await ExecuteAsync(async context =>
|
||||||
{
|
{
|
||||||
var data = await context.HttpClient.GetRawDataAsync($"/spaces/{playerId}/likes", CreateLimitQuery(limit), true);
|
var data = await context.HttpClient.GetRawDataAsync($"/spaces/{playerId}/likes", CreateLikesQuery(limit, currentCycleOnly), true);
|
||||||
return (IReadOnlyList<BriskSpaceLikeItem>)BriskModelMapper.ToSpaceLikeItems(data);
|
return (IReadOnlyList<BriskSpaceLikeItem>)BriskModelMapper.ToSpaceLikeItems(data);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 按登录身份获取最近点赞列表。
|
/// 按登录身份获取点赞列表。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public async Task<IReadOnlyList<BriskSpaceLikeItem>> GetLikesByLoginIdentityAsync(string loginProvider, string loginUserId, int limit = 20)
|
public async Task<IReadOnlyList<BriskSpaceLikeItem>> GetLikesByLoginIdentityAsync(string loginProvider, string loginUserId, int limit = 20, bool currentCycleOnly = false)
|
||||||
{
|
{
|
||||||
ValidateLoginIdentity(loginProvider, loginUserId);
|
ValidateLoginIdentity(loginProvider, loginUserId);
|
||||||
|
|
||||||
@@ -92,6 +92,10 @@ public sealed class BriskSpaceModule
|
|||||||
{
|
{
|
||||||
var query = CreateLoginIdentityQuery(loginProvider, loginUserId);
|
var query = CreateLoginIdentityQuery(loginProvider, loginUserId);
|
||||||
query["limit"] = NormalizeLimit(limit);
|
query["limit"] = NormalizeLimit(limit);
|
||||||
|
if (currentCycleOnly)
|
||||||
|
{
|
||||||
|
query["scope"] = "cycle";
|
||||||
|
}
|
||||||
var data = await context.HttpClient.GetRawDataAsync("/spaces/by-login/likes", query, true);
|
var data = await context.HttpClient.GetRawDataAsync("/spaces/by-login/likes", query, true);
|
||||||
return (IReadOnlyList<BriskSpaceLikeItem>)BriskModelMapper.ToSpaceLikeItems(data);
|
return (IReadOnlyList<BriskSpaceLikeItem>)BriskModelMapper.ToSpaceLikeItems(data);
|
||||||
});
|
});
|
||||||
@@ -236,7 +240,7 @@ public sealed class BriskSpaceModule
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取我的访客列表。
|
/// 获取我的访客列表。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public async Task<IReadOnlyList<BriskSpaceVisit>> GetMyVisitsAsync(int limit = 20)
|
public async Task<IReadOnlyList<BriskSpaceVisit>> GetMyVisitsAsync(int limit = 50)
|
||||||
{
|
{
|
||||||
return await ExecuteAsync(async context =>
|
return await ExecuteAsync(async context =>
|
||||||
{
|
{
|
||||||
@@ -262,6 +266,17 @@ public sealed class BriskSpaceModule
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Dictionary<string, string> CreateLikesQuery(int limit, bool currentCycleOnly)
|
||||||
|
{
|
||||||
|
var query = CreateLimitQuery(limit);
|
||||||
|
if (currentCycleOnly)
|
||||||
|
{
|
||||||
|
query["scope"] = "cycle";
|
||||||
|
}
|
||||||
|
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
private static void ValidatePlayerId(string playerId)
|
private static void ValidatePlayerId(string playerId)
|
||||||
{
|
{
|
||||||
RequireNotEmpty(playerId, nameof(playerId));
|
RequireNotEmpty(playerId, nameof(playerId));
|
||||||
|
|||||||
@@ -112,6 +112,8 @@ public sealed class BriskQuickStartSample : MonoBehaviour
|
|||||||
private GUIStyle _statusStyle;
|
private GUIStyle _statusStyle;
|
||||||
private GUIStyle _listButtonStyle;
|
private GUIStyle _listButtonStyle;
|
||||||
private GUIStyle _modalStyle;
|
private GUIStyle _modalStyle;
|
||||||
|
private PropertyInfo _defaultDialogVisibleProperty;
|
||||||
|
private MethodInfo _defaultDialogRenderMethod;
|
||||||
|
|
||||||
private void OnEnable()
|
private void OnEnable()
|
||||||
{
|
{
|
||||||
@@ -142,6 +144,12 @@ public sealed class BriskQuickStartSample : MonoBehaviour
|
|||||||
|
|
||||||
private void OnGUI()
|
private void OnGUI()
|
||||||
{
|
{
|
||||||
|
if (ShouldRouteInputToDefaultDialog())
|
||||||
|
{
|
||||||
|
RenderDefaultErrorDialogTopmost();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
EnsureStyles();
|
EnsureStyles();
|
||||||
|
|
||||||
var scale = Mathf.Min(Screen.width / DesignWidth, Screen.height / DesignHeight);
|
var scale = Mathf.Min(Screen.width / DesignWidth, Screen.height / DesignHeight);
|
||||||
@@ -167,6 +175,54 @@ public sealed class BriskQuickStartSample : MonoBehaviour
|
|||||||
}
|
}
|
||||||
|
|
||||||
GUI.matrix = previousMatrix;
|
GUI.matrix = previousMatrix;
|
||||||
|
|
||||||
|
RenderDefaultErrorDialogTopmost();
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ShouldRouteInputToDefaultDialog()
|
||||||
|
{
|
||||||
|
if (!IsDefaultErrorDialogVisible() || Event.current == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (Event.current.type)
|
||||||
|
{
|
||||||
|
case EventType.Layout:
|
||||||
|
case EventType.Repaint:
|
||||||
|
return false;
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsDefaultErrorDialogVisible()
|
||||||
|
{
|
||||||
|
EnsureDefaultDialogReflection();
|
||||||
|
return _defaultDialogVisibleProperty != null && (bool)_defaultDialogVisibleProperty.GetValue(null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderDefaultErrorDialogTopmost()
|
||||||
|
{
|
||||||
|
EnsureDefaultDialogReflection();
|
||||||
|
_defaultDialogRenderMethod?.Invoke(null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureDefaultDialogReflection()
|
||||||
|
{
|
||||||
|
if (_defaultDialogVisibleProperty != null && _defaultDialogRenderMethod != null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var dialogType = typeof(Brisk).Assembly.GetType("BriskDefaultErrorDialog");
|
||||||
|
if (dialogType == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_defaultDialogVisibleProperty = dialogType.GetProperty("IsVisible", BindingFlags.Static | BindingFlags.NonPublic);
|
||||||
|
_defaultDialogRenderMethod = dialogType.GetMethod("RenderTopmostLegacyOverlay", BindingFlags.Static | BindingFlags.NonPublic);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawCurrentPage()
|
private void DrawCurrentPage()
|
||||||
@@ -219,6 +275,19 @@ public sealed class BriskQuickStartSample : MonoBehaviour
|
|||||||
GUILayout.Label("失败会停留在当前页,并把错误展示在底部状态栏。", _bodyStyle);
|
GUILayout.Label("失败会停留在当前页,并把错误展示在底部状态栏。", _bodyStyle);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
DrawCard("默认弹窗外观预览", () =>
|
||||||
|
{
|
||||||
|
GUILayout.Label("这些按钮只展示 SDK 默认弹窗外观,不会修改登录状态,也不会请求服务器。", _bodyStyle);
|
||||||
|
DrawInlineButtons(
|
||||||
|
new ButtonSpec("登录过期", PreviewAuthExpiredDialogAsync, false, true),
|
||||||
|
new ButtonSpec("缺少 Token", PreviewMissingTokenDialogAsync, false, true));
|
||||||
|
GUILayout.Space(8f);
|
||||||
|
DrawInlineButtons(
|
||||||
|
new ButtonSpec("服务维护", PreviewMaintenanceDialogAsync, false, true),
|
||||||
|
new ButtonSpec("账号受限", PreviewAccountBannedDialogAsync, false, true),
|
||||||
|
new ButtonSpec("需要更新", PreviewUpdateRequiredDialogAsync, false, true));
|
||||||
|
});
|
||||||
|
|
||||||
GUILayout.FlexibleSpace();
|
GUILayout.FlexibleSpace();
|
||||||
GUILayout.EndScrollView();
|
GUILayout.EndScrollView();
|
||||||
GUILayout.EndArea();
|
GUILayout.EndArea();
|
||||||
@@ -475,14 +544,16 @@ public sealed class BriskQuickStartSample : MonoBehaviour
|
|||||||
DrawValueRow("内容版本", _mySpaceView == null ? "-" : _mySpaceView.ContentVersion.ToString());
|
DrawValueRow("内容版本", _mySpaceView == null ? "-" : _mySpaceView.ContentVersion.ToString());
|
||||||
DrawValueRow("内容类型", _mySpaceView == null ? "-" : NullToDash(_mySpaceView.ContentType));
|
DrawValueRow("内容类型", _mySpaceView == null ? "-" : NullToDash(_mySpaceView.ContentType));
|
||||||
DrawValueRow("内容大小", _mySpaceView == null ? "-" : _mySpaceView.ContentSizeBytes + " bytes");
|
DrawValueRow("内容大小", _mySpaceView == null ? "-" : _mySpaceView.ContentSizeBytes + " bytes");
|
||||||
DrawValueRow("点赞数", _mySpaceStats == null ? "-" : _mySpaceStats.LikeCount.ToString());
|
DrawValueRow("累计点赞数", _mySpaceStats == null ? "-" : _mySpaceStats.LikeCount.ToString());
|
||||||
|
DrawValueRow("今日点赞数", _mySpaceStats == null ? "-" : _mySpaceStats.TodayLikeCount.ToString());
|
||||||
DrawValueRow("访问数", _mySpaceStats == null ? "-" : _mySpaceStats.VisitCount.ToString());
|
DrawValueRow("访问数", _mySpaceStats == null ? "-" : _mySpaceStats.VisitCount.ToString());
|
||||||
|
DrawValueRow("今日点赞重置", _mySpaceStats == null ? "-" : FormatLikeResetAt(_mySpaceStats.LikeResetAt));
|
||||||
DrawValueRow("更新时间", _mySpaceView == null ? "-" : NullToDash(_mySpaceView.UpdatedAt));
|
DrawValueRow("更新时间", _mySpaceView == null ? "-" : NullToDash(_mySpaceView.UpdatedAt));
|
||||||
GUILayout.Space(8f);
|
GUILayout.Space(8f);
|
||||||
GUILayout.Label("最近给我点赞的用户", _sectionTitleStyle);
|
GUILayout.Label("今天给我点赞的用户", _sectionTitleStyle);
|
||||||
if (_mySpaceLikes.Count == 0)
|
if (_mySpaceLikes.Count == 0)
|
||||||
{
|
{
|
||||||
GUILayout.Label("暂无点赞记录。", _bodyStyle);
|
GUILayout.Label("当前周期暂无点赞记录。", _bodyStyle);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -509,9 +580,11 @@ public sealed class BriskQuickStartSample : MonoBehaviour
|
|||||||
DrawValueRow("PlayerId", _targetSpaceView == null ? "-" : NullToDash(_targetSpaceView.PlayerId));
|
DrawValueRow("PlayerId", _targetSpaceView == null ? "-" : NullToDash(_targetSpaceView.PlayerId));
|
||||||
DrawValueRow("内容类型", _targetSpaceView == null ? "-" : NullToDash(_targetSpaceView.ContentType));
|
DrawValueRow("内容类型", _targetSpaceView == null ? "-" : NullToDash(_targetSpaceView.ContentType));
|
||||||
DrawValueRow("内容大小", _targetSpaceView == null ? "-" : _targetSpaceView.ContentSizeBytes + " bytes");
|
DrawValueRow("内容大小", _targetSpaceView == null ? "-" : _targetSpaceView.ContentSizeBytes + " bytes");
|
||||||
DrawValueRow("点赞数", _targetSpaceStats == null ? "-" : _targetSpaceStats.LikeCount.ToString());
|
DrawValueRow("累计点赞数", _targetSpaceStats == null ? "-" : _targetSpaceStats.LikeCount.ToString());
|
||||||
|
DrawValueRow("今日点赞数", _targetSpaceStats == null ? "-" : _targetSpaceStats.TodayLikeCount.ToString());
|
||||||
DrawValueRow("访问数", _targetSpaceStats == null ? "-" : _targetSpaceStats.VisitCount.ToString());
|
DrawValueRow("访问数", _targetSpaceStats == null ? "-" : _targetSpaceStats.VisitCount.ToString());
|
||||||
DrawValueRow("我的点赞状态", _targetSpaceView != null && _targetSpaceView.LikedByMe ? "已点赞" : "未点赞");
|
DrawValueRow("今日点赞状态", _targetSpaceView != null && _targetSpaceView.LikedByMe ? "今日已点赞" : "今日未点赞");
|
||||||
|
DrawValueRow("今日点赞重置", _targetSpaceView == null ? "-" : FormatLikeResetAt(_targetSpaceView.LikeResetAt));
|
||||||
GUILayout.Space(8f);
|
GUILayout.Space(8f);
|
||||||
GUILayout.Label("空间内容", _sectionTitleStyle);
|
GUILayout.Label("空间内容", _sectionTitleStyle);
|
||||||
GUILayout.TextArea(_targetSpaceContentText ?? string.Empty, _textAreaStyle, GUILayout.Height(240f));
|
GUILayout.TextArea(_targetSpaceContentText ?? string.Empty, _textAreaStyle, GUILayout.Height(240f));
|
||||||
@@ -520,8 +593,8 @@ public sealed class BriskQuickStartSample : MonoBehaviour
|
|||||||
DrawCard("操作", () =>
|
DrawCard("操作", () =>
|
||||||
{
|
{
|
||||||
DrawInlineButtons(
|
DrawInlineButtons(
|
||||||
new ButtonSpec("点赞", LikeTargetSpaceAsync, false, true),
|
new ButtonSpec("今日点赞", LikeTargetSpaceAsync, false, _targetSpaceView != null && !_targetSpaceView.LikedByMe),
|
||||||
new ButtonSpec("取消点赞", UnlikeTargetSpaceAsync, false, true));
|
new ButtonSpec("撤销今日点赞", UnlikeTargetSpaceAsync, false, _targetSpaceView != null && _targetSpaceView.LikedByMe));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -559,6 +632,36 @@ public sealed class BriskQuickStartSample : MonoBehaviour
|
|||||||
_page = SamplePage.Home;
|
_page = SamplePage.Home;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Task PreviewAuthExpiredDialogAsync()
|
||||||
|
{
|
||||||
|
BriskDefaultErrorPresenter.Instance.ShowAuthExpired(new BriskAuthExpiredException("invalid token"));
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task PreviewMissingTokenDialogAsync()
|
||||||
|
{
|
||||||
|
BriskDefaultErrorPresenter.Instance.ShowAuthExpired(new BriskAuthExpiredException("Missing access token."));
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task PreviewMaintenanceDialogAsync()
|
||||||
|
{
|
||||||
|
BriskDefaultErrorPresenter.Instance.ShowBlockingError(new BriskMaintenanceException("maintenance"));
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task PreviewAccountBannedDialogAsync()
|
||||||
|
{
|
||||||
|
BriskDefaultErrorPresenter.Instance.ShowBlockingError(new BriskAccountBannedException("account banned"));
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Task PreviewUpdateRequiredDialogAsync()
|
||||||
|
{
|
||||||
|
BriskDefaultErrorPresenter.Instance.ShowBlockingError(new BriskClientUpdateRequiredException("client update required"));
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
private async Task RefreshHomeAsync()
|
private async Task RefreshHomeAsync()
|
||||||
{
|
{
|
||||||
_me = await Brisk.Player.GetMeAsync();
|
_me = await Brisk.Player.GetMeAsync();
|
||||||
@@ -636,7 +739,7 @@ public sealed class BriskQuickStartSample : MonoBehaviour
|
|||||||
|
|
||||||
_mySpaceView = await Brisk.Space.GetByPlayerIdAsync(Brisk.PlayerId);
|
_mySpaceView = await Brisk.Space.GetByPlayerIdAsync(Brisk.PlayerId);
|
||||||
_mySpaceStats = await Brisk.Space.GetStatsByPlayerIdAsync(Brisk.PlayerId);
|
_mySpaceStats = await Brisk.Space.GetStatsByPlayerIdAsync(Brisk.PlayerId);
|
||||||
_mySpaceLikes = await Brisk.Space.GetLikesByPlayerIdAsync(Brisk.PlayerId, 10);
|
_mySpaceLikes = await Brisk.Space.GetLikesByPlayerIdAsync(Brisk.PlayerId, 10, true);
|
||||||
SpacePayloadText = await DownloadSpaceTextAsync(_mySpaceView, () => Brisk.Space.DownloadContentByPlayerIdAsync(Brisk.PlayerId));
|
SpacePayloadText = await DownloadSpaceTextAsync(_mySpaceView, () => Brisk.Space.DownloadContentByPlayerIdAsync(Brisk.PlayerId));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -673,13 +776,15 @@ public sealed class BriskQuickStartSample : MonoBehaviour
|
|||||||
|
|
||||||
private async Task LikeTargetSpaceAsync()
|
private async Task LikeTargetSpaceAsync()
|
||||||
{
|
{
|
||||||
await Brisk.Space.LikeByPlayerIdAsync(SpacePlayerId);
|
var result = await Brisk.Space.LikeByPlayerIdAsync(SpacePlayerId);
|
||||||
|
Log("空间今日点赞结果: created=" + result.Created + ", total=" + result.LikeCount + ", today=" + result.TodayLikeCount);
|
||||||
await LoadTargetSpaceAsync();
|
await LoadTargetSpaceAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task UnlikeTargetSpaceAsync()
|
private async Task UnlikeTargetSpaceAsync()
|
||||||
{
|
{
|
||||||
await Brisk.Space.UnlikeByPlayerIdAsync(SpacePlayerId);
|
var result = await Brisk.Space.UnlikeByPlayerIdAsync(SpacePlayerId);
|
||||||
|
Log("空间撤销今日点赞结果: total=" + result.LikeCount + ", today=" + result.TodayLikeCount);
|
||||||
await LoadTargetSpaceAsync();
|
await LoadTargetSpaceAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1120,6 +1225,22 @@ public sealed class BriskQuickStartSample : MonoBehaviour
|
|||||||
return string.IsNullOrWhiteSpace(value) ? "-" : value;
|
return string.IsNullOrWhiteSpace(value) ? "-" : value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string FormatLikeResetAt(string value)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
|
{
|
||||||
|
return "-";
|
||||||
|
}
|
||||||
|
|
||||||
|
DateTimeOffset parsed;
|
||||||
|
if (!DateTimeOffset.TryParse(value, out parsed))
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsed.LocalDateTime.ToString("yyyy-MM-dd HH:mm:ss");
|
||||||
|
}
|
||||||
|
|
||||||
private static async Task<string> DownloadSpaceTextAsync(BriskSpaceView view, Func<Task<BriskSpaceContentDownloadResult>> downloadContent)
|
private static async Task<string> DownloadSpaceTextAsync(BriskSpaceView view, Func<Task<BriskSpaceContentDownloadResult>> downloadContent)
|
||||||
{
|
{
|
||||||
if (view == null || !view.ContentExists || downloadContent == null)
|
if (view == null || !view.ContentExists || downloadContent == null)
|
||||||
|
|||||||
@@ -259,7 +259,7 @@ MonoBehaviour:
|
|||||||
m_Name:
|
m_Name:
|
||||||
m_EditorClassIdentifier:
|
m_EditorClassIdentifier:
|
||||||
BaseUrl: https://brisk.lightyears.ltd
|
BaseUrl: https://brisk.lightyears.ltd
|
||||||
GameKey: briskh5verify
|
GameKey: game_a1faf5ee93d0
|
||||||
ClientVersion: 1.0.0
|
ClientVersion: 1.0.0
|
||||||
DeviceId: editor-device
|
DeviceId: editor-device
|
||||||
ValidateSessionOnInitialize: 1
|
ValidateSessionOnInitialize: 1
|
||||||
@@ -268,7 +268,7 @@ MonoBehaviour:
|
|||||||
LoginCode:
|
LoginCode:
|
||||||
Nickname: "Unity\u793A\u4F8B\u73A9\u5BB6"
|
Nickname: "Unity\u793A\u4F8B\u73A9\u5BB6"
|
||||||
AvatarUrl:
|
AvatarUrl:
|
||||||
RankKey: h5-verify-rank-20260410034312-5cdd
|
RankKey: rank-20260411062004-2bce
|
||||||
SubmitScoreValue: 128
|
SubmitScoreValue: 128
|
||||||
LeaderboardLimit: 10
|
LeaderboardLimit: 10
|
||||||
AroundMeRange: 5
|
AroundMeRange: 5
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "com.foldcc.cc-framework.brisk-game-server",
|
"name": "com.foldcc.cc-framework.brisk-game-server",
|
||||||
"displayName": "FoldCC Brisk Game Server SDK",
|
"displayName": "FoldCC Brisk Game Server SDK",
|
||||||
"version": "0.3.0",
|
"version": "0.4.1",
|
||||||
"unity": "2021.3",
|
"unity": "2021.3",
|
||||||
"description": "A lightweight Unity SDK for Brisk game services, including bootstrap, auth, announcements, leaderboard, archive, and player space modules.",
|
"description": "A lightweight Unity SDK for Brisk game services, including bootstrap, auth, announcements, leaderboard, archive, and player space modules.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
10
README.md
10
README.md
@@ -34,6 +34,9 @@ Brisk Unity SDK 原始开发工程。
|
|||||||
- `Brisk.Space.UpdateMyAsync(byte[])` 可直接更新二进制内容
|
- `Brisk.Space.UpdateMyAsync(byte[])` 可直接更新二进制内容
|
||||||
- `Brisk.Space.UpdateMyAsync(object)` 会自动转成 JSON 上传
|
- `Brisk.Space.UpdateMyAsync(object)` 会自动转成 JSON 上传
|
||||||
- `Brisk.Space.DownloadContentByPlayerIdAsync(playerId)` 获取原始 bytes
|
- `Brisk.Space.DownloadContentByPlayerIdAsync(playerId)` 获取原始 bytes
|
||||||
|
- `Brisk.Space.LikeByPlayerIdAsync(playerId)` / `UnlikeByPlayerIdAsync(playerId)` 会返回累计点赞数、今日点赞数、当前周期是否创建了新点赞、重置时间
|
||||||
|
- `Brisk.Space.GetLikesByPlayerIdAsync(playerId, limit, true)` 可只读取当前周期点赞列表
|
||||||
|
- `Brisk.Space.GetMyVisitsAsync()` 默认读取最近 `50` 条访问记录,也可手动传入 `limit`
|
||||||
|
|
||||||
云存档上传补充约定:
|
云存档上传补充约定:
|
||||||
|
|
||||||
@@ -82,6 +85,7 @@ Brisk Unity SDK 原始开发工程。
|
|||||||
- 排行榜读取、提交分数、赛季查询
|
- 排行榜读取、提交分数、赛季查询
|
||||||
- 云存档上传下载
|
- 云存档上传下载
|
||||||
- 玩家空间读取、点赞、更新、访客
|
- 玩家空间读取、点赞、更新、访客
|
||||||
|
- 玩家空间今日点赞数、今日点赞状态、今日点赞重置时间
|
||||||
- 全局事件日志与最近一次结果输出
|
- 全局事件日志与最近一次结果输出
|
||||||
|
|
||||||
当前样例已经直接使用 `UploadTextAsync` / `DownloadTextAsync` 这类快捷接口;如果业务需要,也可以继续走原始 bytes 接口。
|
当前样例已经直接使用 `UploadTextAsync` / `DownloadTextAsync` 这类快捷接口;如果业务需要,也可以继续走原始 bytes 接口。
|
||||||
@@ -138,7 +142,7 @@ Brisk Unity SDK 原始开发工程。
|
|||||||
外部项目 Git Package 接入示例:
|
外部项目 Git Package 接入示例:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
http://private.lightyears.ltd:18650/foldcc/CC-Framework.BriskGameServer.git?path=/PackageSource/com.foldcc.cc-framework.brisk-game-server#v0.3.0
|
http://private.lightyears.ltd:18650/foldcc/CC-Framework.BriskGameServer.git?path=/PackageSource/com.foldcc.cc-framework.brisk-game-server#v0.4.1
|
||||||
```
|
```
|
||||||
|
|
||||||
## 如何引入到项目
|
## 如何引入到项目
|
||||||
@@ -165,12 +169,12 @@ http://private.lightyears.ltd:18650/foldcc/CC-Framework.BriskGameServer.git?path
|
|||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"com.foldcc.cc-framework.brisk-game-server": "http://private.lightyears.ltd:18650/foldcc/CC-Framework.BriskGameServer.git?path=/PackageSource/com.foldcc.cc-framework.brisk-game-server#v0.3.0"
|
"com.foldcc.cc-framework.brisk-game-server": "http://private.lightyears.ltd:18650/foldcc/CC-Framework.BriskGameServer.git?path=/PackageSource/com.foldcc.cc-framework.brisk-game-server#v0.4.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
如果要跟随主分支最新代码,可把末尾的 `#v0.3.0` 改成 `#main`;正式项目仍建议固定到发布 tag。
|
如果要跟随主分支最新代码,可把末尾的 `#v0.4.1` 改成 `#main`;正式项目仍建议固定到发布 tag。
|
||||||
|
|
||||||
## 文档位置
|
## 文档位置
|
||||||
|
|
||||||
|
|||||||
@@ -19,17 +19,41 @@
|
|||||||
|
|
||||||
## 2. 客户端主流程
|
## 2. 客户端主流程
|
||||||
|
|
||||||
1. `GET /api/client/bootstrap`
|
1. 可选:`GET /api/client/time`
|
||||||
2. `POST /api/auth/login/exchange`
|
2. `GET /api/client/bootstrap`
|
||||||
3. 带 `Bearer access_token` 调用:
|
3. `POST /api/auth/login/exchange`
|
||||||
|
4. 带 `Bearer access_token` 调用:
|
||||||
- `GET /api/player/me`
|
- `GET /api/player/me`
|
||||||
- `GET /api/announcements`
|
- `GET /api/announcements`
|
||||||
- 排行榜、存档、空间等业务接口
|
- 排行榜、存档、空间等业务接口
|
||||||
4. `GET /api/config/current` 为公开接口,登录前后都可调用
|
5. `GET /api/config/current` 为公开接口,登录前后都可调用
|
||||||
|
|
||||||
## 3. 初始化
|
## 3. 初始化
|
||||||
|
|
||||||
### 3.1 获取初始化信息
|
### 3.1 获取平台时间
|
||||||
|
|
||||||
|
`GET /api/client/time`
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
- 平台级匿名授时接口
|
||||||
|
- 不需要 `game_key`
|
||||||
|
- 不依赖任何项目配置
|
||||||
|
- 返回 UTC 时间,适合客户端启动时做统一对时
|
||||||
|
|
||||||
|
示例:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl "https://brisk.lightyears.ltd/api/client/time"
|
||||||
|
```
|
||||||
|
|
||||||
|
返回重点:
|
||||||
|
|
||||||
|
- `server_time`
|
||||||
|
- `server_unix`
|
||||||
|
- `server_unix_ms`
|
||||||
|
|
||||||
|
### 3.2 获取初始化信息
|
||||||
|
|
||||||
`GET /api/client/bootstrap`
|
`GET /api/client/bootstrap`
|
||||||
|
|
||||||
@@ -265,9 +289,38 @@ curl -X POST "https://brisk.lightyears.ltd/api/archives/slot/1/upload" \
|
|||||||
- `content_size_bytes`
|
- `content_size_bytes`
|
||||||
- `content_checksum`
|
- `content_checksum`
|
||||||
- `like_count`
|
- `like_count`
|
||||||
|
- `today_like_count`
|
||||||
|
- `liked_by_me`
|
||||||
|
- `like_reset_at`
|
||||||
- `visit_count`
|
- `visit_count`
|
||||||
- `updated_at`
|
- `updated_at`
|
||||||
|
|
||||||
|
点赞接口说明:
|
||||||
|
|
||||||
|
- 点赞按 `Asia/Shanghai` 自然日分周期
|
||||||
|
- 同一玩家对同一空间同一天重复点赞时:
|
||||||
|
- 接口仍返回成功
|
||||||
|
- `created=false`
|
||||||
|
- `like_count` 与 `today_like_count` 不会继续增加
|
||||||
|
- `DELETE /like` 只会撤销当前周期内的那一次点赞
|
||||||
|
- 支持给自己的空间点赞
|
||||||
|
|
||||||
|
点赞列表说明:
|
||||||
|
|
||||||
|
- `GET /api/spaces/{player_id}/likes`
|
||||||
|
- `GET /api/spaces/by-login/likes`
|
||||||
|
- 支持可选查询参数 `scope`
|
||||||
|
- `history`:历史点赞记录,默认值
|
||||||
|
- `cycle`:当前周期内的点赞记录
|
||||||
|
- `limit` 最大返回 `100`
|
||||||
|
|
||||||
|
最近访问列表说明:
|
||||||
|
|
||||||
|
- `GET /api/spaces/me/visits`
|
||||||
|
- 支持可选查询参数 `limit`
|
||||||
|
- 默认返回 `50` 条
|
||||||
|
- 单次最大返回 `100` 条
|
||||||
|
|
||||||
上传表单字段:
|
上传表单字段:
|
||||||
|
|
||||||
- `base_version`:用于乐观锁;首次上传可传 `0`
|
- `base_version`:用于乐观锁;首次上传可传 `0`
|
||||||
@@ -308,6 +361,26 @@ curl -X POST "https://brisk.lightyears.ltd/api/spaces/by-login/like?login_provid
|
|||||||
-H "Authorization: Bearer <token>"
|
-H "Authorization: Bearer <token>"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
按第三方身份查询当前周期点赞列表示例:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl "https://brisk.lightyears.ltd/api/spaces/by-login/likes?login_provider=tap&login_user_id=tap_user_10001&scope=cycle&limit=20" \
|
||||||
|
-H "Authorization: Bearer <token>"
|
||||||
|
```
|
||||||
|
|
||||||
|
点赞返回示例:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"liked": true,
|
||||||
|
"created": true,
|
||||||
|
"liked_by_me": true,
|
||||||
|
"like_count": 128,
|
||||||
|
"today_like_count": 17,
|
||||||
|
"like_reset_at": "2026-04-22T00:00:00+08:00"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## 10. 后台项目与配置
|
## 10. 后台项目与配置
|
||||||
|
|
||||||
### 10.1 项目
|
### 10.1 项目
|
||||||
|
|||||||
@@ -2,18 +2,18 @@
|
|||||||
|
|
||||||
## 1. 这份文档适合做什么
|
## 1. 这份文档适合做什么
|
||||||
|
|
||||||
这份文档用于说明当前 Brisk Unity SDK 的接入结构与推荐调用方式。
|
这份文档用于在 Unity 侧封装 Brisk SDK。
|
||||||
|
|
||||||
当前 SDK 已采用静态总入口:
|
建议把 SDK 拆成以下层次:
|
||||||
|
|
||||||
- `Brisk.InitializeAsync(...)`
|
- `BriskTime`
|
||||||
- `Brisk.Auth.xxx`
|
- `BriskClient`
|
||||||
- `Brisk.Player.xxx`
|
- `BriskAuth`
|
||||||
- `Brisk.Config.xxx`
|
- `BriskConfig`
|
||||||
- `Brisk.Announcements.xxx`
|
- `BriskAnnouncements`
|
||||||
- `Brisk.Leaderboard.xxx`
|
- `BriskRanks`
|
||||||
- `Brisk.Archive.xxx`
|
- `BriskArchives`
|
||||||
- `Brisk.Space.xxx`
|
- `BriskSpaces`
|
||||||
|
|
||||||
## 2. 初始化模型
|
## 2. 初始化模型
|
||||||
|
|
||||||
@@ -26,6 +26,8 @@ Unity 客户端初始化建议持有:
|
|||||||
|
|
||||||
不再需要项目级 `channel / platform`。
|
不再需要项目级 `channel / platform`。
|
||||||
|
|
||||||
|
如果只是做平台授时,不需要传 `gameKey`,可以单独调用时间接口。
|
||||||
|
|
||||||
示例:
|
示例:
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
@@ -64,18 +66,31 @@ Task<LoginResult> LoginAsync(
|
|||||||
|
|
||||||
## 4. 推荐接入顺序
|
## 4. 推荐接入顺序
|
||||||
|
|
||||||
1. `BootstrapAsync()`
|
1. 可选:`SyncServerTimeAsync()`
|
||||||
2. 从第三方 SDK 获取:
|
2. `BootstrapAsync()`
|
||||||
|
3. 从第三方 SDK 获取:
|
||||||
- `loginProvider`
|
- `loginProvider`
|
||||||
- `loginUserId` 或 `code`
|
- `loginUserId` 或 `code`
|
||||||
- 可选昵称、头像、扩展资料
|
- 可选昵称、头像、扩展资料
|
||||||
3. `LoginAsync(...)`
|
4. `LoginAsync(...)`
|
||||||
4. 保存 `accessToken`
|
5. 保存 `accessToken`
|
||||||
5. 拉取:
|
6. 拉取:
|
||||||
- `GetMeAsync()`
|
- `GetMeAsync()`
|
||||||
- `GetCurrentConfigAsync()`
|
- `GetCurrentConfigAsync()`
|
||||||
- `ListAnnouncementsAsync()`
|
- `ListAnnouncementsAsync()`
|
||||||
6. 进入业务模块
|
7. 进入业务模块
|
||||||
|
|
||||||
|
平台授时接口建议封装:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
Task<ServerTimeResult> SyncServerTimeAsync()
|
||||||
|
```
|
||||||
|
|
||||||
|
建议返回:
|
||||||
|
|
||||||
|
- `ServerTime`
|
||||||
|
- `ServerUnix`
|
||||||
|
- `ServerUnixMs`
|
||||||
|
|
||||||
## 5. 身份模型建议
|
## 5. 身份模型建议
|
||||||
|
|
||||||
@@ -142,27 +157,12 @@ Task LikeSpaceByLoginIdentityAsync(string loginProvider, string loginUserId)
|
|||||||
- msgpack bytes
|
- msgpack bytes
|
||||||
- 压缩后的存档二进制
|
- 压缩后的存档二进制
|
||||||
|
|
||||||
当前 SDK 已提供:
|
Unity SDK 建议提供:
|
||||||
|
|
||||||
- `GetArchiveSlotsAsync()`
|
- `GetArchiveSlotsAsync()`
|
||||||
- `GetArchiveMetaAsync(slotNo)`
|
- `GetArchiveMetaAsync(slotNo)`
|
||||||
- `UploadAsync(slotNo, byte[], baseVersion, checksum)`
|
- `UploadArchiveAsync(slotNo, baseVersion, checksum, byte[])`
|
||||||
- `UploadTextAsync(slotNo, text, baseVersion, checksum)`
|
|
||||||
- `UploadJsonAsync(slotNo, payload, baseVersion, checksum)`
|
|
||||||
- `DownloadArchiveAsync(slotNo)`
|
- `DownloadArchiveAsync(slotNo)`
|
||||||
- `DownloadTextAsync(slotNo)`
|
|
||||||
- `DownloadJsonAsync(slotNo)`
|
|
||||||
|
|
||||||
推荐上层使用策略:
|
|
||||||
|
|
||||||
- 文本存档:优先直接用 `UploadTextAsync / DownloadTextAsync`
|
|
||||||
- JSON 存档:优先直接用 `UploadJsonAsync / DownloadJsonAsync`
|
|
||||||
- 二进制存档:继续用 `UploadAsync / DownloadAsync`
|
|
||||||
|
|
||||||
其中:
|
|
||||||
|
|
||||||
- SDK 会自动计算上传用 `checksum`
|
|
||||||
- 手动传 `checksum` 时也可以带 `sha256:` 前缀,SDK 会自动归一化
|
|
||||||
|
|
||||||
## 9. 玩家空间模块建议
|
## 9. 玩家空间模块建议
|
||||||
|
|
||||||
@@ -177,7 +177,7 @@ Task LikeSpaceByLoginIdentityAsync(string loginProvider, string loginUserId)
|
|||||||
- 小型二进制空间内容
|
- 小型二进制空间内容
|
||||||
- 文本、JSON、protobuf、msgpack 等自定义格式
|
- 文本、JSON、protobuf、msgpack 等自定义格式
|
||||||
|
|
||||||
当前 SDK / 接口核心是:
|
当前接口核心是:
|
||||||
|
|
||||||
- `UploadMySpaceContentAsync(baseVersion, contentType, checksum, byte[])`
|
- `UploadMySpaceContentAsync(baseVersion, contentType, checksum, byte[])`
|
||||||
- `DownloadSpaceContentByPlayerIdAsync(playerId)`
|
- `DownloadSpaceContentByPlayerIdAsync(playerId)`
|
||||||
@@ -188,16 +188,6 @@ Task LikeSpaceByLoginIdentityAsync(string loginProvider, string loginUserId)
|
|||||||
- `LikeSpaceByLoginIdentityAsync(loginProvider, loginUserId)`
|
- `LikeSpaceByLoginIdentityAsync(loginProvider, loginUserId)`
|
||||||
- `GetMyVisitsAsync(limit)`
|
- `GetMyVisitsAsync(limit)`
|
||||||
|
|
||||||
当前 SDK 额外提供了一个极简便捷入口:
|
|
||||||
|
|
||||||
- `Brisk.Space.UpdateMyAsync(payload, baseVersion, contentType, checksum)`
|
|
||||||
|
|
||||||
它的行为是:
|
|
||||||
|
|
||||||
- `payload` 为 `string` 时,自动按 `text/plain` 上传
|
|
||||||
- `payload` 为 `byte[]` 时,自动按 `application/octet-stream` 上传
|
|
||||||
- `payload` 为其他对象时,自动序列化为 JSON 并按 `application/json` 上传
|
|
||||||
|
|
||||||
建议 SDK 暴露两层模型:
|
建议 SDK 暴露两层模型:
|
||||||
|
|
||||||
- 空间主信息:
|
- 空间主信息:
|
||||||
@@ -213,8 +203,7 @@ Task LikeSpaceByLoginIdentityAsync(string loginProvider, string loginUserId)
|
|||||||
- 下载响应头中的 `X-Space-Version`
|
- 下载响应头中的 `X-Space-Version`
|
||||||
- 下载响应头中的 `X-Space-Checksum`
|
- 下载响应头中的 `X-Space-Checksum`
|
||||||
|
|
||||||
如果上层业务追求最省事,可以直接用 `UpdateMyAsync(string)` 或 `UpdateMyAsync(object)`;
|
如果上层业务实际保存的是文本或 JSON,建议仍按 `byte[]` 统一收发,再由业务层自己决定如何编码和反序列化。
|
||||||
如果业务自己管理编码、压缩、protobuf、msgpack 等格式,则继续走 `byte[]` 上传下载接口。
|
|
||||||
|
|
||||||
## 10. 动态配置建议
|
## 10. 动态配置建议
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Brisk 错误码文档(内部实施版)
|
# Brisk 错误码文档(内部实施版)
|
||||||
|
|
||||||
**版本**:v0.1
|
**版本**:v0.2
|
||||||
**日期**:2026-04-08
|
**日期**:2026-04-11
|
||||||
**说明**:当前工程内已使用的错误码分组与含义。
|
**说明**:当前工程内已使用的错误码分组与含义。
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -45,6 +45,13 @@
|
|||||||
|
|
||||||
## 3. 当前已使用错误码
|
## 3. 当前已使用错误码
|
||||||
|
|
||||||
|
补充说明:
|
||||||
|
|
||||||
|
- `GET /api/client/time` 为平台匿名授时接口
|
||||||
|
- 该接口当前没有新增专属业务错误码
|
||||||
|
- 触发限流时仍使用通用限流错误码 `80001`
|
||||||
|
- 限流组件不可用时仍使用通用错误码 `80002`
|
||||||
|
|
||||||
| 错误码 | 接口/模块 | 含义 |
|
| 错误码 | 接口/模块 | 含义 |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `10001` | 客户端鉴权 | 缺少 token |
|
| `10001` | 客户端鉴权 | 缺少 token |
|
||||||
@@ -210,6 +217,7 @@
|
|||||||
| `70040` | `spaces/{player_id}/likes` | 缺少空间目标参数 |
|
| `70040` | `spaces/{player_id}/likes` | 缺少空间目标参数 |
|
||||||
| `70041` | `spaces/{player_id}/likes` | 玩家不存在 |
|
| `70041` | `spaces/{player_id}/likes` | 玩家不存在 |
|
||||||
| `70042` | `spaces/{player_id}/likes` | 点赞列表查询失败 |
|
| `70042` | `spaces/{player_id}/likes` | 点赞列表查询失败 |
|
||||||
|
| `70043` | `spaces/{player_id}/likes` | 点赞列表 scope 参数非法 |
|
||||||
|
|
||||||
| 错误码 | 接口/模块 | 含义 |
|
| 错误码 | 接口/模块 | 含义 |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ Brisk 游戏服务端工程。
|
|||||||
|
|
||||||
当前已实现模块:
|
当前已实现模块:
|
||||||
|
|
||||||
|
- 平台授时 `time`
|
||||||
- 客户端初始化 `bootstrap`
|
- 客户端初始化 `bootstrap`
|
||||||
- 登录换票 `auth`
|
- 登录换票 `auth`
|
||||||
- 玩家信息 `player`
|
- 玩家信息 `player`
|
||||||
@@ -20,6 +21,7 @@ Brisk 游戏服务端工程。
|
|||||||
- 项目唯一标识只有 `game_key`
|
- 项目唯一标识只有 `game_key`
|
||||||
- 项目级 `channel / platform` 已移除
|
- 项目级 `channel / platform` 已移除
|
||||||
- 初始化和动态配置只依赖 `game_key`,配置筛选只看 `client_version`
|
- 初始化和动态配置只依赖 `game_key`,配置筛选只看 `client_version`
|
||||||
|
- 平台授时接口不依赖 `game_key`,可直接匿名获取 UTC 服务器时间
|
||||||
- 登录模块单独处理第三方身份:
|
- 登录模块单独处理第三方身份:
|
||||||
- `login_provider`:如 `tap`
|
- `login_provider`:如 `tap`
|
||||||
- `login_user_id`:该登录平台返回的稳定唯一用户 ID
|
- `login_user_id`:该登录平台返回的稳定唯一用户 ID
|
||||||
@@ -43,6 +45,7 @@ docker compose up --build
|
|||||||
|
|
||||||
- `GET /health`
|
- `GET /health`
|
||||||
- `GET /api/ping`
|
- `GET /api/ping`
|
||||||
|
- `GET /api/client/time`
|
||||||
- `GET /openapi.yaml`
|
- `GET /openapi.yaml`
|
||||||
|
|
||||||
开发环境默认账号:
|
开发环境默认账号:
|
||||||
@@ -54,6 +57,10 @@ docker compose up --build
|
|||||||
|
|
||||||
## 接入说明
|
## 接入说明
|
||||||
|
|
||||||
|
- 平台授时:
|
||||||
|
- 匿名接口 `GET /api/client/time`
|
||||||
|
- 无需传 `game_key`
|
||||||
|
- 返回 `server_time`、`server_unix`、`server_unix_ms`
|
||||||
- 客户端初始化:
|
- 客户端初始化:
|
||||||
- 必填 `game_key`
|
- 必填 `game_key`
|
||||||
- 可选 `client_version`
|
- 可选 `client_version`
|
||||||
@@ -69,6 +76,9 @@ docker compose up --build
|
|||||||
- 主信息接口返回空间元数据
|
- 主信息接口返回空间元数据
|
||||||
- 自定义空间内容通过独立二进制接口上传/下载
|
- 自定义空间内容通过独立二进制接口上传/下载
|
||||||
- 支持任意 bytes,兼容二进制、文本、JSON、protobuf、msgpack 等格式
|
- 支持任意 bytes,兼容二进制、文本、JSON、protobuf、msgpack 等格式
|
||||||
|
- 空间点赞按 `Asia/Shanghai` 自然日计周期,同一玩家对同一空间每天最多记一次点赞
|
||||||
|
- 空间点赞相关接口返回累计点赞数 `like_count`、当日点赞数 `today_like_count`、当日是否已点赞 `liked_by_me`
|
||||||
|
- 最近访问列表支持可选 `limit` 参数,默认 `50` 条,最大 `100` 条
|
||||||
|
|
||||||
## 测试脚本
|
## 测试脚本
|
||||||
|
|
||||||
@@ -77,6 +87,7 @@ Windows PowerShell:
|
|||||||
```powershell
|
```powershell
|
||||||
.\scripts\smoke-test.ps1
|
.\scripts\smoke-test.ps1
|
||||||
.\scripts\acceptance.ps1 -Tasks all -ManageStack
|
.\scripts\acceptance.ps1 -Tasks all -ManageStack
|
||||||
|
.\\scripts\\acceptance.ps1 -Tasks space -ManageStack
|
||||||
```
|
```
|
||||||
|
|
||||||
## 迁移
|
## 迁移
|
||||||
@@ -86,6 +97,7 @@ Windows PowerShell:
|
|||||||
- [0001_init.sql](/F:/OtherWork/BriskGameSerivce/internal/platform/db/migrations/0001_init.sql)
|
- [0001_init.sql](/F:/OtherWork/BriskGameSerivce/internal/platform/db/migrations/0001_init.sql)
|
||||||
- [0008_identity_and_config_simplification.sql](/F:/OtherWork/BriskGameSerivce/internal/platform/db/migrations/0008_identity_and_config_simplification.sql)
|
- [0008_identity_and_config_simplification.sql](/F:/OtherWork/BriskGameSerivce/internal/platform/db/migrations/0008_identity_and_config_simplification.sql)
|
||||||
- [0009_space_binary_content.sql](/F:/OtherWork/BriskGameSerivce/internal/platform/db/migrations/0009_space_binary_content.sql)
|
- [0009_space_binary_content.sql](/F:/OtherWork/BriskGameSerivce/internal/platform/db/migrations/0009_space_binary_content.sql)
|
||||||
|
- [0010_space_like_daily_cycle.sql](/F:/OtherWork/BriskGameSerivce/internal/platform/db/migrations/0010_space_like_daily_cycle.sql)
|
||||||
|
|
||||||
服务启动时会自动执行未应用迁移。
|
服务启动时会自动执行未应用迁移。
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
openapi: 3.0.3
|
openapi: 3.0.3
|
||||||
info:
|
info:
|
||||||
title: Brisk Game Service API
|
title: Brisk Game Service API
|
||||||
version: 0.1.0
|
version: 0.2.0
|
||||||
description: Current implemented Brisk server API for internal integration.
|
description: Current implemented Brisk server API for internal integration.
|
||||||
servers:
|
servers:
|
||||||
- url: /api
|
- url: /api
|
||||||
tags:
|
tags:
|
||||||
|
- name: time
|
||||||
- name: bootstrap
|
- name: bootstrap
|
||||||
- name: auth
|
- name: auth
|
||||||
- name: player
|
- name: player
|
||||||
@@ -51,6 +52,21 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
data:
|
data:
|
||||||
nullable: true
|
nullable: true
|
||||||
|
ClientTimePayload:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
server_time:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
example: 2026-04-11T08:12:30Z
|
||||||
|
server_unix:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
example: 1775895150
|
||||||
|
server_unix_ms:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
example: 1775895150123
|
||||||
ExchangeRequest:
|
ExchangeRequest:
|
||||||
type: object
|
type: object
|
||||||
required: [game_key, login_provider]
|
required: [game_key, login_provider]
|
||||||
@@ -397,11 +413,17 @@ components:
|
|||||||
like_count:
|
like_count:
|
||||||
type: integer
|
type: integer
|
||||||
format: int64
|
format: int64
|
||||||
|
today_like_count:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
visit_count:
|
visit_count:
|
||||||
type: integer
|
type: integer
|
||||||
format: int64
|
format: int64
|
||||||
liked_by_me:
|
liked_by_me:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
like_reset_at:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
updated_at:
|
updated_at:
|
||||||
type: string
|
type: string
|
||||||
format: date-time
|
format: date-time
|
||||||
@@ -436,9 +458,15 @@ components:
|
|||||||
like_count:
|
like_count:
|
||||||
type: integer
|
type: integer
|
||||||
format: int64
|
format: int64
|
||||||
|
today_like_count:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
visit_count:
|
visit_count:
|
||||||
type: integer
|
type: integer
|
||||||
format: int64
|
format: int64
|
||||||
|
like_reset_at:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
updated_at:
|
updated_at:
|
||||||
type: string
|
type: string
|
||||||
format: date-time
|
format: date-time
|
||||||
@@ -466,9 +494,19 @@ components:
|
|||||||
properties:
|
properties:
|
||||||
liked:
|
liked:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
created:
|
||||||
|
type: boolean
|
||||||
|
liked_by_me:
|
||||||
|
type: boolean
|
||||||
like_count:
|
like_count:
|
||||||
type: integer
|
type: integer
|
||||||
format: int64
|
format: int64
|
||||||
|
today_like_count:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
like_reset_at:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
SpaceVisitItem:
|
SpaceVisitItem:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
@@ -493,7 +531,36 @@ components:
|
|||||||
created_at:
|
created_at:
|
||||||
type: string
|
type: string
|
||||||
format: date-time
|
format: date-time
|
||||||
|
SpaceLikeListResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
items:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/SpaceLikeItem'
|
||||||
paths:
|
paths:
|
||||||
|
/client/time:
|
||||||
|
get:
|
||||||
|
tags: [time]
|
||||||
|
summary: Get platform server time
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Current platform UTC time
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
code:
|
||||||
|
type: integer
|
||||||
|
example: 0
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
example: ok
|
||||||
|
data:
|
||||||
|
$ref: '#/components/schemas/ClientTimePayload'
|
||||||
|
'429': { description: Rate limited }
|
||||||
|
'500': { description: Rate limit unavailable }
|
||||||
/client/bootstrap:
|
/client/bootstrap:
|
||||||
get:
|
get:
|
||||||
tags: [bootstrap]
|
tags: [bootstrap]
|
||||||
@@ -877,15 +944,20 @@ paths:
|
|||||||
- in: query
|
- in: query
|
||||||
name: limit
|
name: limit
|
||||||
schema: { type: integer }
|
schema: { type: integer }
|
||||||
|
- in: query
|
||||||
|
name: scope
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
enum: [history, cycle]
|
||||||
|
default: history
|
||||||
|
description: Whether to list historical likes or only likes from the current daily cycle.
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: Space likes
|
description: Space likes
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
type: array
|
$ref: '#/components/schemas/SpaceLikeListResponse'
|
||||||
items:
|
|
||||||
$ref: '#/components/schemas/SpaceLikeItem'
|
|
||||||
/spaces/me/content:
|
/spaces/me/content:
|
||||||
put:
|
put:
|
||||||
tags: [spaces]
|
tags: [spaces]
|
||||||
@@ -1041,15 +1113,20 @@ paths:
|
|||||||
- in: query
|
- in: query
|
||||||
name: limit
|
name: limit
|
||||||
schema: { type: integer }
|
schema: { type: integer }
|
||||||
|
- in: query
|
||||||
|
name: scope
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
enum: [history, cycle]
|
||||||
|
default: history
|
||||||
|
description: Whether to list historical likes or only likes from the current daily cycle.
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: Space likes
|
description: Space likes
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
type: array
|
$ref: '#/components/schemas/SpaceLikeListResponse'
|
||||||
items:
|
|
||||||
$ref: '#/components/schemas/SpaceLikeItem'
|
|
||||||
/spaces/by-login/like:
|
/spaces/by-login/like:
|
||||||
post:
|
post:
|
||||||
tags: [spaces]
|
tags: [spaces]
|
||||||
@@ -1101,7 +1178,8 @@ paths:
|
|||||||
parameters:
|
parameters:
|
||||||
- in: query
|
- in: query
|
||||||
name: limit
|
name: limit
|
||||||
schema: { type: integer }
|
schema: { type: integer, default: 50 }
|
||||||
|
description: Optional page size. Defaults to 50 and is capped at 100.
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: Visitor list
|
description: Visitor list
|
||||||
|
|||||||
Reference in New Issue
Block a user