Files
CC-Framework.BriskGameServer/Assets/BriskSdk/Samples/QuickStart/BriskQuickStartSample.cs

1370 lines
47 KiB
C#
Raw Normal View History

2026-04-10 22:04:51 +08:00
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
public sealed class BriskQuickStartSample : MonoBehaviour
{
private const float DesignWidth = 720f;
private const float DesignHeight = 1280f;
private enum SamplePage
{
Initialize,
Login,
Home,
Announcements,
Leaderboard,
Archive,
Space
}
[Header("初始化")]
2026-04-10 22:04:51 +08:00
public string BaseUrl = "https://brisk.lightyears.ltd";
public string GameKey = "demo-game";
public string ClientVersion = "1.0.0";
public string DeviceId = "editor-device";
public bool ValidateSessionOnInitialize = true;
[Header("登录")]
2026-04-10 22:04:51 +08:00
public string LoginProvider = "tap";
public string LoginUserId = "tap_user_10001";
public string LoginCode = string.Empty;
public string Nickname = "Unity示例玩家";
2026-04-10 22:04:51 +08:00
public string AvatarUrl = string.Empty;
[Header("排行榜")]
2026-04-10 22:04:51 +08:00
public string RankKey = "season-score";
public string SubmitScoreValue = "0";
2026-04-10 22:04:51 +08:00
public string LeaderboardLimit = "10";
public string AroundMeRange = "5";
public string SeasonId = string.Empty;
public string SeasonHistoryLimit = "20";
[Header("云存档")]
2026-04-10 22:04:51 +08:00
public string ArchiveSlotNo = "1";
public string ArchiveBaseVersion = string.Empty;
[TextArea(3, 6)]
public string ArchiveContent = "{\n \"save\": 1,\n \"coins\": 128,\n \"hero\": \"mage\",\n \"title\": \"中文测试存档\"\n}";
2026-04-10 22:04:51 +08:00
[Header("公告")]
2026-04-10 22:04:51 +08:00
public string AnnouncementId = string.Empty;
[Header("玩家空间")]
2026-04-10 22:04:51 +08:00
public string SpacePlayerId = string.Empty;
public string SpaceLoginProvider = "tap";
public string SpaceLoginUserId = "tap_user_10001";
[TextArea(3, 6)]
public string SpacePayloadText = "这里是 unity 测试账户的空间内容。\n\n0.o";
2026-04-10 22:04:51 +08:00
[Header("演示")]
2026-04-10 22:04:51 +08:00
public bool AutoRunOnStart;
private readonly List<string> _logs = new List<string>();
2026-04-10 22:04:51 +08:00
private Vector2 _initScroll;
private Vector2 _loginScroll;
private Vector2 _homeScroll;
private Vector2 _announcementScroll;
private Vector2 _leaderboardScroll;
private Vector2 _archiveScroll;
private Vector2 _spaceScroll;
private Vector2 _modalScroll;
private SamplePage _page = SamplePage.Initialize;
2026-04-10 22:04:51 +08:00
private bool _isBusy;
private string _busyAction = string.Empty;
private string _statusText = "等待开始";
private string _errorText = string.Empty;
private BriskPlayerMe _me;
private BriskConfigCurrent _config;
private IReadOnlyList<BriskAnnouncementItem> _announcements = Array.Empty<BriskAnnouncementItem>();
private BriskAnnouncementItem _selectedAnnouncement;
private IReadOnlyList<BriskLeaderboardEntry> _leaderboardEntries = Array.Empty<BriskLeaderboardEntry>();
private BriskLeaderboardPlayerRank _leaderboardMe;
private IReadOnlyList<BriskArchiveSlot> _archiveSlots = Array.Empty<BriskArchiveSlot>();
private BriskArchiveMeta _archiveMeta;
private BriskSpaceView _mySpaceView;
private BriskSpaceStats _mySpaceStats;
private IReadOnlyList<BriskSpaceLikeItem> _mySpaceLikes = Array.Empty<BriskSpaceLikeItem>();
private BriskSpaceView _targetSpaceView;
private BriskSpaceStats _targetSpaceStats;
private string _targetSpaceContentText = string.Empty;
private bool _viewAroundMe;
private bool _showAnnouncementModal;
private bool _viewingTargetSpace;
private GUIStyle _pageStyle;
private GUIStyle _cardStyle;
private GUIStyle _titleStyle;
private GUIStyle _sectionTitleStyle;
private GUIStyle _bodyStyle;
private GUIStyle _hintStyle;
private GUIStyle _buttonStyle;
private GUIStyle _primaryButtonStyle;
private GUIStyle _dangerButtonStyle;
private GUIStyle _inputStyle;
private GUIStyle _textAreaStyle;
private GUIStyle _statusStyle;
private GUIStyle _listButtonStyle;
private GUIStyle _modalStyle;
2026-05-13 19:05:00 +08:00
private PropertyInfo _defaultDialogVisibleProperty;
private MethodInfo _defaultDialogRenderMethod;
2026-04-10 22:04:51 +08:00
private void OnEnable()
{
Brisk.OnInitialized += HandleInitialized;
Brisk.OnLoggedIn += HandleLoggedIn;
Brisk.OnLoggedOut += HandleLoggedOut;
Brisk.OnAuthExpired += HandleAuthExpired;
Brisk.OnBlockingError += HandleBlockingError;
}
private void OnDisable()
{
Brisk.OnInitialized -= HandleInitialized;
Brisk.OnLoggedIn -= HandleLoggedIn;
Brisk.OnLoggedOut -= HandleLoggedOut;
Brisk.OnAuthExpired -= HandleAuthExpired;
Brisk.OnBlockingError -= HandleBlockingError;
}
private void Start()
{
SyncPageBySdkState();
2026-04-10 22:04:51 +08:00
if (AutoRunOnStart)
{
RunAction("自动冒烟", RunSmokeFlowAsync);
2026-04-10 22:04:51 +08:00
}
}
private void OnGUI()
{
2026-05-13 19:05:00 +08:00
if (ShouldRouteInputToDefaultDialog())
{
RenderDefaultErrorDialogTopmost();
return;
}
EnsureStyles();
2026-04-10 22:04:51 +08:00
var scale = Mathf.Min(Screen.width / DesignWidth, Screen.height / DesignHeight);
var offsetX = (Screen.width - (DesignWidth * scale)) * 0.5f;
var offsetY = (Screen.height - (DesignHeight * scale)) * 0.5f;
var previousMatrix = GUI.matrix;
GUI.matrix = Matrix4x4.TRS(new Vector3(offsetX, offsetY, 0f), Quaternion.identity, new Vector3(scale, scale, 1f));
GUI.Box(new Rect(0f, 0f, DesignWidth, DesignHeight), GUIContent.none);
GUILayout.BeginArea(new Rect(0f, 0f, DesignWidth, DesignHeight));
DrawCurrentPage();
2026-04-10 22:04:51 +08:00
GUILayout.EndArea();
if (_showAnnouncementModal && _selectedAnnouncement != null)
{
DrawAnnouncementModal();
}
if (_isBusy)
{
DrawBusyOverlay();
}
GUI.matrix = previousMatrix;
2026-05-13 19:05:00 +08:00
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);
2026-04-10 22:04:51 +08:00
}
private void DrawCurrentPage()
2026-04-10 22:04:51 +08:00
{
switch (_page)
{
case SamplePage.Initialize:
DrawInitializePage();
break;
case SamplePage.Login:
DrawLoginPage();
break;
case SamplePage.Home:
DrawHomePage();
break;
case SamplePage.Announcements:
DrawAnnouncementsPage();
break;
case SamplePage.Leaderboard:
DrawLeaderboardPage();
break;
case SamplePage.Archive:
DrawArchivePage();
break;
default:
DrawSpacePage();
break;
}
2026-04-10 22:04:51 +08:00
}
private void DrawInitializePage()
2026-04-10 22:04:51 +08:00
{
var contentRect = DrawPageFrame("初始化", null, null);
GUILayout.BeginArea(contentRect);
_initScroll = GUILayout.BeginScrollView(_initScroll);
2026-04-10 22:04:51 +08:00
DrawCard("当前配置", () =>
{
BaseUrl = DrawInputRow("服务地址", BaseUrl);
GameKey = DrawInputRow("游戏 Key", GameKey);
ClientVersion = DrawInputRow("客户端版本", ClientVersion);
DeviceId = DrawInputRow("设备标识", DeviceId);
ValidateSessionOnInitialize = DrawToggleRow("初始化时校验旧会话", ValidateSessionOnInitialize);
2026-04-10 22:04:51 +08:00
});
DrawCard("说明", () =>
2026-04-10 22:04:51 +08:00
{
GUILayout.Label("点击“初始化”后会直接调用 Brisk.InitializeAsync。", _bodyStyle);
GUILayout.Label("成功时自动进入登录页;如果已恢复登录态,则直接进入主页。", _bodyStyle);
GUILayout.Label("失败会停留在当前页,并把错误展示在底部状态栏。", _bodyStyle);
2026-04-10 22:04:51 +08:00
});
2026-05-13 19:05:00 +08:00
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.EndScrollView();
GUILayout.EndArea();
DrawBottomButtons(
new ButtonSpec("初始化", InitializeFlowAsync, true, true),
new ButtonSpec("重新填写", ResetTransientStatusAsync, false, true));
2026-04-10 22:04:51 +08:00
}
private void DrawLoginPage()
2026-04-10 22:04:51 +08:00
{
var contentRect = DrawPageFrame("登录", "返回初始化", () => _page = SamplePage.Initialize);
GUILayout.BeginArea(contentRect);
_loginScroll = GUILayout.BeginScrollView(_loginScroll);
2026-04-10 22:04:51 +08:00
DrawCard("登录信息", () =>
{
LoginProvider = DrawInputRow("登录提供方", LoginProvider);
LoginUserId = DrawInputRow("用户 ID", LoginUserId);
LoginCode = DrawInputRow("登录 Code可选", LoginCode);
Nickname = DrawInputRow("昵称", Nickname);
AvatarUrl = DrawInputRow("头像地址", AvatarUrl);
});
DrawCard("说明", () =>
{
GUILayout.Label("点击“登录”时:如果填写了 LoginCode则优先走 code 登录;否则走用户 ID 登录。", _bodyStyle);
GUILayout.Label("登录成功后自动拉取主页数据,并进入主页。", _bodyStyle);
});
GUILayout.EndScrollView();
GUILayout.EndArea();
DrawBottomButtons(new ButtonSpec("登录", LoginFlowAsync, true, true));
2026-04-10 22:04:51 +08:00
}
private void DrawHomePage()
2026-04-10 22:04:51 +08:00
{
var contentRect = DrawPageFrame("主页", "退出登录", () => RunAction("退出登录", LogoutFlowAsync));
GUILayout.BeginArea(contentRect);
_homeScroll = GUILayout.BeginScrollView(_homeScroll);
2026-04-10 22:04:51 +08:00
DrawCard("当前用户状态", () =>
{
DrawValueRow("已初始化", Brisk.IsInitialized ? "是" : "否");
DrawValueRow("已登录", Brisk.IsLoggedIn ? "是" : "否");
DrawValueRow("PlayerId", NullToDash(Brisk.PlayerId));
DrawValueRow("登录身份", Brisk.Identity == null ? "-" : Brisk.Identity.LoginProvider + " / " + Brisk.Identity.LoginUserId);
DrawValueRow("昵称", _me == null ? "-" : NullToDash(_me.Nickname));
DrawValueRow("项目账号", _me == null ? "-" : NullToDash(_me.ProjectAccountId));
});
DrawCard("动态配置", () =>
{
GUILayout.Label("FeatureFlags", _sectionTitleStyle);
GUILayout.TextArea(FormatValue(_config == null ? null : _config.FeatureFlags), _textAreaStyle, GUILayout.Height(120f));
GUILayout.Space(8f);
GUILayout.Label("DynamicConfig", _sectionTitleStyle);
GUILayout.TextArea(FormatValue(_config == null ? null : _config.DynamicConfig), _textAreaStyle, GUILayout.Height(160f));
});
DrawCard("快捷操作", () =>
{
DrawInlineButtons(
new ButtonSpec("刷新主页数据", RefreshHomeAsync, false, true),
new ButtonSpec("查看日志", ShowHomeLogsAsync, false, true));
});
GUILayout.EndScrollView();
GUILayout.EndArea();
DrawBottomNav(
new NavSpec("公告", SamplePage.Announcements),
new NavSpec("排行榜", SamplePage.Leaderboard),
new NavSpec("云存档", SamplePage.Archive),
new NavSpec("空间", SamplePage.Space));
2026-04-10 22:04:51 +08:00
}
private void DrawAnnouncementsPage()
2026-04-10 22:04:51 +08:00
{
var contentRect = DrawPageFrame("公告", "返回主页", () => _page = SamplePage.Home, "刷新", LoadAnnouncementsAsync);
GUILayout.BeginArea(contentRect);
_announcementScroll = GUILayout.BeginScrollView(_announcementScroll);
if (_announcements.Count == 0)
2026-04-10 22:04:51 +08:00
{
DrawCard("公告列表", () => GUILayout.Label("暂无公告,点击右上角“刷新”获取。", _bodyStyle));
}
else
{
DrawCard("公告列表", () =>
2026-04-10 22:04:51 +08:00
{
for (var i = 0; i < _announcements.Count; i++)
{
var item = _announcements[i];
var prefix = item.IsRead ? "[已读] " : "[未读] ";
if (GUILayout.Button(prefix + item.Title, _listButtonStyle, GUILayout.Height(58f)))
{
_selectedAnnouncement = item;
_showAnnouncementModal = true;
}
}
2026-04-10 22:04:51 +08:00
});
}
GUILayout.EndScrollView();
GUILayout.EndArea();
2026-04-10 22:04:51 +08:00
}
private void DrawLeaderboardPage()
2026-04-10 22:04:51 +08:00
{
var contentRect = DrawPageFrame("排行榜", "返回主页", () => _page = SamplePage.Home);
GUILayout.BeginArea(contentRect);
_leaderboardScroll = GUILayout.BeginScrollView(_leaderboardScroll);
2026-04-10 22:04:51 +08:00
DrawCard("排行榜配置", () =>
{
RankKey = DrawInputRow("排行榜 Key", RankKey);
LeaderboardLimit = DrawInputRow("Top 数量", LeaderboardLimit);
AroundMeRange = DrawInputRow("附近范围", AroundMeRange);
DrawInlineButtons(
new ButtonSpec("更新列表", RefreshLeaderboardAsync, false, true),
new ButtonSpec(_viewAroundMe ? "切换到 Top" : "切换到我附近", ToggleLeaderboardModeAsync, false, true));
});
2026-04-10 22:04:51 +08:00
DrawCard("我的排名", () =>
{
DrawValueRow("当前模式", _viewAroundMe ? "我附近的排名" : "Top 排行榜");
DrawValueRow("我的名次", _leaderboardMe == null ? "-" : _leaderboardMe.Rank.ToString());
DrawValueRow("我的分数", _leaderboardMe == null ? "-" : _leaderboardMe.Score.ToString());
DrawValueRow("当前待提交分数", SubmitScoreValue);
DrawInlineButtons(new ButtonSpec("分数 +1 并提交", IncreaseAndSubmitScoreAsync, true, true));
});
2026-04-10 22:04:51 +08:00
DrawCard("排行榜列表", () =>
{
if (_leaderboardEntries.Count == 0)
{
GUILayout.Label("暂无数据,请先点击“更新列表”。", _bodyStyle);
return;
}
2026-04-10 22:04:51 +08:00
for (var i = 0; i < _leaderboardEntries.Count; i++)
{
var entry = _leaderboardEntries[i];
GUILayout.BeginVertical(_cardStyle);
GUILayout.Label("#" + entry.Rank + " " + NullToDash(entry.Nickname), _sectionTitleStyle);
GUILayout.Label("PlayerId: " + NullToDash(entry.PlayerId), _bodyStyle);
GUILayout.Label("分数: " + entry.Score, _bodyStyle);
GUILayout.EndVertical();
}
});
GUILayout.EndScrollView();
GUILayout.EndArea();
2026-04-10 22:04:51 +08:00
}
private void DrawArchivePage()
2026-04-10 22:04:51 +08:00
{
var contentRect = DrawPageFrame("云存档", "返回主页", () => _page = SamplePage.Home);
GUILayout.BeginArea(contentRect);
_archiveScroll = GUILayout.BeginScrollView(_archiveScroll);
2026-04-10 22:04:51 +08:00
DrawCard("槽位与操作", () =>
{
ArchiveSlotNo = DrawInputRow("槽位号", ArchiveSlotNo);
ArchiveBaseVersion = DrawInputRow("基准版本", ArchiveBaseVersion);
DrawInlineButtons(
new ButtonSpec("获取槽位列表", GetArchiveSlotsAsync, false, true),
new ButtonSpec("获取元信息", GetArchiveMetaAsync, false, true),
new ButtonSpec("下载存档", DownloadArchiveAsync, false, true),
new ButtonSpec("上传存档", UploadArchiveAsync, true, true));
});
DrawCard("文本存档内容", () =>
{
ArchiveContent = GUILayout.TextArea(ArchiveContent ?? string.Empty, _textAreaStyle, GUILayout.Height(260f));
});
DrawCard("槽位列表", () =>
{
if (_archiveSlots.Count == 0)
{
GUILayout.Label("暂无槽位数据。", _bodyStyle);
}
else
{
for (var i = 0; i < _archiveSlots.Count; i++)
{
var slot = _archiveSlots[i];
GUILayout.Label("槽位 " + slot.SlotNo + " | version " + slot.Version + " | " + slot.SizeBytes + " bytes", _bodyStyle);
}
}
GUILayout.Space(8f);
GUILayout.Label("当前元信息", _sectionTitleStyle);
GUILayout.TextArea(FormatValue(_archiveMeta), _textAreaStyle, GUILayout.Height(120f));
});
GUILayout.EndScrollView();
GUILayout.EndArea();
2026-04-10 22:04:51 +08:00
}
private void DrawSpacePage()
2026-04-10 22:04:51 +08:00
{
var title = _viewingTargetSpace ? "TA 的空间" : "我的空间";
var backLabel = _viewingTargetSpace ? "返回我的空间" : "返回主页";
Action backAction = () =>
{
if (_viewingTargetSpace)
{
_viewingTargetSpace = false;
_targetSpaceView = null;
_targetSpaceStats = null;
_targetSpaceContentText = string.Empty;
return;
}
2026-04-10 22:04:51 +08:00
_page = SamplePage.Home;
};
2026-04-10 22:04:51 +08:00
var contentRect = DrawPageFrame(title, backLabel, backAction, "刷新", RefreshSpacePageAsync);
GUILayout.BeginArea(contentRect);
_spaceScroll = GUILayout.BeginScrollView(_spaceScroll);
2026-04-10 22:04:51 +08:00
if (_viewingTargetSpace)
{
DrawTargetSpaceContent();
}
else
{
DrawMySpaceContent();
}
GUILayout.EndScrollView();
GUILayout.EndArea();
2026-04-10 22:04:51 +08:00
}
private void DrawMySpaceContent()
2026-04-10 22:04:51 +08:00
{
DrawCard("我的空间内容", () =>
{
GUILayout.Label("当前空间文本", _sectionTitleStyle);
SpacePayloadText = GUILayout.TextArea(SpacePayloadText ?? string.Empty, _textAreaStyle, GUILayout.Height(240f));
DrawInlineButtons(
new ButtonSpec("更新我的空间", UpdateMySpaceAsync, true, true),
new ButtonSpec("重新拉取我的空间", LoadMySpaceAsync, false, true));
});
2026-04-10 22:04:51 +08:00
DrawCard("我的空间状态", () =>
{
DrawValueRow("我的 PlayerId", NullToDash(Brisk.PlayerId));
DrawValueRow("昵称", _mySpaceView == null ? "-" : NullToDash(_mySpaceView.Nickname));
DrawValueRow("内容版本", _mySpaceView == null ? "-" : _mySpaceView.ContentVersion.ToString());
DrawValueRow("内容类型", _mySpaceView == null ? "-" : NullToDash(_mySpaceView.ContentType));
DrawValueRow("内容大小", _mySpaceView == null ? "-" : _mySpaceView.ContentSizeBytes + " bytes");
DrawValueRow("累计点赞数", _mySpaceStats == null ? "-" : _mySpaceStats.LikeCount.ToString());
DrawValueRow("今日点赞数", _mySpaceStats == null ? "-" : _mySpaceStats.TodayLikeCount.ToString());
DrawValueRow("访问数", _mySpaceStats == null ? "-" : _mySpaceStats.VisitCount.ToString());
DrawValueRow("今日点赞重置", _mySpaceStats == null ? "-" : FormatLikeResetAt(_mySpaceStats.LikeResetAt));
DrawValueRow("更新时间", _mySpaceView == null ? "-" : NullToDash(_mySpaceView.UpdatedAt));
GUILayout.Space(8f);
GUILayout.Label("今天给我点赞的用户", _sectionTitleStyle);
if (_mySpaceLikes.Count == 0)
{
GUILayout.Label("当前周期暂无点赞记录。", _bodyStyle);
}
else
{
for (var i = 0; i < _mySpaceLikes.Count; i++)
{
var item = _mySpaceLikes[i];
GUILayout.Label(NullToDash(item.Nickname) + " | " + NullToDash(item.PlayerId), _bodyStyle);
}
}
});
2026-04-10 22:04:51 +08:00
DrawCard("访问其他用户空间", () =>
{
SpacePlayerId = DrawInputRow("目标用户 PlayerId", SpacePlayerId);
DrawInlineButtons(new ButtonSpec("前往该用户空间", VisitTargetSpaceAsync, true, true));
});
2026-04-10 22:04:51 +08:00
}
private void DrawTargetSpaceContent()
{
DrawCard("TA 的空间内容", () =>
{
DrawValueRow("昵称", _targetSpaceView == null ? "-" : NullToDash(_targetSpaceView.Nickname));
DrawValueRow("PlayerId", _targetSpaceView == null ? "-" : NullToDash(_targetSpaceView.PlayerId));
DrawValueRow("内容类型", _targetSpaceView == null ? "-" : NullToDash(_targetSpaceView.ContentType));
DrawValueRow("内容大小", _targetSpaceView == null ? "-" : _targetSpaceView.ContentSizeBytes + " bytes");
DrawValueRow("累计点赞数", _targetSpaceStats == null ? "-" : _targetSpaceStats.LikeCount.ToString());
DrawValueRow("今日点赞数", _targetSpaceStats == null ? "-" : _targetSpaceStats.TodayLikeCount.ToString());
DrawValueRow("访问数", _targetSpaceStats == null ? "-" : _targetSpaceStats.VisitCount.ToString());
DrawValueRow("今日点赞状态", _targetSpaceView != null && _targetSpaceView.LikedByMe ? "今日已点赞" : "今日未点赞");
DrawValueRow("今日点赞重置", _targetSpaceView == null ? "-" : FormatLikeResetAt(_targetSpaceView.LikeResetAt));
GUILayout.Space(8f);
GUILayout.Label("空间内容", _sectionTitleStyle);
GUILayout.TextArea(_targetSpaceContentText ?? string.Empty, _textAreaStyle, GUILayout.Height(240f));
});
DrawCard("操作", () =>
{
DrawInlineButtons(
new ButtonSpec("今日点赞", LikeTargetSpaceAsync, false, _targetSpaceView != null && !_targetSpaceView.LikedByMe),
new ButtonSpec("撤销今日点赞", UnlikeTargetSpaceAsync, false, _targetSpaceView != null && _targetSpaceView.LikedByMe));
});
}
private async Task InitializeFlowAsync()
2026-04-10 22:04:51 +08:00
{
await Brisk.InitializeAsync(new BriskOptions
{
BaseUrl = BaseUrl,
GameKey = GameKey,
ClientVersion = ClientVersion,
DeviceId = DeviceId,
ValidateSessionOnInitialize = ValidateSessionOnInitialize,
ExitHandler = HandleExitRequested
});
_page = Brisk.IsLoggedIn ? SamplePage.Home : SamplePage.Login;
if (Brisk.IsLoggedIn)
{
await RefreshHomeAsync();
}
2026-04-10 22:04:51 +08:00
}
private async Task LoginFlowAsync()
2026-04-10 22:04:51 +08:00
{
if (string.IsNullOrWhiteSpace(LoginCode))
{
await Brisk.Auth.LoginWithUserIdAsync(LoginProvider, LoginUserId, CreateProfile());
}
else
2026-04-10 22:04:51 +08:00
{
await Brisk.Auth.LoginWithCodeAsync(LoginProvider, LoginCode, CreateProfile());
2026-04-10 22:04:51 +08:00
}
await RefreshHomeAsync();
_page = SamplePage.Home;
2026-04-10 22:04:51 +08:00
}
2026-05-13 19:05:00 +08:00
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()
2026-04-10 22:04:51 +08:00
{
_me = await Brisk.Player.GetMeAsync();
_config = await Brisk.Config.GetCurrentAsync();
ApplyIdentity(_me.PlayerId, _me.LoginProvider, _me.LoginUserId);
await LoadMySpaceAsync();
2026-04-10 22:04:51 +08:00
}
private async Task LoadAnnouncementsAsync()
2026-04-10 22:04:51 +08:00
{
_announcements = await Brisk.Announcements.GetListAsync();
if (_announcements.Count > 0)
{
AnnouncementId = _announcements[0].Id.ToString();
}
2026-04-10 22:04:51 +08:00
}
private async Task RefreshLeaderboardAsync()
2026-04-10 22:04:51 +08:00
{
_leaderboardMe = await Brisk.Leaderboard.GetMeAsync(RankKey);
SubmitScoreValue = (_leaderboardMe == null ? 0L : _leaderboardMe.Score).ToString();
_leaderboardEntries = _viewAroundMe
? await Brisk.Leaderboard.GetAroundMeAsync(RankKey, ParseOrDefault(AroundMeRange, 5))
: await Brisk.Leaderboard.GetTopAsync(RankKey, ParseOrDefault(LeaderboardLimit, 10));
2026-04-10 22:04:51 +08:00
}
private async Task ToggleLeaderboardModeAsync()
2026-04-10 22:04:51 +08:00
{
_viewAroundMe = !_viewAroundMe;
await RefreshLeaderboardAsync();
2026-04-10 22:04:51 +08:00
}
private async Task IncreaseAndSubmitScoreAsync()
2026-04-10 22:04:51 +08:00
{
var score = ParseOrDefault(SubmitScoreValue, 0) + 1;
SubmitScoreValue = score.ToString();
await Brisk.Leaderboard.SubmitScoreAsync(RankKey, score);
await RefreshLeaderboardAsync();
2026-04-10 22:04:51 +08:00
}
private async Task GetArchiveSlotsAsync()
2026-04-10 22:04:51 +08:00
{
_archiveSlots = await Brisk.Archive.GetSlotsAsync();
}
2026-04-10 22:04:51 +08:00
private async Task GetArchiveMetaAsync()
{
_archiveMeta = await Brisk.Archive.GetMetaAsync(ParseRequiredInt(ArchiveSlotNo, nameof(ArchiveSlotNo)));
ArchiveBaseVersion = _archiveMeta == null ? ArchiveBaseVersion : _archiveMeta.Version.ToString();
2026-04-10 22:04:51 +08:00
}
private async Task DownloadArchiveAsync()
2026-04-10 22:04:51 +08:00
{
var result = await Brisk.Archive.DownloadAsync(ParseRequiredInt(ArchiveSlotNo, nameof(ArchiveSlotNo)));
ArchiveBaseVersion = result == null ? ArchiveBaseVersion : result.Version.ToString();
ArchiveContent = await Brisk.Archive.DownloadTextAsync(ParseRequiredInt(ArchiveSlotNo, nameof(ArchiveSlotNo)));
await GetArchiveMetaAsync();
}
private async Task UploadArchiveAsync()
{
var baseVersion = ParseNullableInt(ArchiveBaseVersion);
var result = await Brisk.Archive.UploadTextAsync(ParseRequiredInt(ArchiveSlotNo, nameof(ArchiveSlotNo)), ArchiveContent ?? string.Empty, baseVersion);
ArchiveBaseVersion = result == null ? ArchiveBaseVersion : result.Version.ToString();
await GetArchiveSlotsAsync();
await GetArchiveMetaAsync();
2026-04-10 22:04:51 +08:00
}
private async Task LoadMySpaceAsync()
2026-04-10 22:04:51 +08:00
{
if (string.IsNullOrWhiteSpace(Brisk.PlayerId))
2026-04-10 22:04:51 +08:00
{
return;
2026-04-10 22:04:51 +08:00
}
_mySpaceView = await Brisk.Space.GetByPlayerIdAsync(Brisk.PlayerId);
_mySpaceStats = await Brisk.Space.GetStatsByPlayerIdAsync(Brisk.PlayerId);
_mySpaceLikes = await Brisk.Space.GetLikesByPlayerIdAsync(Brisk.PlayerId, 10, true);
SpacePayloadText = await DownloadSpaceTextAsync(_mySpaceView, () => Brisk.Space.DownloadContentByPlayerIdAsync(Brisk.PlayerId));
2026-04-10 22:04:51 +08:00
}
private async Task RefreshSpacePageAsync()
2026-04-10 22:04:51 +08:00
{
if (_viewingTargetSpace)
{
await LoadTargetSpaceAsync();
return;
}
2026-04-10 22:04:51 +08:00
await LoadMySpaceAsync();
2026-04-10 22:04:51 +08:00
}
private async Task UpdateMySpaceAsync()
2026-04-10 22:04:51 +08:00
{
var baseVersion = _mySpaceView != null && _mySpaceView.ContentExists ? _mySpaceView.ContentVersion : 0L;
await Brisk.Space.UpdateMyAsync(SpacePayloadText ?? string.Empty, baseVersion, "text/plain");
await LoadMySpaceAsync();
2026-04-10 22:04:51 +08:00
}
private async Task VisitTargetSpaceAsync()
2026-04-10 22:04:51 +08:00
{
await LoadTargetSpaceAsync();
_viewingTargetSpace = true;
2026-04-10 22:04:51 +08:00
}
private async Task LoadTargetSpaceAsync()
2026-04-10 22:04:51 +08:00
{
_targetSpaceView = await Brisk.Space.GetByPlayerIdAsync(SpacePlayerId);
_targetSpaceStats = await Brisk.Space.GetStatsByPlayerIdAsync(SpacePlayerId);
_targetSpaceContentText = await DownloadSpaceTextAsync(_targetSpaceView, () => Brisk.Space.DownloadContentByPlayerIdAsync(SpacePlayerId));
2026-04-10 22:04:51 +08:00
}
private async Task LikeTargetSpaceAsync()
2026-04-10 22:04:51 +08:00
{
var result = await Brisk.Space.LikeByPlayerIdAsync(SpacePlayerId);
Log("空间今日点赞结果: created=" + result.Created + ", total=" + result.LikeCount + ", today=" + result.TodayLikeCount);
await LoadTargetSpaceAsync();
2026-04-10 22:04:51 +08:00
}
private async Task UnlikeTargetSpaceAsync()
2026-04-10 22:04:51 +08:00
{
var result = await Brisk.Space.UnlikeByPlayerIdAsync(SpacePlayerId);
Log("空间撤销今日点赞结果: total=" + result.LikeCount + ", today=" + result.TodayLikeCount);
await LoadTargetSpaceAsync();
2026-04-10 22:04:51 +08:00
}
private async Task LogoutFlowAsync()
2026-04-10 22:04:51 +08:00
{
await Brisk.Auth.LogoutAsync();
_page = SamplePage.Login;
2026-04-10 22:04:51 +08:00
}
private Task ResetTransientStatusAsync()
2026-04-10 22:04:51 +08:00
{
_statusText = "等待开始";
_errorText = string.Empty;
return Task.CompletedTask;
2026-04-10 22:04:51 +08:00
}
private Task ShowHomeLogsAsync()
2026-04-10 22:04:51 +08:00
{
_errorText = string.Join("\n", _logs.ToArray());
return Task.CompletedTask;
2026-04-10 22:04:51 +08:00
}
private async Task RunSmokeFlowAsync()
2026-04-10 22:04:51 +08:00
{
await InitializeFlowAsync();
if (!Brisk.IsLoggedIn)
2026-04-10 22:04:51 +08:00
{
await LoginFlowAsync();
}
2026-04-10 22:04:51 +08:00
}
private void DrawAnnouncementModal()
2026-04-10 22:04:51 +08:00
{
GUI.color = new Color(0f, 0f, 0f, 0.55f);
GUI.Box(new Rect(0f, 0f, DesignWidth, DesignHeight), GUIContent.none);
GUI.color = Color.white;
var modalRect = new Rect(48f, 180f, DesignWidth - 96f, DesignHeight - 360f);
GUILayout.BeginArea(modalRect, GUI.skin.box);
GUILayout.Label(_selectedAnnouncement.Title, _titleStyle);
GUILayout.Label("开始: " + NullToDash(_selectedAnnouncement.StartAt), _bodyStyle);
GUILayout.Label("结束: " + NullToDash(_selectedAnnouncement.EndAt), _bodyStyle);
GUILayout.Label("状态: " + (_selectedAnnouncement.IsRead ? "已读" : "未读"), _bodyStyle);
GUILayout.Space(8f);
_modalScroll = GUILayout.BeginScrollView(_modalScroll, GUILayout.Height(520f));
GUILayout.TextArea(NullToDash(_selectedAnnouncement.Content), _textAreaStyle, GUILayout.ExpandHeight(true));
GUILayout.EndScrollView();
GUILayout.Space(10f);
DrawInlineButtons(
new ButtonSpec("标记已读", MarkSelectedAnnouncementAsync, false, true),
new ButtonSpec("关闭", CloseAnnouncementModalAsync, false, true));
GUILayout.EndArea();
2026-04-10 22:04:51 +08:00
}
private Task CloseAnnouncementModalAsync()
2026-04-10 22:04:51 +08:00
{
_showAnnouncementModal = false;
return Task.CompletedTask;
2026-04-10 22:04:51 +08:00
}
private async Task MarkSelectedAnnouncementAsync()
2026-04-10 22:04:51 +08:00
{
if (_selectedAnnouncement == null)
{
return;
}
await Brisk.Announcements.MarkReadAsync(_selectedAnnouncement.Id);
_selectedAnnouncement.IsRead = true;
await LoadAnnouncementsAsync();
_showAnnouncementModal = false;
2026-04-10 22:04:51 +08:00
}
private void DrawBusyOverlay()
2026-04-10 22:04:51 +08:00
{
GUI.color = new Color(0f, 0f, 0f, 0.35f);
GUI.Box(new Rect(0f, 0f, DesignWidth, DesignHeight), GUIContent.none);
GUI.color = Color.white;
GUI.Box(new Rect(180f, 580f, 360f, 120f), "正在处理: " + _busyAction);
2026-04-10 22:04:51 +08:00
}
private Rect DrawPageFrame(string title, string backLabel, Action backAction, string rightLabel = null, Func<Task> rightAction = null)
2026-04-10 22:04:51 +08:00
{
var outerRect = new Rect(20f, 20f, DesignWidth - 40f, DesignHeight - 40f);
GUI.Box(outerRect, GUIContent.none, _pageStyle);
var headerRect = new Rect(outerRect.x + 20f, outerRect.y + 16f, outerRect.width - 40f, 70f);
GUILayout.BeginArea(headerRect);
GUILayout.BeginHorizontal();
if (!string.IsNullOrWhiteSpace(backLabel))
{
DrawHeaderButton(backLabel, backAction);
}
else
{
GUILayout.Space(150f);
}
GUILayout.FlexibleSpace();
GUILayout.Label(title, _titleStyle, GUILayout.Height(48f));
GUILayout.FlexibleSpace();
if (!string.IsNullOrWhiteSpace(rightLabel) && rightAction != null)
{
DrawHeaderButton(rightLabel, () => RunAction(rightLabel, rightAction));
}
else
{
GUILayout.Space(150f);
}
GUILayout.EndHorizontal();
GUILayout.EndArea();
var statusRect = new Rect(outerRect.x + 20f, headerRect.yMax + 6f, outerRect.width - 40f, 74f);
GUI.Box(statusRect, GUIContent.none, _cardStyle);
GUILayout.BeginArea(new Rect(statusRect.x + 12f, statusRect.y + 10f, statusRect.width - 24f, statusRect.height - 20f));
GUILayout.Label("状态: " + _statusText, _statusStyle);
GUILayout.Label("错误: " + (string.IsNullOrWhiteSpace(_errorText) ? "无" : _errorText), _hintStyle);
GUILayout.EndArea();
return new Rect(outerRect.x + 20f, statusRect.yMax + 12f, outerRect.width - 40f, outerRect.height - 250f);
2026-04-10 22:04:51 +08:00
}
private void DrawBottomButtons(params ButtonSpec[] buttons)
2026-04-10 22:04:51 +08:00
{
var rect = new Rect(40f, DesignHeight - 170f, DesignWidth - 80f, 110f);
GUILayout.BeginArea(rect);
DrawInlineButtons(buttons);
GUILayout.EndArea();
2026-04-10 22:04:51 +08:00
}
private void DrawBottomNav(params NavSpec[] items)
2026-04-10 22:04:51 +08:00
{
var rect = new Rect(30f, DesignHeight - 170f, DesignWidth - 60f, 110f);
GUI.Box(new Rect(rect.x, rect.y, rect.width, rect.height), GUIContent.none, _cardStyle);
GUILayout.BeginArea(new Rect(rect.x + 10f, rect.y + 10f, rect.width - 20f, rect.height - 20f));
GUILayout.BeginHorizontal();
for (var i = 0; i < items.Length; i++)
2026-04-10 22:04:51 +08:00
{
var item = items[i];
if (GUILayout.Button(item.Label, _primaryButtonStyle, GUILayout.Height(64f), GUILayout.Width(150f)))
{
_page = item.Page;
if (item.Page == SamplePage.Announcements)
{
RunAction("加载公告", LoadAnnouncementsAsync);
}
else if (item.Page == SamplePage.Leaderboard)
{
RunAction("加载排行榜", RefreshLeaderboardAsync);
}
else if (item.Page == SamplePage.Archive)
{
RunAction("加载存档", GetArchiveSlotsAsync);
}
else if (item.Page == SamplePage.Space)
{
RunAction("加载空间", LoadMySpaceAsync);
}
}
}
GUILayout.EndHorizontal();
GUILayout.EndArea();
2026-04-10 22:04:51 +08:00
}
private void DrawHeaderButton(string label, Action action)
2026-04-10 22:04:51 +08:00
{
if (GUILayout.Button(label, _buttonStyle, GUILayout.Width(150f), GUILayout.Height(42f)))
2026-04-10 22:04:51 +08:00
{
action();
}
2026-04-10 22:04:51 +08:00
}
private void DrawInlineButtons(params ButtonSpec[] buttons)
2026-04-10 22:04:51 +08:00
{
GUILayout.BeginHorizontal();
for (var i = 0; i < buttons.Length; i++)
2026-04-10 22:04:51 +08:00
{
var button = buttons[i];
var previous = GUI.enabled;
GUI.enabled = previous && !_isBusy && button.Enabled;
var style = button.Primary ? _primaryButtonStyle : _buttonStyle;
if (GUILayout.Button(button.Label, style, GUILayout.Height(56f)))
{
RunAction(button.Label, button.Action);
}
GUI.enabled = previous;
}
2026-04-10 22:04:51 +08:00
GUILayout.EndHorizontal();
2026-04-10 22:04:51 +08:00
}
private void DrawCard(string title, Action content)
2026-04-10 22:04:51 +08:00
{
GUILayout.BeginVertical(_cardStyle);
GUILayout.Label(title, _sectionTitleStyle);
GUILayout.Space(6f);
content();
GUILayout.EndVertical();
GUILayout.Space(12f);
2026-04-10 22:04:51 +08:00
}
private string DrawInputRow(string label, string value)
2026-04-10 22:04:51 +08:00
{
GUILayout.Label(label, _hintStyle);
return GUILayout.TextField(value ?? string.Empty, _inputStyle, GUILayout.Height(48f));
2026-04-10 22:04:51 +08:00
}
private bool DrawToggleRow(string label, bool value)
2026-04-10 22:04:51 +08:00
{
GUILayout.Label(label, _hintStyle);
return GUILayout.Toggle(value, value ? "开启" : "关闭", GUILayout.Height(42f));
}
2026-04-10 22:04:51 +08:00
private void DrawValueRow(string label, string value)
{
GUILayout.BeginHorizontal();
GUILayout.Label(label, _hintStyle, GUILayout.Width(180f));
GUILayout.Label(value, _bodyStyle);
GUILayout.EndHorizontal();
2026-04-10 22:04:51 +08:00
}
private void RunAction(string actionName, Func<Task> action)
{
if (_isBusy)
{
return;
}
RunActionInternal(actionName, action);
2026-04-10 22:04:51 +08:00
}
private async void RunActionInternal(string actionName, Func<Task> action)
2026-04-10 22:04:51 +08:00
{
_isBusy = true;
_busyAction = actionName;
_statusText = "执行中: " + actionName;
_errorText = string.Empty;
Log("开始执行: " + actionName);
2026-04-10 22:04:51 +08:00
try
{
await action();
_statusText = "执行成功: " + actionName;
Log("执行成功: " + actionName);
2026-04-10 22:04:51 +08:00
}
catch (Exception exception)
{
_statusText = "执行失败: " + actionName;
_errorText = FormatException(exception);
Log("执行失败: " + actionName + " | " + exception.Message);
2026-04-10 22:04:51 +08:00
Debug.LogException(exception, this);
}
finally
{
_busyAction = string.Empty;
_isBusy = false;
}
}
private void SyncPageBySdkState()
2026-04-10 22:04:51 +08:00
{
if (!Brisk.IsInitialized)
2026-04-10 22:04:51 +08:00
{
_page = SamplePage.Initialize;
2026-04-10 22:04:51 +08:00
return;
}
_page = Brisk.IsLoggedIn ? SamplePage.Home : SamplePage.Login;
2026-04-10 22:04:51 +08:00
}
private void HandleInitialized()
{
SyncPageBySdkState();
Log("事件: 初始化完成");
2026-04-10 22:04:51 +08:00
}
private void HandleLoggedIn()
{
SyncPageBySdkState();
Log("事件: 登录完成");
2026-04-10 22:04:51 +08:00
}
private void HandleLoggedOut()
{
SyncPageBySdkState();
Log("事件: 登出完成");
2026-04-10 22:04:51 +08:00
}
private void HandleAuthExpired(BriskAuthExpiredException exception)
{
_page = SamplePage.Login;
Log("事件: 登录态失效 | " + exception.Message);
2026-04-10 22:04:51 +08:00
}
private void HandleBlockingError(BriskBlockingException exception)
{
Log("事件: 阻断错误 | " + exception.Message);
2026-04-10 22:04:51 +08:00
}
private void HandleExitRequested()
{
Log("Brisk 请求退出。");
2026-04-10 22:04:51 +08:00
}
private void Log(string message)
{
_logs.Add("[" + DateTime.Now.ToString("HH:mm:ss") + "] " + message);
if (_logs.Count > 80)
2026-04-10 22:04:51 +08:00
{
_logs.RemoveAt(0);
2026-04-10 22:04:51 +08:00
}
}
2026-04-10 22:04:51 +08:00
private BriskProfile CreateProfile()
{
return new BriskProfile
{
Nickname = Nickname,
AvatarUrl = AvatarUrl
};
2026-04-10 22:04:51 +08:00
}
private void ApplyIdentity(string playerId, string loginProvider, string loginUserId)
2026-04-10 22:04:51 +08:00
{
if (!string.IsNullOrWhiteSpace(playerId))
2026-04-10 22:04:51 +08:00
{
SpacePlayerId = playerId;
2026-04-10 22:04:51 +08:00
}
if (!string.IsNullOrWhiteSpace(loginProvider))
{
SpaceLoginProvider = loginProvider;
LoginProvider = loginProvider;
}
if (!string.IsNullOrWhiteSpace(loginUserId))
{
SpaceLoginUserId = loginUserId;
LoginUserId = loginUserId;
}
2026-04-10 22:04:51 +08:00
}
private void EnsureStyles()
2026-04-10 22:04:51 +08:00
{
if (_pageStyle != null)
{
return;
}
_pageStyle = new GUIStyle(GUI.skin.box)
{
padding = new RectOffset(18, 18, 18, 18)
};
_cardStyle = new GUIStyle(GUI.skin.box)
{
padding = new RectOffset(16, 16, 14, 14),
margin = new RectOffset(0, 0, 0, 0)
};
_titleStyle = new GUIStyle(GUI.skin.label)
{
fontSize = 28,
fontStyle = FontStyle.Bold,
alignment = TextAnchor.MiddleCenter,
wordWrap = true
};
_sectionTitleStyle = new GUIStyle(GUI.skin.label)
{
fontSize = 18,
fontStyle = FontStyle.Bold,
wordWrap = true
};
_bodyStyle = new GUIStyle(GUI.skin.label)
{
fontSize = 16,
wordWrap = true
};
_hintStyle = new GUIStyle(GUI.skin.label)
{
fontSize = 14,
wordWrap = true
};
_statusStyle = new GUIStyle(_bodyStyle)
{
fontStyle = FontStyle.Bold
};
_buttonStyle = new GUIStyle(GUI.skin.button)
{
fontSize = 16,
fontStyle = FontStyle.Bold,
wordWrap = true
};
_primaryButtonStyle = new GUIStyle(_buttonStyle);
_dangerButtonStyle = new GUIStyle(_buttonStyle);
_inputStyle = new GUIStyle(GUI.skin.textField) { fontSize = 16 };
_textAreaStyle = new GUIStyle(GUI.skin.textArea) { fontSize = 15, wordWrap = true };
_listButtonStyle = new GUIStyle(GUI.skin.button) { fontSize = 16, alignment = TextAnchor.MiddleLeft, wordWrap = true };
_modalStyle = new GUIStyle(GUI.skin.box) { padding = new RectOffset(20, 20, 20, 20) };
}
private static int ParseRequiredInt(string value, string fieldName)
{
if (!int.TryParse(value, out var result))
2026-04-10 22:04:51 +08:00
{
throw new InvalidOperationException(fieldName + " 必须是合法整数。");
2026-04-10 22:04:51 +08:00
}
return result;
}
private static int ParseOrDefault(string value, int fallback)
2026-04-10 22:04:51 +08:00
{
return int.TryParse(value, out var result) ? result : fallback;
}
private static int? ParseNullableInt(string value)
{
if (string.IsNullOrWhiteSpace(value))
{
return null;
}
return int.TryParse(value, out var result) ? result : (int?)null;
}
private static string NullToDash(string 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)
{
if (view == null || !view.ContentExists || downloadContent == null)
{
return string.Empty;
}
var content = await downloadContent();
if (content == null || content.Bytes == null || content.Bytes.Length == 0)
{
return string.Empty;
}
return Encoding.UTF8.GetString(content.Bytes);
}
2026-04-10 22:04:51 +08:00
private static string FormatException(Exception exception)
{
if (exception == null)
{
return "未知错误";
2026-04-10 22:04:51 +08:00
}
var builder = new StringBuilder();
var current = exception;
while (current != null)
{
builder.AppendLine(current.GetType().Name + ": " + current.Message);
current = current.InnerException;
}
return builder.ToString().TrimEnd();
}
private static string FormatValue(object value)
{
var builder = new StringBuilder();
AppendValue(builder, value, 0, 0);
return builder.Length == 0 ? "-" : builder.ToString().TrimEnd();
2026-04-10 22:04:51 +08:00
}
private static void AppendValue(StringBuilder builder, object value, int indent, int depth)
{
if (depth > 5)
{
builder.AppendLine(new string(' ', indent) + "...");
2026-04-10 22:04:51 +08:00
return;
}
if (value == null)
{
builder.AppendLine(new string(' ', indent) + "null");
2026-04-10 22:04:51 +08:00
return;
}
if (value is string stringValue)
{
builder.AppendLine(new string(' ', indent) + stringValue);
2026-04-10 22:04:51 +08:00
return;
}
if (value is IDictionary dictionary)
{
foreach (DictionaryEntry entry in dictionary)
{
builder.AppendLine(new string(' ', indent) + entry.Key + ":");
2026-04-10 22:04:51 +08:00
AppendValue(builder, entry.Value, indent + 2, depth + 1);
}
return;
}
if (value is IEnumerable enumerable && !(value is byte[]))
2026-04-10 22:04:51 +08:00
{
var index = 0;
foreach (var item in enumerable)
{
builder.AppendLine(new string(' ', indent) + "[" + index + "]");
2026-04-10 22:04:51 +08:00
AppendValue(builder, item, indent + 2, depth + 1);
index++;
}
return;
}
var type = value.GetType();
if (type.IsPrimitive || value is decimal)
2026-04-10 22:04:51 +08:00
{
builder.AppendLine(new string(' ', indent) + value);
2026-04-10 22:04:51 +08:00
return;
}
var fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public);
2026-04-10 22:04:51 +08:00
foreach (var field in fields)
{
builder.AppendLine(new string(' ', indent) + field.Name + ":");
2026-04-10 22:04:51 +08:00
AppendValue(builder, field.GetValue(value), indent + 2, depth + 1);
}
}
private readonly struct ButtonSpec
2026-04-10 22:04:51 +08:00
{
public ButtonSpec(string label, Func<Task> action, bool primary, bool enabled)
{
Label = label;
Action = action;
Primary = primary;
Enabled = enabled;
}
2026-04-10 22:04:51 +08:00
public string Label { get; }
public Func<Task> Action { get; }
public bool Primary { get; }
public bool Enabled { get; }
2026-04-10 22:04:51 +08:00
}
private readonly struct NavSpec
2026-04-10 22:04:51 +08:00
{
public NavSpec(string label, SamplePage page)
{
Label = label;
Page = page;
}
2026-04-10 22:04:51 +08:00
public string Label { get; }
public SamplePage Page { get; }
2026-04-10 22:04:51 +08:00
}
}