commit b0e48bcd8f417ec3bb1895f56c7b6bd49d29384a Author: ci-gitlab Date: Thu Nov 2 19:51:01 2023 +0800 feat:update upm diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..9fea556 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,260 @@ +# ChangeLog +## 3.11.1 +- TapTap.Login v3.11.1 +- TapTap.Common v3.11.1 +- LeanCloud.Realtime v0.10.11 +- LeanCloud.Storage v0.10.11 + +## 3.11.0 + +### Dependencies + +- TapTap.Login v3.11.0 +- TapTap.Common v3.11.0 +- LeanCloud.Realtime v0.10.11 +- LeanCloud.Storage v0.10.11 + +## 3.10.0 + +### New Feature +- 支持排行榜 + +### Dependencies + +- TapTap.Login v3.10.0 +- TapTap.Common v3.10.0 +- LeanCloud.Realtime v0.10.11 +- LeanCloud.Storage v0.10.11 + +## 3.9.0 + +### Dependencies + +- TapTap.Login v3.9.0 +- TapTap.Common v3.9.0 +- LeanCloud.Realtime v0.10.11 +- LeanCloud.Storage v0.10.11 + +## 3.8.0 + +### Dependencies + +- TapTap.Login v3.8.0 +- TapTap.Common v3.8.0 +- LeanCloud.Realtime v0.10.10 +- LeanCloud.Storage v0.10.10 + +## 3.7.1 + +### Dependencies + +- TapTap.Login v3.7.1 +- TapTap.Common v3.7.1 +- LeanCloud.Realtime v0.10.0 +- LeanCloud.Storage v0.10.0 + +## 3.7.0 + +### Dependencies + +- TapTap.Login v3.7.0 +- TapTap.Common v3.7.0 +- LeanCloud.Realtime v0.10.0 +- LeanCloud.Storage v0.10.0 + + +## 3.6.3 + +### Dependencies + +- TapTap.Login v3.6.3 +- TapTap.Common v3.6.3 +- LeanCloud.Realtime v0.10.0 +- LeanCloud.Storage v0.10.0 + +## 3.6.1 + +### Dependencies + +- TapTap.Login v3.6.1 +- TapTap.Common v3.6.1 +- LeanCloud.Realtime v0.10.0 +- LeanCloud.Storage v0.10.0 + +## 3.6.0 + +### Dependencies + +- TapTap.Login v3.6.0 +- TapTap.Common v3.6.0 +- LeanCloud.Realtime v0.10.0 +- LeanCloud.Storage v0.10.0 + +## 3.5.0 + +### Dependencies + +- TapTap.Login v3.5.0 +- TapTap.Common v3.5.0 +- LeanCloud.Realtime v0.10.0 +- LeanCloud.Storage v0.10.0 + +## 3.4.0 + +### Dependencies + +- TapTap.Login v3.4.0 +- TapTap.Common v3.4.0 +- LeanCloud.Realtime v0.10.0 +- LeanCloud.Storage v0.10.0 + +## 3.3.0 + +### Dependencies + +- TapTap.Login v3.3.0 +- TapTap.Common v3.3.0 +- LeanCloud.Realtime v0.9.11 +- LeanCloud.Storage v0.9.11 + +## 3.2.0 + +### New Feature + +- 支持云存档 + +### Dependencies + +- TapTap.Login v3.2.0 +- TapTap.Common v3.2.0 +- LeanCloud.Storage v0.9.5 +- LeanCloud.Realtime v0.9.5 + +## 3.1.0 + +### New Feature + +- `TDSUser` 新增好友系统操作 + +### Dependencies + +- TapTap.Login v3.1.0 +- TapTap.Common v3.1.0 +- LeanCloud.Storage v0.9.2 +- LeanCloud.Realtime v0.9.2 + +## 3.0.0 + +TapSDK 3.0 开始,我们在单纯的 TapTap 登录之外,还提供了一个内建账户系统供游戏使用:开发者可以直接用 TapTap OAuth +授权的结果生成一个游戏内的账号(TDSUser),然后用该账号保存更多玩家数据。同时,我们也支持将更多第三方认证登录的结果绑定到这一账号上来(以及后续的解绑操作)。 + +### New Feature + +- 新增 `TDSUser` 用于内建账户系统操作 + +### BreakingChange + +- `TapBootstrap` 接口仅保留 `TapBootstrap.Init(tapConfig)` 接口 + +### Dependencies + +- LeanCloud.Storage v0.8.2 +- TapTap.Login v3.0.0 +- TapTap.Common v3.0.0 + +## 2.1.7 + +### Dependencies + +- TapTap.Common v2.1.7 + +## 2.1.6 + +### Dependencies + +- TapTap.Common v2.1.6 + +## 2.1.5 + +### Dependencies + +- TapTap.Common v2.1.5 + +## 2.1.4 + +### Optimization and fixed bugs + +- 优化多语言相关 + +### Dependencies + +- TapTap.Common v2.1.4 + +## 2.1.3 + +### Feature + +* 新增繁中、日文、韩文、泰文和印尼语多语言配置 + +## 2.1.2 + +### BreakingChange + +* 废弃 OpenUserCenter 接口 + +### Dependencies + +* TapTap.Common v2.1.2 + +## 2.1.1 + +### Feature + +* 新增篝火测试资格校验 + ``` + TapBootstrap.GetTestQualification((bool, error)=>{ }): + ``` +* 通过 TapConfig 进行初始化配置 + * 新增 TapDBConfig 用于 TapDB 初始化配置 + * 新增 ClientSecret 用于 TapSDK 初始化 + ```c# + //建议使用以下 TapConfig 构造方法进行初始化 + var config = new TapConfig.Builder() + .ClientID("client_id") + .ClientSecret("client_secret") + .RegionType(RegionType.CN) + .TapDBConfig(true, "gameChannel", "gameVersion", true) + .ConfigBuilder(); + TapBootstrap.Init(config); + ``` + +### Breaking changes + +* LoginType 删除 Apple、Guest 登陆方式 +* TDS-Info.plist 删除 Apple_SignIn_Enable 配置 +* 废弃 Bind 接口 +* TapConfig 构造方法参数修改 + +### Dependencies + +* TapTap.Common v2.1.1 + +## 2.1.0 + +### Feature + +* 支持性改动用于 TapTap.Friends + +### Dependencies + +* TapTap.Common v2.1.0 + +## 2.0.0 + +### Feature + +* TapTap Bootstrap + +### Dependencies + +* TapTap.Common v2.0.0 \ No newline at end of file diff --git a/CHANGELOG.md.meta b/CHANGELOG.md.meta new file mode 100644 index 0000000..419383c --- /dev/null +++ b/CHANGELOG.md.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 7388165be5344a9682382768c5fce466 +timeCreated: 1616755935 \ No newline at end of file diff --git a/Documentation.meta b/Documentation.meta new file mode 100644 index 0000000..1130a03 --- /dev/null +++ b/Documentation.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: bd03b50332353477c899419b5b455fb0 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Documentation/README.md b/Documentation/README.md new file mode 100644 index 0000000..2564c0c --- /dev/null +++ b/Documentation/README.md @@ -0,0 +1,257 @@ +# 使用 TapTap.Bootstrap + +## 使用前提 + +使用 TapTap.Bootstrap 前提是必须依赖以下库: +* [TapTap.Common](https://github.com/TapTap/TapCommon-Unity.git) +* [TapTap.Login](https://github.com/TapTap/TapLogin-Unity.git) +* [LeanCloud.Storage](https://github.com/leancloud/csharp-sdk) +* [LeanCloud.RealTime](https://github.com/leancloud/csharp-sdk) + +## 命名空间 + +```c# +using TapTap.Bootstrap; +``` + +## 接口描述 + +## 1.初始化 + +TapBootstrap 会根据 TapConfig 中的 TapDBConfig 配置来进行 TapDB 的自动初始化。 + +### 开启 TapDB +```c# +var config = new TapConfig.Builder() + .ClientID("client_id") + .ClientToken("client_token") + .ServerURL("https://ikggdre2.lc-cn-n1-shared.com") + .RegionType(RegionType.CN) + .TapDBConfig(true,"channel","gameVersion",true) + .Builder(); +``` +### 关闭 TapDB +```c# +var config = new TapConfig.Builder() + .ClientID("client_id") + .ClientToken("client_token") + .ServerURL("https://ikggdre2.lc-cn-n1-shared.com") + .RegionType(RegionType.CN) +//# .TapDBConfig(false,null,null,false) + .EnableTapDB(false) + .Builder(); +``` +### 初始化 +```c# +TapBootstrap.Init(config); +``` + +## 2.账户系统 + +> 登陆成功之后,都会得到一个 `TDSUser` 实例 + +### 使用 TapTap OAuth 授权结果直接登陆/注册账户系统 + +```c# +var tdsUser = await TDSUser.LoginWithTapTap(); +``` + +### 游客登陆 + +```c# +var tdsUser = await TDSUser.LoginAnonymously(); +``` + +### 使用第三方平台授权登录/注册账户 + +```c# +var tdsUser = await TDSUser.LoginWithAuthData(Dictionary authData, string platform, +LCUserAuthDataLoginOption option = null); +``` + +### 绑定第三方平台授权 + +```c# +await TDSUser.AssociateAuthData(Dictionary authData, string platform); +``` + +### 退出登陆 + +```c# +TDSUser.Logout(); +``` + +## 3.好友系统 + +### 申请成为好友 + +```c# +TDSUser tom, jerry; +await tom.ApplyFriendship(jerry); +``` +申请成功的回调中,我们会得到一个 LCFriendshipRequest 的实例,这个实例中包含了两个用户: +- sourceUser,指请求的发起方,上面的例子中就是 `tom`。 +- friend,指请求的目的方,上面的例子中就是 `jerry`。 + +tom 也可以在申请好友的时候,添加更多的属性,例如 tom 希望加 jerry 为好友的时候,也设定一个名为 cat 的圈子,可以这样操作: + +```cs +Dictionary attrs = new Dictionary { + { "group", "cat" } +}; +await tom.ApplyFriendship(jerry, attrs); +``` + +### 获取好友申请列表 +好友申请有三种状态: + +- `pending`,对方没有回应,还处于等待中。 +- `accepted`,对方已经接受,现在双方成为好友。 +- `declined`,对方已经拒绝。 + +好友请求创建之后默认是 `pending` 状态。 + +jerry 这里可以通过 `friendshipRequestQuery` 来查找不同状态的请求。例如 jerry 想看看新的好友请求,可以这样操作: + +```cs +LCQuery query = jerry.GetFriendshipRequestQuery(LCFriendshipRequest.STATUS_PENDING, false, true); +ReadOnlyCollection reqs = await query.Find(); +foreach (LCFriendshipRequest req in reqs) { + Console.WriteLine(req); +} +``` + +### 处理好友申请 + +jerry 对于新的好友请求,可以同意或者拒绝,也可以什么都不做,无视这些请求,甚至直接删除。这些操作我们都是支持的,请看下面的示例: + +```cs +LCFriendshipRequest tomRequest, tuffyRequest, otherRequest; +await jerry.AcceptFriendshipRequest(tomRequest); +await jerry.DeclineFriendshipRequest(tuffyRequest); +await jerry.DeleteFriendshipRequest(otherRequest); +``` + +注意: +* 在 jerry 拒绝了 tom 的好友请求之后,如果 tom 再次请求成为 jerry 的好友,tom 在执行 applyFriendshipInBackground 时会直接得到错误的应答,表明 jerry 不想和 ta 成为好友。 +* jerry 同意了 tuffy 的好友请求之后,它们就成为了好友,之后两个人中任何一人再次调用 applyFriendshipInBackground 申请横位好友时,也会直接得到错误的应答,表明它们已经是好友无需再次申请。 +* jerry 删除陌生人的好友请求后,对方还可以再次发起请求。 + +### 响应好友变化通知 + +TDS 好友模块支持客户端监听好友状态变化,在游戏中实时给玩家提示。好友状态变化的接口包括 + +```cs +public class FriendshipNotification { + public Action OnNewRequestComing { get; set; } + public Action OnRequestAccepted { get; set; } + public Action OnRequestDeclined { get; set; } +} +``` + +其中: +- onNewRequestComing 表示有其他人申请成为当前用户的好友,通过调用 `LCFriendshipRequest#getSourceUser()` 方法可以获得发起方用户信息。 +- onRequestAccepted 表示当前用户的好友申请被对方通过,通过调用 `LCFriendshipRequest#getFriend()` 方法可以获得对方用户信息。 +- onRequestDeclined 表示当前用户的好友申请被对方拒绝,通过调用 `LCFriendshipRequest#getFriend()` 方法可以获得对方用户信息。 + +开发者可以通过 `TDSUser#registerFriendshipNotification` 来注册通知接收器,通过调用 `TDSUser#unregisterFriendshipNotification` 来取消通知接收器。 + +### 获取好友列表 + +调用 `TDSUser#friendshipQuery()` 可以得到查询好友的 `LCQuery` 实例,之后调用 `LCQuery#findInBackground()` 方法就可以得到好友列表。示例如下: + +```cs +LCQuery query = jerry.GetFirendshipQuery(); +``` + +LCFriendship 里面会包含两个用户: + +- `LCFriendship#getLCUser(LCFriendship.ATTR_USER)` 得到的是 jerry 自己; +- `LCFriendship#getLCUser(LCFriendship.ATTR_FOLLOWEE)` 得到的就是另一方的用户信息。 + +### 删除好友 + +成为好友关系的两个用户,之后也可以单方面删除好友。例如 jerry 不想再和 tom 成为好友,那只需要在自己的好友列表中删除包含 tom 的那条 LCFriendship 记录即可: + +```cs +await friendship.Delete(); +``` + +### 查询好友关系 + +我们使用 LCQuery 可以单独查询两个用户是否为好友关系。 + +```cs +LCQuery query = jerry.GetFirendshipQuery(); +query.whereEqualTo("followee", tom); +int count = await query.Count(); +if (count > 0) { + // tom is a friend of jerry. +} else { + // tom isn't a friend of jerry. +} +``` + +这一查询是通过网络发送到服务端执行的,一般情况下,我们推荐开发者在游戏启动时拉取一次当前登录用户的好友列表,然后缓存在本地,以后需要检查另外玩家是否为当前用户的好友时,直接从缓存中查询即可。如果担心好友数据变化,缓存没有得到及时更新,可以调用前面「响应好友变化通知」的方法,对好友数据更新进行监听,这样在绝大部分时候数据同步都是可以保证的。 + +## 4.云存档 + +### 构建云存档元数据 + +```c# +var gameSave = new TapGameSave +{ + Name = "GameSave_Name",// 存档名称 + Summary = "GameSave_Description", // 该字段会作为展示给用户的实际存档名 + ModifiedAt = DateTime.Now.ToLocalTime(), // 原文件修改时间 + PlayedTime = 1000L, // 游戏时长,单位 ms (非必填) + ProgressValue = 100, // 游戏进度 ,单位 int (非必填) + CoverFilePath = pic, // 游戏封面,可以传入一个本地文件路径,SDK 限制为 png/jpeg 格式 + GameFilePath = dll // 存档源文件,可以传入一个本地文件路径 +}; + +``` +### 保存存档 + +保存存档时,会去检查当前`TDSUser`是否已经登录以及元数据。 + +同时 SDK 在上传时会去限制存档本身以及相关联的两个文件( Cover 以及 GameFile )的权限为当前用户本身。 +```c# +await gameSave.Save(); +``` + +### 查询当前用户的所有存档 + +```c# +var collection = await TapGameSave.GetCurrentUserGameSaves(); + +foreach(var gameSave in collection){ + // 存档概览 + var name = gameSave.Summary; + // 原文件修改时间 + var modifiedAt = gameSave.ModifiedAt; + // 游戏时长 + var playedTime = gameSave.PlayedTime; + // 游戏进度 + var progressValue = gameSave.ProgressValue; + // 游戏封面 + var coverFile = gameSave.CoverFile; + // 存档源文件 + var gameFile = gameSave.GameFile; + // 源文件下载地址 + var gameFileUrl = gameFile.Url; +} +``` + +### 查询当前用户存档 + +我们使用 `LCQuery` 来查询当前用户的云存档。 + +SDK 查询封装了一个限定方法用于查询当前`TDSUser`的云存档。 +```c# +TDSUser user = await TDSUser.GetCurrent(); +LCQuery gameSaveQuery = TapGameSave.GetQueryWithUser(user); +// 查询 Name 为 TDSUser_GameSave_Name 的云存档 +gameSaveQuery.WhereEqualTo("name","TDSUser_GameSave_Name"); +var collection = await gameSaveQuery.Find(); +``` \ No newline at end of file diff --git a/Documentation/README.md.meta b/Documentation/README.md.meta new file mode 100644 index 0000000..125a6de --- /dev/null +++ b/Documentation/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 17ed6166c017a4fcda77ff618f2703db +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor.meta b/Editor.meta new file mode 100644 index 0000000..53d66be --- /dev/null +++ b/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 92ca05cc6fb3247f79cddb7f5ef08c2a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/TapBootstrapProcessBuild.cs b/Editor/TapBootstrapProcessBuild.cs new file mode 100644 index 0000000..c328cff --- /dev/null +++ b/Editor/TapBootstrapProcessBuild.cs @@ -0,0 +1,20 @@ +using System; +using UnityEditor.Build.Reporting; +using TapTap.Common.Editor; + +namespace TapTap.Bootstrap.Editor { + public class TapBootstrapProcessBuild : SDKLinkProcessBuild { + public override int callbackOrder => 0; + + public override string LinkPath => "TapTap/Bootstrap/link.xml"; + + public override LinkedAssembly[] LinkedAssemblies => new LinkedAssembly[] { + new LinkedAssembly { Fullname = "TapTap.Bootstrap.Runtime" }, + }; + + public override Func IsTargetPlatform => (report) => { + return BuildTargetUtils.IsSupportMobile(report.summary.platform) || + BuildTargetUtils.IsSupportStandalone(report.summary.platform); + }; + } +} diff --git a/Editor/TapBootstrapProcessBuild.cs.meta b/Editor/TapBootstrapProcessBuild.cs.meta new file mode 100644 index 0000000..6bafa2e --- /dev/null +++ b/Editor/TapBootstrapProcessBuild.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 395365d4e25e54d7cb6a461aa19b50c0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/TapTap.Bootstrap.Editor.asmdef b/Editor/TapTap.Bootstrap.Editor.asmdef new file mode 100644 index 0000000..772a063 --- /dev/null +++ b/Editor/TapTap.Bootstrap.Editor.asmdef @@ -0,0 +1,17 @@ +{ + "name": "TapTap.Bootstrap.Editor", + "references": [ + "GUID:616cea76def2d4f059b94440fc8cc03d" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Editor/TapTap.Bootstrap.Editor.asmdef.meta b/Editor/TapTap.Bootstrap.Editor.asmdef.meta new file mode 100644 index 0000000..abf9395 --- /dev/null +++ b/Editor/TapTap.Bootstrap.Editor.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 3cfcce84a61c740fc86ea125ab9c81e0 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/README.md b/README.md new file mode 100644 index 0000000..ac2eea9 --- /dev/null +++ b/README.md @@ -0,0 +1,254 @@ +# [使用 TapTap.Bootstrap ](./Documentation/README.md) + +## 使用前提 + +使用 TapTap.Bootstrap 前提是必须依赖以下库: +* [TapTap.Common](https://github.com/TapTap/TapCommon-Unity.git) +* [TapTap.Login](https://github.com/TapTap/TapLogin-Unity.git) +* [LeanCloud.Storage](https://github.com/leancloud/csharp-sdk) +* [LeanCloud.RealTime](https://github.com/leancloud/csharp-sdk) + +## 命名空间 + +```c# +using TapTap.Bootstrap; +``` + +## 接口描述 + +## 1.初始化 + +TapBootstrap 会根据 TapConfig 中的 TapDBConfig 配置来进行 TapDB 的自动初始化。 + +### 开启 TapDB +```c# +var config = new TapConfig.Builder() + .ClientID("client_id") + .ClientToken("client_token") + .ServerURL("https://ikggdre2.lc-cn-n1-shared.com") + .RegionType(RegionType.CN) + .TapDBConfig(true,"channel","gameVersion",true) + .Builder(); +``` +### 关闭 TapDB +```c# +var config = new TapConfig.Builder() + .ClientID("client_id") + .ClientToken("client_token") + .ServerURL("https://ikggdre2.lc-cn-n1-shared.com") + .RegionType(RegionType.CN) +//# .TapDBConfig(false,null,null,false) + .EnableTapDB(false) + .Builder(); +``` +### 初始化 +```c# +TapBootstrap.Init(config); +``` + +## 2.账户系统 + +> 登陆成功之后,都会得到一个 `TDSUser` 实例 + +### 使用 TapTap OAuth 授权结果直接登陆/注册账户系统 + +```c# +var tdsUser = await TDSUser.LoginWithTapTap(); +``` + +### 游客登陆 + +```c# +var tdsUser = await TDSUser.LoginAnonymously(); +``` + +### 使用第三方平台授权登录/注册账户 + +```c# +var tdsUser = await TDSUser.LoginWithAuthData(Dictionary authData, string platform, +LCUserAuthDataLoginOption option = null); +``` + +### 绑定第三方平台授权 + +```c# +await TDSUser.AssociateAuthData(Dictionary authData, string platform); +``` + +### 退出登陆 + +```c# +TDSUser.Logout(); +``` + +## 3.好友系统 + +### 申请成为好友 + +```c# +TDSUser tom, jerry; +await tom.ApplyFriendship(jerry); +``` +申请成功的回调中,我们会得到一个 LCFriendshipRequest 的实例,这个实例中包含了两个用户: +- sourceUser,指请求的发起方,上面的例子中就是 `tom`。 +- friend,指请求的目的方,上面的例子中就是 `jerry`。 + +tom 也可以在申请好友的时候,添加更多的属性,例如 tom 希望加 jerry 为好友的时候,也设定一个名为 cat 的圈子,可以这样操作: + +```cs +Dictionary attrs = new Dictionary { + { "group", "cat" } +}; +await tom.ApplyFriendship(jerry, attrs); +``` + +### 获取好友申请列表 +好友申请有三种状态: + +- `pending`,对方没有回应,还处于等待中。 +- `accepted`,对方已经接受,现在双方成为好友。 +- `declined`,对方已经拒绝。 + +好友请求创建之后默认是 `pending` 状态。 + +jerry 这里可以通过 `friendshipRequestQuery` 来查找不同状态的请求。例如 jerry 想看看新的好友请求,可以这样操作: + +```cs +LCQuery query = jerry.GetFriendshipRequestQuery(LCFriendshipRequest.STATUS_PENDING, false, true); +ReadOnlyCollection reqs = await query.Find(); +foreach (LCFriendshipRequest req in reqs) { + Console.WriteLine(req); +} +``` + +### 处理好友申请 + +jerry 对于新的好友请求,可以同意或者拒绝,也可以什么都不做,无视这些请求,甚至直接删除。这些操作我们都是支持的,请看下面的示例: + +```cs +LCFriendshipRequest tomRequest, tuffyRequest, otherRequest; +await jerry.AcceptFriendshipRequest(tomRequest); +await jerry.DeclineFriendshipRequest(tuffyRequest); +await jerry.DeleteFriendshipRequest(otherRequest); +``` + +注意: +* 在 jerry 拒绝了 tom 的好友请求之后,如果 tom 再次请求成为 jerry 的好友,tom 在执行 applyFriendshipInBackground 时会直接得到错误的应答,表明 jerry 不想和 ta 成为好友。 +* jerry 同意了 tuffy 的好友请求之后,它们就成为了好友,之后两个人中任何一人再次调用 applyFriendshipInBackground 申请横位好友时,也会直接得到错误的应答,表明它们已经是好友无需再次申请。 +* jerry 删除陌生人的好友请求后,对方还可以再次发起请求。 + +### 响应好友变化通知 + +TDS 好友模块支持客户端监听好友状态变化,在游戏中实时给玩家提示。好友状态变化的接口包括 + +```cs +public class FriendshipNotification { + public Action OnNewRequestComing { get; set; } + public Action OnRequestAccepted { get; set; } + public Action OnRequestDeclined { get; set; } +} +``` + +其中: +- onNewRequestComing 表示有其他人申请成为当前用户的好友,通过调用 `LCFriendshipRequest#getSourceUser()` 方法可以获得发起方用户信息。 +- onRequestAccepted 表示当前用户的好友申请被对方通过,通过调用 `LCFriendshipRequest#getFriend()` 方法可以获得对方用户信息。 +- onRequestDeclined 表示当前用户的好友申请被对方拒绝,通过调用 `LCFriendshipRequest#getFriend()` 方法可以获得对方用户信息。 + +开发者可以通过 `TDSUser#registerFriendshipNotification` 来注册通知接收器,通过调用 `TDSUser#unregisterFriendshipNotification` 来取消通知接收器。 + +### 获取好友列表 + +调用 `TDSUser#friendshipQuery()` 可以得到查询好友的 `LCQuery` 实例,之后调用 `LCQuery#findInBackground()` 方法就可以得到好友列表。示例如下: + +```cs +LCQuery query = jerry.GetFirendshipQuery(); +``` + +LCFriendship 里面会包含两个用户: + +- `LCFriendship#getLCUser(LCFriendship.ATTR_USER)` 得到的是 jerry 自己; +- `LCFriendship#getLCUser(LCFriendship.ATTR_FOLLOWEE)` 得到的就是另一方的用户信息。 + +### 删除好友 + +成为好友关系的两个用户,之后也可以单方面删除好友。例如 jerry 不想再和 tom 成为好友,那只需要在自己的好友列表中删除包含 tom 的那条 LCFriendship 记录即可: + +```cs +await friendship.Delete(); +``` + +### 查询好友关系 + +我们使用 LCQuery 可以单独查询两个用户是否为好友关系。 + +```cs +LCQuery query = jerry.GetFirendshipQuery(); +query.whereEqualTo("followee", tom); +int count = await query.Count(); +if (count > 0) { + // tom is a friend of jerry. +} else { + // tom isn't a friend of jerry. +} +``` + +这一查询是通过网络发送到服务端执行的,一般情况下,我们推荐开发者在游戏启动时拉取一次当前登录用户的好友列表,然后缓存在本地,以后需要检查另外玩家是否为当前用户的好友时,直接从缓存中查询即可。如果担心好友数据变化,缓存没有得到及时更新,可以调用前面「响应好友变化通知」的方法,对好友数据更新进行监听,这样在绝大部分时候数据同步都是可以保证的。 + +## 4.云存档 + +### 构建云存档元数据 + +```c# +var gameSave = new TapGameSave +{ + Name = "GameSave_Name",// 存档名称 + Summary = "GameSave_Description", // 该字段会作为展示给用户的实际存档名 + ModifiedAt = DateTime.Now.ToLocalTime(), // 原文件修改时间 + PlayedTime = 1000L, // 游戏时长,单位 ms (非必填) + ProgressValue = 100, // 游戏进度 ,单位 int (非必填) + CoverFilePath = pic, // 游戏封面,可以传入一个本地文件路径,SDK 限制为 png/jpeg 格式 + GameFilePath = dll // 存档源文件,可以传入一个本地文件路径 +}; + +``` +### 保存存档 + +保存存档时,会去检查当前`TDSUser`是否已经登录以及元数据。 + +同时 SDK 在上传时会去限制存档本身以及相关联的两个文件( Cover 以及 GameFile )的权限为当前用户本身。 +```c# +await gameSave.Save(); +``` + +### 查询当前用户的所有存档 + +```c# +var collection = await TapGameSave.GetCurrentUserGameSaves(); + +foreach(var gameSave in collection){ + // 存档概览 + var name = gameSave.Summary; + // 原文件修改时间 + var modifiedAt = gameSave.ModifiedAt; + // 游戏时长 + var playedTime = gameSave.PlayedTime; + // 游戏进度 + var progressValue = gameSave.ProgressValue; + // 游戏封面 + var coverFile = gameSave.CoverFile; + // 存档源文件 + var gameFile = gameSave.GameFile; + // 源文件下载地址 + var gameFileUrl = gameFile.Url; +} +``` + +### 查询云存档 +云存档只能查询当前用户的所有存档,需要查询该用户的特定存档时,可以增加相应过滤,可参考 [查询条件](https://developer.taptap.com/docs/sdk/storage/guide/objc/#%E6%9F%A5%E8%AF%A2%E6%9D%A1%E4%BB%B6) +```c# +TDSUser user = await TDSUser.GetCurrent(); +LCQuery gameSaveQuery = TapGameSave.GetQueryWithUser(user); +// 查询 Name 为 TDSUser_GameSave_Name 的云存档 +gameSaveQuery.WhereEqualTo("name","TDSUser_GameSave_Name"); +var collection = await gameSaveQuery.Find(); +``` diff --git a/README.md.meta b/README.md.meta new file mode 100644 index 0000000..74191cc --- /dev/null +++ b/README.md.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: c5330aab2c6a41699a1601f1af6b27d9 +timeCreated: 1616755935 \ No newline at end of file diff --git a/Runtime.meta b/Runtime.meta new file mode 100644 index 0000000..233b93e --- /dev/null +++ b/Runtime.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ff7a465ff38894e5c806c4c68b81994b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Internal.meta b/Runtime/Internal.meta new file mode 100644 index 0000000..c406b07 --- /dev/null +++ b/Runtime/Internal.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 11618fe14bf534d1fa85a19c9cf8d5bd +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Internal/Init.meta b/Runtime/Internal/Init.meta new file mode 100644 index 0000000..9c385e7 --- /dev/null +++ b/Runtime/Internal/Init.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5787ee3104e1a4ebd9f25f9bd5a82523 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Internal/Init/BootstrapInitTask.cs b/Runtime/Internal/Init/BootstrapInitTask.cs new file mode 100644 index 0000000..432e5eb --- /dev/null +++ b/Runtime/Internal/Init/BootstrapInitTask.cs @@ -0,0 +1,16 @@ +using LeanCloud; +using LeanCloud.Storage; +using TapTap.Common; +using TapTap.Common.Internal.Init; + +namespace TapTap.Bootstrap.Internal.Init { + public class BootstrapInitTask : IInitTask { + public int Order => 12; + + public void Init(TapConfig config) { + LCApplication.Initialize(config.ClientID, config.ClientToken, config.ServerURL); + LCObject.RegisterSubclass("_User", () => new TDSUser()); + LCObject.RegisterSubclass(TapGameSave.CLASS_NAME, () => new TapGameSave()); + } + } +} diff --git a/Runtime/Internal/Init/BootstrapInitTask.cs.meta b/Runtime/Internal/Init/BootstrapInitTask.cs.meta new file mode 100644 index 0000000..4b9279d --- /dev/null +++ b/Runtime/Internal/Init/BootstrapInitTask.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f5ad7914a84cf410e898068636f3790f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Internal/TDSLeaderBoardRankingService.cs b/Runtime/Internal/TDSLeaderBoardRankingService.cs new file mode 100644 index 0000000..4e2741c --- /dev/null +++ b/Runtime/Internal/TDSLeaderBoardRankingService.cs @@ -0,0 +1,41 @@ +using System.Threading.Tasks; +using System.Collections.Generic; +using LeanCloud.Common; +using System.Collections.ObjectModel; +using System.Linq; + +namespace TapTap.Bootstrap.@internal +{ + public class TDSLeaderBoardRankingService + { + public TDSLeaderBoardRankingService() + { + } + + public static async Task> QueryList(string name, + int from, int limit) + { + string api = "/friend/v2/taptap/leadboard"; + Dictionary queryParams = new Dictionary + { + {"type", name }, + {"skip", from }, + {"limit", limit } + }; + Dictionary response = await LCCore.HttpClient.Get>(api, + queryParams: queryParams, withAPIVersion: false); + if (response.TryGetValue("results", out object resultObj) && resultObj is List results) + { + return results.Cast>() + .Select(item => { + return new TDSLeaderBoardRanking(item); + }).ToList().AsReadOnly(); + } + else + { + return null; + } + + } + } +} diff --git a/Runtime/Internal/TDSLeaderBoardRankingService.cs.meta b/Runtime/Internal/TDSLeaderBoardRankingService.cs.meta new file mode 100644 index 0000000..8ec6635 --- /dev/null +++ b/Runtime/Internal/TDSLeaderBoardRankingService.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 645b957e90a0842298583193473e506f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Internal/TDSUserEventTrigger.cs b/Runtime/Internal/TDSUserEventTrigger.cs new file mode 100644 index 0000000..8ee946a --- /dev/null +++ b/Runtime/Internal/TDSUserEventTrigger.cs @@ -0,0 +1,46 @@ +using System.Threading.Tasks; +using System.Collections.Generic; +using LeanCloud.Common; +using System.Collections.ObjectModel; +using System.Linq; +using TapTap.Common; +using TapTap.Login; + +namespace TapTap.Bootstrap.@internal +{ + public class TDSUserEventTrigger + { + private static bool tdsUserLogin; + + public static void TriggerLoginInfo(TDSUser tdsUser) { + if (!tdsUserLogin && tdsUser != null && !string.IsNullOrEmpty(tdsUser.ObjectId)) { + TriggerLoginInfo(UnityTDSUser.TDS_CHANNEL, tdsUser.ObjectId); + tdsUserLogin = true; + } + } + + private static void TriggerLoginInfo(string platform, string userId) { + if (string.IsNullOrEmpty(platform) || string.IsNullOrEmpty(userId)) return; + if (platform.Equals("taptap".ToLower())) platform = UnityTDSUser.TDS_CHANNEL; + EventManager.TriggerEvent(EventConst.OnTapLogin, new KeyValuePair(platform, userId)); + } + + + public static void TriggerLogoutInfo(string platform) { + EventManager.TriggerEvent(EventConst.OnTapLogout, platform); + tdsUserLogin = false; + } + + public static void TriggerBindInfo(string platform, string userId) { + if (string.IsNullOrEmpty(platform) || string.IsNullOrEmpty(userId)) return; + if (platform.Equals("taptap".ToLower())) platform = UnityTDSUser.TDS_CHANNEL; + EventManager.TriggerEvent(EventConst.OnBind, new KeyValuePair(platform, userId)); + } + + public static void TriggerUnbindInfo(string platform) { + if (string.IsNullOrEmpty(platform)) return; + if (platform.Equals("taptap".ToLower())) platform = UnityTDSUser.TDS_CHANNEL; + EventManager.TriggerEvent(EventConst.OnUnbind, platform); + } + } +} \ No newline at end of file diff --git a/Runtime/Internal/TDSUserEventTrigger.cs.meta b/Runtime/Internal/TDSUserEventTrigger.cs.meta new file mode 100644 index 0000000..e2da8f2 --- /dev/null +++ b/Runtime/Internal/TDSUserEventTrigger.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: e9b209f0f3604d809035b0ac2ddd7a5b +timeCreated: 1692241103 \ No newline at end of file diff --git a/Runtime/Public.meta b/Runtime/Public.meta new file mode 100644 index 0000000..3c737e0 --- /dev/null +++ b/Runtime/Public.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d4d24a39c0c00462f92614f8c3eac3d0 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Public/ITapBootstrap.cs b/Runtime/Public/ITapBootstrap.cs new file mode 100644 index 0000000..5e8b289 --- /dev/null +++ b/Runtime/Public/ITapBootstrap.cs @@ -0,0 +1,9 @@ +using TapTap.Common; + +namespace TapTap.Bootstrap +{ + public interface ITapBootstrap + { + void Init(TapConfig config); + } +} \ No newline at end of file diff --git a/Runtime/Public/ITapBootstrap.cs.meta b/Runtime/Public/ITapBootstrap.cs.meta new file mode 100644 index 0000000..dbd8360 --- /dev/null +++ b/Runtime/Public/ITapBootstrap.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2b7e098f3b3504e6e80966d9b6be1131 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Public/TDSLeaderBoardRanking.cs b/Runtime/Public/TDSLeaderBoardRanking.cs new file mode 100644 index 0000000..2f96dd2 --- /dev/null +++ b/Runtime/Public/TDSLeaderBoardRanking.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using LeanCloud.Storage.Internal.Object; +using System.Collections.ObjectModel; +using System.Threading.Tasks; +using TapTap.Bootstrap.@internal; + +namespace TapTap.Bootstrap +{ + public class TDSLeaderBoardRanking + { + public string StatisticName + { + get; internal set; + } + + public int StatisticValue + { + get; internal set; + } + + public int Rank + { + get; internal set; + } + + public TDSUser User + { + get; internal set; + } + + internal TDSLeaderBoardRanking(Dictionary data) + { + + StatisticName = data["statisticName"] as string; + StatisticValue = Convert.ToInt32(data["statisticValue"]); + Rank = Convert.ToInt32(data["rank"]); + TDSUser tdsUser = TDSUser.Create(TDSUser.CLASS_NAME) as TDSUser; + if (data.TryGetValue("user", out object userObj) && + userObj is Dictionary userDict) + { + tdsUser.Merge(LCObjectData.Decode(userDict)); + } + User = tdsUser; + + } + + public static async Task> QueryTapFriendsLeaderBoard(string name, + int from, int limit) + { + return await TDSLeaderBoardRankingService.QueryList(name, from, limit); + } + } +} diff --git a/Runtime/Public/TDSLeaderBoardRanking.cs.meta b/Runtime/Public/TDSLeaderBoardRanking.cs.meta new file mode 100644 index 0000000..a9aa68c --- /dev/null +++ b/Runtime/Public/TDSLeaderBoardRanking.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 23d637800a62a4ecdbfff2cf39446485 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Public/TDSUser.cs b/Runtime/Public/TDSUser.cs new file mode 100644 index 0000000..06c655f --- /dev/null +++ b/Runtime/Public/TDSUser.cs @@ -0,0 +1,408 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using LeanCloud.Storage; +using LeanCloud.LiveQuery; +using TapTap.Common; +using TapTap.Bootstrap.@internal; +using TapTap.Login; +using TapTap.Login.Internal; + +namespace TapTap.Bootstrap +{ + public class TDSUser : LCUser { + public new string Password { + get { throw new NotImplementedException(); } + set { throw new NotImplementedException(); } + } + + public new string Email { + get { throw new NotImplementedException(); } + set { throw new NotImplementedException(); } + } + + public new string Mobile { + get { throw new NotImplementedException(); } + set { throw new NotImplementedException(); } + } + + public new bool EmailVerified { + get { throw new NotImplementedException(); } + } + + public new bool MobileVerified { + get { throw new NotImplementedException(); } + } + + public new Task SignUp() { + throw new NotImplementedException(); + } + + public static new Task RequestLoginSMSCode(string mobile) { + throw new NotImplementedException(); + } + + public static new Task SignUpOrLoginByMobilePhone(string mobile, string code) { + throw new NotImplementedException(); + } + + public static new Task Login(string username, string password) { + throw new NotImplementedException(); + } + + public static new Task LoginByEmail(string email, string password) { + throw new NotImplementedException(); + } + + public static new Task LoginByMobilePhoneNumber(string mobile, string password) { + throw new NotImplementedException(); + } + + public static new Task LoginBySMSCode(string mobile, string code) { + throw new NotImplementedException(); + } + + public static new Task RequestEmailVerify(string email) { + throw new NotImplementedException(); + } + + public static new Task RequestMobilePhoneVerify(string mobile) { + throw new NotImplementedException(); + } + + public static new Task VerifyMobilePhone(string mobile, string code) { + throw new NotImplementedException(); + } + + public static new Task RequestPasswordReset(string email) { + throw new NotImplementedException(); + } + + public static new Task RequestPasswordResetBySmsCode(string mobile) { + throw new NotImplementedException(); + } + + public static new Task ResetPasswordBySmsCode(string mobile, string code, string newPassword) { + throw new NotImplementedException(); + } + + public new Task UpdatePassword(string oldPassword, string newPassword) { + throw new NotImplementedException(); + } + + public static new Task RequestSMSCodeForUpdatingPhoneNumber(string mobile, int ttl = 360, + string captchaToken = null) { + throw new NotImplementedException(); + } + + public static new Task VerifyCodeForUpdatingPhoneNumber(string mobile, string code) { + throw new NotImplementedException(); + } + + #region API + + /// + /// Gets the currently logged in TDSUser with a valid session, from + /// memory or disk if necessary. + /// + /// + public static new async Task GetCurrent() { + LCUser user = await LCUser.GetCurrent(); + var result = user as TDSUser; + TDSUserEventTrigger.TriggerLoginInfo(result); + return result; + } + + /// + /// Signs in a user with a sessionToken. + /// + /// + /// + public static new async Task BecomeWithSessionToken(string sessionToken) { + return (await LCUser.BecomeWithSessionToken(sessionToken)) as TDSUser; + } + + /// + /// Signs in a user with a sessionToken for XDSDK. + /// + /// + /// + public static async Task LoginWithSessionToken(string sessionToken) { + var result = (await LCUser.BecomeWithSessionToken(sessionToken)) as TDSUser; + TDSUserEventTrigger.TriggerLoginInfo(result); + return result; + } + + /// + /// Signs up or signs in a user with third party authData. + /// + /// + /// + /// + /// + public static new async Task LoginWithAuthData(Dictionary authData, string platform, + LCUserAuthDataLoginOption option = null) { + var result = (await LCUser.LoginWithAuthData(authData, platform, option)) as TDSUser; + TDSUserEventTrigger.TriggerLoginInfo(result); + return result; + } + + public new Task AssociateAuthData(Dictionary authData, string platform) { + var result = base.AssociateAuthData(authData, platform); + TDSUserEventTrigger.TriggerBindInfo(platform, ObjectId); + return result; + } + + + public new Task AssociateAuthDataAndUnionId(Dictionary authData, + string platform, + string unionId, + LCUserAuthDataLoginOption option = null) { + var result = base.AssociateAuthDataAndUnionId(authData, platform, unionId, option); + TDSUserEventTrigger.TriggerBindInfo(platform, ObjectId); + return result; + } + + public new Task DisassociateWithAuthData(string platform) { + var result = base.DisassociateWithAuthData(platform); + TDSUserEventTrigger.TriggerUnbindInfo(platform); + return result; + } + + + /// + /// Signs up or signs in a user with third party authData and unionId. + /// + /// + /// + /// + /// + /// + public static new async Task LoginWithAuthDataAndUnionId(Dictionary authData, + string platform, string unionId, + LCUserAuthDataLoginOption option = null) { + var result = (await LCUser.LoginWithAuthDataAndUnionId(authData, platform, unionId, option)) as TDSUser; + TDSUserEventTrigger.TriggerLoginInfo(result); + return result; + } + + /// + /// Creates an anonymous user. + /// + /// + public static new async Task LoginAnonymously() { + var result = (await LCUser.LoginAnonymously()) as TDSUser; + TDSUserEventTrigger.TriggerLoginInfo(result); + return result; + } + + /// + /// Signs up or signs in a user with TapTap. + /// + /// + public static async Task LoginWithTapTap(string[] permissions = null) { + Dictionary authData = await LoginTapTap(permissions); + LCUser user = await LoginWithAuthData(authData, "taptap"); + TDSUserEventTrigger.TriggerLoginInfo(user as TDSUser); + return user as TDSUser; + } + + /// + /// Logs out the currently logged in user session. + /// + public static new async Task Logout() { + TapLogin.Logout(); + TDSUserEventTrigger.TriggerLogoutInfo(UnityTDSUser.TDS_CHANNEL); + await LCUser.Logout(); + } + + /// + /// Constructs a LCQuery for TDSUser. + /// + /// + public static new LCQuery GetQuery() { + return new LCQuery(CLASS_NAME); + } + + /// + /// Save this user to the cloud. + /// + /// + /// + /// + public new async Task Save(bool fetchWhenSave = false, LCQuery query = null) { + return (await base.Save(fetchWhenSave, query)) as TDSUser; + } + + #endregion + + private static async Task> LoginTapTap(string[] permissions) { + AccessToken token; + if (permissions == null) { + token = await TapLogin.Login(); + } else { + token = await TapLogin.Login(permissions); + } + + var profile = await TapLogin.GetProfile(); + + var result = new Dictionary + { + {"kid", token.kid}, + {"access_token", token.accessToken}, + {"token_type", token.tokenType}, + {"mac_key", token.macKey}, + {"mac_algorithm", token.macAlgorithm}, + + {"openid", profile.openid}, + {"name", profile.name}, + {"avatar", profile.avatar}, + {"unionid", profile.unionid} + }; + return result; + } + + #region Friendship + + public class FriendshipNotification { + public Action OnNewRequestComing { get; set; } + public Action OnRequestAccepted { get; set; } + public Action OnRequestDeclined { get; set; } + } + + private LCLiveQuery friendshipLivequery; + + public Task ApplyFriendship(TDSUser user, Dictionary attributes = null) { + if (user == null || string.IsNullOrEmpty(user.ObjectId)) { + throw new ArgumentNullException("User or userId is null."); + } + return ApplyFriendship(user.ObjectId, attributes); + } + + public Task ApplyFriendship(string userId, Dictionary attributes = null) { + if (string.IsNullOrEmpty(userId)) { + throw new ArgumentNullException(nameof(userId)); + } + return LCFriendship.Request(userId, attributes); + } + + public Task AcceptFriendshipRequest(LCFriendshipRequest request, Dictionary attributes = null) { + if (request == null) { + throw new ArgumentNullException(nameof(request)); + } + return LCFriendship.AcceptRequest(request, attributes); + } + + public Task DeclineFriendshipRequest(LCFriendshipRequest request) { + if (request == null) { + throw new ArgumentNullException(nameof(request)); + } + return LCFriendship.DeclineRequest(request); + } + + public Task DeleteFriendshipRequest(LCFriendshipRequest request) { + if (request == null) { + throw new ArgumentNullException(nameof(request)); + } + return request.Delete(); + } + + public LCQuery GetFriendshipRequestQuery(int status, bool includeTargetUser, bool reachToCurrentUser) { + List statusList = new List(); + if ((status & LCFriendshipRequest.STATUS_PENDING) == LCFriendshipRequest.STATUS_PENDING) { + statusList.Add("pending"); + } + if ((status & LCFriendshipRequest.STATUS_ACCEPTED) == LCFriendshipRequest.STATUS_ACCEPTED) { + statusList.Add("accepted"); + } + if ((status & LCFriendshipRequest.STATUS_DECLINED) == LCFriendshipRequest.STATUS_DECLINED) { + statusList.Add("declined"); + } + if (statusList.Count < 1) { + throw new ArgumentException("status is invalid."); + } + + LCQuery query = LCFriendshipRequest.GetQuery(); + query.WhereContainedIn("status", statusList); + if (reachToCurrentUser) { + query.WhereEqualTo("friend", this); + if (includeTargetUser) { + query.Include("user"); + } + } else { + query.WhereEqualTo("user", this); + if (includeTargetUser) { + query.Include("friend"); + } + } + query.AddDescendingOrder("updatedAt"); + return query; + } + + public LCQuery GetFriendshipQuery() { + return FriendshipQuery(); + } + + + public async Task RegisterFriendshipNotification(FriendshipNotification notification) { + if (friendshipLivequery != null) { + // 避免重复注册 + return; + } + + // 构建 LiveQuery + LCQuery selfRequestQuery = new LCQuery(LCFriendshipRequest.CLASS_NAME) + .WhereEqualTo("user", this); + LCQuery otherRequestQuery = new LCQuery(LCFriendshipRequest.CLASS_NAME) + .WhereEqualTo("friend", this); + LCQuery allQuery = LCQuery.Or(new LCQuery[] { + selfRequestQuery, otherRequestQuery + }); + + friendshipLivequery = await allQuery.Subscribe(); + friendshipLivequery.OnCreate = obj => { + if (!(obj is LCFriendshipRequest req)) { + return; + } + + LCUser friend = req.Friend; + if (friend == null || friend.ObjectId != ObjectId) { + return; + } + + notification.OnNewRequestComing(req); + }; + friendshipLivequery.OnUpdate = (obj, keys) => { + if (!(obj is LCFriendshipRequest req)) { + return; + } + if (keys == null || !keys.Contains("status")) { + return; + } + + LCUser user = req.User; + if (user == null || user.ObjectId != ObjectId) { + return; + } + + string status = req.Status; + if (status == "accepted") { + notification.OnRequestAccepted(req); + } else if (status == "declined") { + notification.OnRequestDeclined(req); + } + }; + } + + public async Task UnregisterFriendshipNotification() { + if (friendshipLivequery == null) { + return; + } + + await friendshipLivequery.Unsubscribe(); + friendshipLivequery = null; + } + + #endregion + } +} diff --git a/Runtime/Public/TDSUser.cs.meta b/Runtime/Public/TDSUser.cs.meta new file mode 100644 index 0000000..e612b84 --- /dev/null +++ b/Runtime/Public/TDSUser.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 62c37df89e7e848a0b8133d5a4e0bcc8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Public/TapBootstrap.cs b/Runtime/Public/TapBootstrap.cs new file mode 100644 index 0000000..caa3b7c --- /dev/null +++ b/Runtime/Public/TapBootstrap.cs @@ -0,0 +1,17 @@ +using TapTap.Common; + +namespace TapTap.Bootstrap +{ + public class TapBootstrap + { + public static void Init(TapConfig tapConfig) + { + TapBootstrapImpl.GetInstance().Init(tapConfig); + } + + public static void SetPreferredLanguage(TapLanguage language) + { + TapCommon.SetLanguage(language); + } + } +} \ No newline at end of file diff --git a/Runtime/Public/TapBootstrap.cs.meta b/Runtime/Public/TapBootstrap.cs.meta new file mode 100644 index 0000000..c095fc3 --- /dev/null +++ b/Runtime/Public/TapBootstrap.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b97116f83686249a9a4a9d6eb5400170 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Public/TapBootstrapImpl.cs b/Runtime/Public/TapBootstrapImpl.cs new file mode 100644 index 0000000..37a34a3 --- /dev/null +++ b/Runtime/Public/TapBootstrapImpl.cs @@ -0,0 +1,87 @@ +using System; +using System.Reflection; +using System.Linq; +using System.Collections.Generic; +using LeanCloud.Storage; +using TapTap.Common; +using TapTap.Common.Internal.Init; +using UnityEngine; + +namespace TapTap.Bootstrap +{ + public class TapBootstrapImpl : ITapBootstrap + { + private static volatile TapBootstrapImpl _sInstance; + + private static readonly object Locker = new object(); + + public static TapBootstrapImpl GetInstance() + { + lock (Locker) + { + if (_sInstance == null) + { + _sInstance = new TapBootstrapImpl(); + } + } + return _sInstance; + } + + public async void Init(TapConfig config) + { + // 初始化各个模块 + Type interfaceType = typeof(IInitTask); + Type[] initTaskTypes = AppDomain.CurrentDomain.GetAssemblies() + .Where(asssembly => asssembly.GetName().FullName.StartsWith("TapTap")) + .SelectMany(assembly => assembly.GetTypes()) + .Where(clazz => interfaceType.IsAssignableFrom(clazz) && clazz.IsClass) + .ToArray(); + if (initTaskTypes != null) { + List initTasks = new List(); + foreach (Type initTaskType in initTaskTypes) { + initTasks.Add(Activator.CreateInstance(initTaskType) as IInitTask); + } + initTasks = initTasks.OrderBy(task => task.Order).ToList(); + foreach (IInitTask task in initTasks) { + TapLogger.Debug($"Init: {task.GetType().Name}"); + task.Init(config); + } + } + + TapCommon.SetXua(); + + await TDSUser.GetCurrent(); + + TapCommon.RegisterProperties("sessionToken", new SessionTokenProperty()); + + TapCommon.RegisterProperties("objectId", new ObjectIdProperty()); + + } + + private class SessionTokenProperty : ITapPropertiesProxy + { + public string GetProperties() + { + Debug.Log($"sessionToken User:{GetCurrentUser()}"); + var sessionToken = GetCurrentUser()?.SessionToken; + return string.IsNullOrEmpty(sessionToken) ? "" : sessionToken; + } + } + + private class ObjectIdProperty : ITapPropertiesProxy + { + public string GetProperties() + { + Debug.Log($"objectId User:{GetCurrentUser()}"); + var objectId = GetCurrentUser()?.ObjectId; + return string.IsNullOrEmpty(objectId) ? "" : objectId; + } + } + + private static LCUser GetCurrentUser() + { + var field = typeof(LCUser).GetField("currentUser", BindingFlags.Static | BindingFlags.NonPublic); + return field?.GetValue(new LCUser()) as LCUser; + } + } +} \ No newline at end of file diff --git a/Runtime/Public/TapBootstrapImpl.cs.meta b/Runtime/Public/TapBootstrapImpl.cs.meta new file mode 100644 index 0000000..f5793ca --- /dev/null +++ b/Runtime/Public/TapBootstrapImpl.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fde65756640e24079982b9077782ed28 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Public/TapGameSave.cs b/Runtime/Public/TapGameSave.cs new file mode 100644 index 0000000..81f1b7d --- /dev/null +++ b/Runtime/Public/TapGameSave.cs @@ -0,0 +1,154 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Threading.Tasks; +using LeanCloud.Storage; + +namespace TapTap.Bootstrap +{ + public class TapGameSave : LCObject + { + public const string CLASS_NAME = "_GameSave"; + + private const string PATH_PREFIX = "gamesaves"; + + public string Name + { + get => this["name"] as string; + set => this["name"] = value; + } + + public string Summary + { + get => this["summary"] as string; + set => this["summary"] = value; + } + + public DateTime ModifiedAt + { + get => this["modifiedAt"] is DateTime ? (DateTime) this["modifiedAt"] : default; + set => this["modifiedAt"] = value; + } + + public double PlayedTime + { + get => this["playedTime"] is double ? (double) this["playedTime"] : -1d; + set => this["playedTime"] = value; + } + + public int ProgressValue + { + get => this["progressValue"] is int ? (int) this["progressValue"] : -1; + set => this["progressValue"] = value; + } + + public LCFile Cover + { + get => this["cover"] as LCFile; + } + + public LCFile GameFile + { + get => this["gameFile"] as LCFile; + } + + public LCUser User + { + set => this["user"] = value; + } + + public string CoverFilePath + { + set => this["cover"] = new LCFile(Path.GetFileName(value), value); + } + + public string GameFilePath + { + set => this["gameFile"] = new LCFile(Path.GetFileName(value), value); + } + + private void CheckArguments() + { + if (string.IsNullOrEmpty(Name)) throw new ArgumentNullException(nameof(Name)); + if (string.IsNullOrEmpty(Summary)) throw new ArgumentNullException(nameof(Summary)); + if (Summary.Length > 1000) throw new ArgumentOutOfRangeException(nameof(Summary)); + if (GameFile == null) throw new ArgumentNullException(nameof(GameFile)); + if (Cover == null) return; + } + + private static class GameSaveMimeType + { + internal static readonly List SupportImageMimeType = new List + { + "image/png", "image/jpeg" + }; + } + + public TapGameSave() + : base(CLASS_NAME) + { + } + + #region API + + /// + /// Save a GameSave to cloud for Current User. + /// + /// + public async Task Save() + { + var currentUser = await LCUser.GetCurrent(); + if (currentUser == null) throw new UnauthorizedAccessException("Not Login"); + CheckArguments(); + var acl = new LCACL(); + acl.SetUserWriteAccess(currentUser, true); + acl.SetUserReadAccess(currentUser, true); + ACL = acl; + User = currentUser; + if (Cover != null) + { + Cover.ACL = acl; + Cover.PathPrefix = PATH_PREFIX; + } + + GameFile.ACL = acl; + GameFile.PathPrefix = PATH_PREFIX; + return await base.Save() as TapGameSave; + } + + + /// + /// Get all GameSave by Current User. + /// + /// + public static async Task> GetCurrentUserGameSaves() + { + var user = await LCUser.GetCurrent(); + if (user == null) throw new UnauthorizedAccessException("Not Login"); + return await GetQueryWithUser(user).Find(); + } + + /// + /// Constructor a LCQuery for GameSave. + /// + /// + public static LCQuery GetQuery() => new LCQuery(CLASS_NAME); + + /// + /// Constructor a LCQuery for GameSave with LCUser + /// + /// + /// + public static LCQuery GetQueryWithUser(LCUser user) + { + var query = GetQuery(); + query.Include("cover"); + query.Include("gameFile"); + query.WhereEqualTo("user", user); + return query; + } + + #endregion API + } +} \ No newline at end of file diff --git a/Runtime/Public/TapGameSave.cs.meta b/Runtime/Public/TapGameSave.cs.meta new file mode 100644 index 0000000..02d082c --- /dev/null +++ b/Runtime/Public/TapGameSave.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ec803f6b9bcc04c97aebbba198757ef4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/TapTap.Bootstrap.Runtime.asmdef b/Runtime/TapTap.Bootstrap.Runtime.asmdef new file mode 100644 index 0000000..c0d6fdf --- /dev/null +++ b/Runtime/TapTap.Bootstrap.Runtime.asmdef @@ -0,0 +1,16 @@ +{ + "name": "TapTap.Bootstrap.Runtime", + "references": [ + "GUID:e8754b6153389406c963cd52996cc80f", + "GUID:0b3f64ec33f5b4da98a17367a35b82f2" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Runtime/TapTap.Bootstrap.Runtime.asmdef.meta b/Runtime/TapTap.Bootstrap.Runtime.asmdef.meta new file mode 100644 index 0000000..a2a8cae --- /dev/null +++ b/Runtime/TapTap.Bootstrap.Runtime.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 0ab0f87386c0640798a09051c047d4ab +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VERSIONNOTE.md b/VERSIONNOTE.md new file mode 100644 index 0000000..e69de29 diff --git a/VERSIONNOTE.md.meta b/VERSIONNOTE.md.meta new file mode 100644 index 0000000..4bb7039 --- /dev/null +++ b/VERSIONNOTE.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: d5391b17003874fb2afc3e309a3f63ef +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/package.json b/package.json new file mode 100644 index 0000000..6a713e3 --- /dev/null +++ b/package.json @@ -0,0 +1,12 @@ +{ + "name": "com.taptap.tds.bootstrap", + "displayName": "TapTap Bootstrap", + "description": "TapTap Develop Service", + "version": "3.26.0", + "unity": "2019.4", + "license": "MIT", + "dependencies": { + "com.taptap.tds.login": "3.26.0", + "com.leancloud.realtime": "2.3.0" + } +} \ No newline at end of file diff --git a/package.json.meta b/package.json.meta new file mode 100644 index 0000000..a42bf59 --- /dev/null +++ b/package.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: e618b9375caf846708cffea428908521 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: