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

1370 lines
47 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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("初始化")]
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("登录")]
public string LoginProvider = "tap";
public string LoginUserId = "tap_user_10001";
public string LoginCode = string.Empty;
public string Nickname = "Unity示例玩家";
public string AvatarUrl = string.Empty;
[Header("排行榜")]
public string RankKey = "season-score";
public string SubmitScoreValue = "0";
public string LeaderboardLimit = "10";
public string AroundMeRange = "5";
public string SeasonId = string.Empty;
public string SeasonHistoryLimit = "20";
[Header("云存档")]
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}";
[Header("公告")]
public string AnnouncementId = string.Empty;
[Header("玩家空间")]
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";
[Header("演示")]
public bool AutoRunOnStart;
private readonly List<string> _logs = new List<string>();
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;
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;
private PropertyInfo _defaultDialogVisibleProperty;
private MethodInfo _defaultDialogRenderMethod;
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();
if (AutoRunOnStart)
{
RunAction("自动冒烟", RunSmokeFlowAsync);
}
}
private void OnGUI()
{
if (ShouldRouteInputToDefaultDialog())
{
RenderDefaultErrorDialogTopmost();
return;
}
EnsureStyles();
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();
GUILayout.EndArea();
if (_showAnnouncementModal && _selectedAnnouncement != null)
{
DrawAnnouncementModal();
}
if (_isBusy)
{
DrawBusyOverlay();
}
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()
{
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;
}
}
private void DrawInitializePage()
{
var contentRect = DrawPageFrame("初始化", null, null);
GUILayout.BeginArea(contentRect);
_initScroll = GUILayout.BeginScrollView(_initScroll);
DrawCard("当前配置", () =>
{
BaseUrl = DrawInputRow("服务地址", BaseUrl);
GameKey = DrawInputRow("游戏 Key", GameKey);
ClientVersion = DrawInputRow("客户端版本", ClientVersion);
DeviceId = DrawInputRow("设备标识", DeviceId);
ValidateSessionOnInitialize = DrawToggleRow("初始化时校验旧会话", ValidateSessionOnInitialize);
});
DrawCard("说明", () =>
{
GUILayout.Label("点击“初始化”后会直接调用 Brisk.InitializeAsync。", _bodyStyle);
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.EndScrollView();
GUILayout.EndArea();
DrawBottomButtons(
new ButtonSpec("初始化", InitializeFlowAsync, true, true),
new ButtonSpec("重新填写", ResetTransientStatusAsync, false, true));
}
private void DrawLoginPage()
{
var contentRect = DrawPageFrame("登录", "返回初始化", () => _page = SamplePage.Initialize);
GUILayout.BeginArea(contentRect);
_loginScroll = GUILayout.BeginScrollView(_loginScroll);
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));
}
private void DrawHomePage()
{
var contentRect = DrawPageFrame("主页", "退出登录", () => RunAction("退出登录", LogoutFlowAsync));
GUILayout.BeginArea(contentRect);
_homeScroll = GUILayout.BeginScrollView(_homeScroll);
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));
}
private void DrawAnnouncementsPage()
{
var contentRect = DrawPageFrame("公告", "返回主页", () => _page = SamplePage.Home, "刷新", LoadAnnouncementsAsync);
GUILayout.BeginArea(contentRect);
_announcementScroll = GUILayout.BeginScrollView(_announcementScroll);
if (_announcements.Count == 0)
{
DrawCard("公告列表", () => GUILayout.Label("暂无公告,点击右上角“刷新”获取。", _bodyStyle));
}
else
{
DrawCard("公告列表", () =>
{
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;
}
}
});
}
GUILayout.EndScrollView();
GUILayout.EndArea();
}
private void DrawLeaderboardPage()
{
var contentRect = DrawPageFrame("排行榜", "返回主页", () => _page = SamplePage.Home);
GUILayout.BeginArea(contentRect);
_leaderboardScroll = GUILayout.BeginScrollView(_leaderboardScroll);
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));
});
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));
});
DrawCard("排行榜列表", () =>
{
if (_leaderboardEntries.Count == 0)
{
GUILayout.Label("暂无数据,请先点击“更新列表”。", _bodyStyle);
return;
}
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();
}
private void DrawArchivePage()
{
var contentRect = DrawPageFrame("云存档", "返回主页", () => _page = SamplePage.Home);
GUILayout.BeginArea(contentRect);
_archiveScroll = GUILayout.BeginScrollView(_archiveScroll);
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();
}
private void DrawSpacePage()
{
var title = _viewingTargetSpace ? "TA 的空间" : "我的空间";
var backLabel = _viewingTargetSpace ? "返回我的空间" : "返回主页";
Action backAction = () =>
{
if (_viewingTargetSpace)
{
_viewingTargetSpace = false;
_targetSpaceView = null;
_targetSpaceStats = null;
_targetSpaceContentText = string.Empty;
return;
}
_page = SamplePage.Home;
};
var contentRect = DrawPageFrame(title, backLabel, backAction, "刷新", RefreshSpacePageAsync);
GUILayout.BeginArea(contentRect);
_spaceScroll = GUILayout.BeginScrollView(_spaceScroll);
if (_viewingTargetSpace)
{
DrawTargetSpaceContent();
}
else
{
DrawMySpaceContent();
}
GUILayout.EndScrollView();
GUILayout.EndArea();
}
private void DrawMySpaceContent()
{
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));
});
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);
}
}
});
DrawCard("访问其他用户空间", () =>
{
SpacePlayerId = DrawInputRow("目标用户 PlayerId", SpacePlayerId);
DrawInlineButtons(new ButtonSpec("前往该用户空间", VisitTargetSpaceAsync, true, true));
});
}
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()
{
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();
}
}
private async Task LoginFlowAsync()
{
if (string.IsNullOrWhiteSpace(LoginCode))
{
await Brisk.Auth.LoginWithUserIdAsync(LoginProvider, LoginUserId, CreateProfile());
}
else
{
await Brisk.Auth.LoginWithCodeAsync(LoginProvider, LoginCode, CreateProfile());
}
await RefreshHomeAsync();
_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()
{
_me = await Brisk.Player.GetMeAsync();
_config = await Brisk.Config.GetCurrentAsync();
ApplyIdentity(_me.PlayerId, _me.LoginProvider, _me.LoginUserId);
await LoadMySpaceAsync();
}
private async Task LoadAnnouncementsAsync()
{
_announcements = await Brisk.Announcements.GetListAsync();
if (_announcements.Count > 0)
{
AnnouncementId = _announcements[0].Id.ToString();
}
}
private async Task RefreshLeaderboardAsync()
{
_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));
}
private async Task ToggleLeaderboardModeAsync()
{
_viewAroundMe = !_viewAroundMe;
await RefreshLeaderboardAsync();
}
private async Task IncreaseAndSubmitScoreAsync()
{
var score = ParseOrDefault(SubmitScoreValue, 0) + 1;
SubmitScoreValue = score.ToString();
await Brisk.Leaderboard.SubmitScoreAsync(RankKey, score);
await RefreshLeaderboardAsync();
}
private async Task GetArchiveSlotsAsync()
{
_archiveSlots = await Brisk.Archive.GetSlotsAsync();
}
private async Task GetArchiveMetaAsync()
{
_archiveMeta = await Brisk.Archive.GetMetaAsync(ParseRequiredInt(ArchiveSlotNo, nameof(ArchiveSlotNo)));
ArchiveBaseVersion = _archiveMeta == null ? ArchiveBaseVersion : _archiveMeta.Version.ToString();
}
private async Task DownloadArchiveAsync()
{
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();
}
private async Task LoadMySpaceAsync()
{
if (string.IsNullOrWhiteSpace(Brisk.PlayerId))
{
return;
}
_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));
}
private async Task RefreshSpacePageAsync()
{
if (_viewingTargetSpace)
{
await LoadTargetSpaceAsync();
return;
}
await LoadMySpaceAsync();
}
private async Task UpdateMySpaceAsync()
{
var baseVersion = _mySpaceView != null && _mySpaceView.ContentExists ? _mySpaceView.ContentVersion : 0L;
await Brisk.Space.UpdateMyAsync(SpacePayloadText ?? string.Empty, baseVersion, "text/plain");
await LoadMySpaceAsync();
}
private async Task VisitTargetSpaceAsync()
{
await LoadTargetSpaceAsync();
_viewingTargetSpace = true;
}
private async Task LoadTargetSpaceAsync()
{
_targetSpaceView = await Brisk.Space.GetByPlayerIdAsync(SpacePlayerId);
_targetSpaceStats = await Brisk.Space.GetStatsByPlayerIdAsync(SpacePlayerId);
_targetSpaceContentText = await DownloadSpaceTextAsync(_targetSpaceView, () => Brisk.Space.DownloadContentByPlayerIdAsync(SpacePlayerId));
}
private async Task LikeTargetSpaceAsync()
{
var result = await Brisk.Space.LikeByPlayerIdAsync(SpacePlayerId);
Log("空间今日点赞结果: created=" + result.Created + ", total=" + result.LikeCount + ", today=" + result.TodayLikeCount);
await LoadTargetSpaceAsync();
}
private async Task UnlikeTargetSpaceAsync()
{
var result = await Brisk.Space.UnlikeByPlayerIdAsync(SpacePlayerId);
Log("空间撤销今日点赞结果: total=" + result.LikeCount + ", today=" + result.TodayLikeCount);
await LoadTargetSpaceAsync();
}
private async Task LogoutFlowAsync()
{
await Brisk.Auth.LogoutAsync();
_page = SamplePage.Login;
}
private Task ResetTransientStatusAsync()
{
_statusText = "等待开始";
_errorText = string.Empty;
return Task.CompletedTask;
}
private Task ShowHomeLogsAsync()
{
_errorText = string.Join("\n", _logs.ToArray());
return Task.CompletedTask;
}
private async Task RunSmokeFlowAsync()
{
await InitializeFlowAsync();
if (!Brisk.IsLoggedIn)
{
await LoginFlowAsync();
}
}
private void DrawAnnouncementModal()
{
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();
}
private Task CloseAnnouncementModalAsync()
{
_showAnnouncementModal = false;
return Task.CompletedTask;
}
private async Task MarkSelectedAnnouncementAsync()
{
if (_selectedAnnouncement == null)
{
return;
}
await Brisk.Announcements.MarkReadAsync(_selectedAnnouncement.Id);
_selectedAnnouncement.IsRead = true;
await LoadAnnouncementsAsync();
_showAnnouncementModal = false;
}
private void DrawBusyOverlay()
{
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);
}
private Rect DrawPageFrame(string title, string backLabel, Action backAction, string rightLabel = null, Func<Task> rightAction = null)
{
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);
}
private void DrawBottomButtons(params ButtonSpec[] buttons)
{
var rect = new Rect(40f, DesignHeight - 170f, DesignWidth - 80f, 110f);
GUILayout.BeginArea(rect);
DrawInlineButtons(buttons);
GUILayout.EndArea();
}
private void DrawBottomNav(params NavSpec[] items)
{
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++)
{
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();
}
private void DrawHeaderButton(string label, Action action)
{
if (GUILayout.Button(label, _buttonStyle, GUILayout.Width(150f), GUILayout.Height(42f)))
{
action();
}
}
private void DrawInlineButtons(params ButtonSpec[] buttons)
{
GUILayout.BeginHorizontal();
for (var i = 0; i < buttons.Length; i++)
{
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;
}
GUILayout.EndHorizontal();
}
private void DrawCard(string title, Action content)
{
GUILayout.BeginVertical(_cardStyle);
GUILayout.Label(title, _sectionTitleStyle);
GUILayout.Space(6f);
content();
GUILayout.EndVertical();
GUILayout.Space(12f);
}
private string DrawInputRow(string label, string value)
{
GUILayout.Label(label, _hintStyle);
return GUILayout.TextField(value ?? string.Empty, _inputStyle, GUILayout.Height(48f));
}
private bool DrawToggleRow(string label, bool value)
{
GUILayout.Label(label, _hintStyle);
return GUILayout.Toggle(value, value ? "开启" : "关闭", GUILayout.Height(42f));
}
private void DrawValueRow(string label, string value)
{
GUILayout.BeginHorizontal();
GUILayout.Label(label, _hintStyle, GUILayout.Width(180f));
GUILayout.Label(value, _bodyStyle);
GUILayout.EndHorizontal();
}
private void RunAction(string actionName, Func<Task> action)
{
if (_isBusy)
{
return;
}
RunActionInternal(actionName, action);
}
private async void RunActionInternal(string actionName, Func<Task> action)
{
_isBusy = true;
_busyAction = actionName;
_statusText = "执行中: " + actionName;
_errorText = string.Empty;
Log("开始执行: " + actionName);
try
{
await action();
_statusText = "执行成功: " + actionName;
Log("执行成功: " + actionName);
}
catch (Exception exception)
{
_statusText = "执行失败: " + actionName;
_errorText = FormatException(exception);
Log("执行失败: " + actionName + " | " + exception.Message);
Debug.LogException(exception, this);
}
finally
{
_busyAction = string.Empty;
_isBusy = false;
}
}
private void SyncPageBySdkState()
{
if (!Brisk.IsInitialized)
{
_page = SamplePage.Initialize;
return;
}
_page = Brisk.IsLoggedIn ? SamplePage.Home : SamplePage.Login;
}
private void HandleInitialized()
{
SyncPageBySdkState();
Log("事件: 初始化完成");
}
private void HandleLoggedIn()
{
SyncPageBySdkState();
Log("事件: 登录完成");
}
private void HandleLoggedOut()
{
SyncPageBySdkState();
Log("事件: 登出完成");
}
private void HandleAuthExpired(BriskAuthExpiredException exception)
{
_page = SamplePage.Login;
Log("事件: 登录态失效 | " + exception.Message);
}
private void HandleBlockingError(BriskBlockingException exception)
{
Log("事件: 阻断错误 | " + exception.Message);
}
private void HandleExitRequested()
{
Log("Brisk 请求退出。");
}
private void Log(string message)
{
_logs.Add("[" + DateTime.Now.ToString("HH:mm:ss") + "] " + message);
if (_logs.Count > 80)
{
_logs.RemoveAt(0);
}
}
private BriskProfile CreateProfile()
{
return new BriskProfile
{
Nickname = Nickname,
AvatarUrl = AvatarUrl
};
}
private void ApplyIdentity(string playerId, string loginProvider, string loginUserId)
{
if (!string.IsNullOrWhiteSpace(playerId))
{
SpacePlayerId = playerId;
}
if (!string.IsNullOrWhiteSpace(loginProvider))
{
SpaceLoginProvider = loginProvider;
LoginProvider = loginProvider;
}
if (!string.IsNullOrWhiteSpace(loginUserId))
{
SpaceLoginUserId = loginUserId;
LoginUserId = loginUserId;
}
}
private void EnsureStyles()
{
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))
{
throw new InvalidOperationException(fieldName + " 必须是合法整数。");
}
return result;
}
private static int ParseOrDefault(string value, int fallback)
{
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);
}
private static string FormatException(Exception exception)
{
if (exception == null)
{
return "未知错误";
}
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();
}
private static void AppendValue(StringBuilder builder, object value, int indent, int depth)
{
if (depth > 5)
{
builder.AppendLine(new string(' ', indent) + "...");
return;
}
if (value == null)
{
builder.AppendLine(new string(' ', indent) + "null");
return;
}
if (value is string stringValue)
{
builder.AppendLine(new string(' ', indent) + stringValue);
return;
}
if (value is IDictionary dictionary)
{
foreach (DictionaryEntry entry in dictionary)
{
builder.AppendLine(new string(' ', indent) + entry.Key + ":");
AppendValue(builder, entry.Value, indent + 2, depth + 1);
}
return;
}
if (value is IEnumerable enumerable && !(value is byte[]))
{
var index = 0;
foreach (var item in enumerable)
{
builder.AppendLine(new string(' ', indent) + "[" + index + "]");
AppendValue(builder, item, indent + 2, depth + 1);
index++;
}
return;
}
var type = value.GetType();
if (type.IsPrimitive || value is decimal)
{
builder.AppendLine(new string(' ', indent) + value);
return;
}
var fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public);
foreach (var field in fields)
{
builder.AppendLine(new string(' ', indent) + field.Name + ":");
AppendValue(builder, field.GetValue(value), indent + 2, depth + 1);
}
}
private readonly struct ButtonSpec
{
public ButtonSpec(string label, Func<Task> action, bool primary, bool enabled)
{
Label = label;
Action = action;
Primary = primary;
Enabled = enabled;
}
public string Label { get; }
public Func<Task> Action { get; }
public bool Primary { get; }
public bool Enabled { get; }
}
private readonly struct NavSpec
{
public NavSpec(string label, SamplePage page)
{
Label = label;
Page = page;
}
public string Label { get; }
public SamplePage Page { get; }
}
}