diff --git a/.githooks/commit-msg b/.githooks/commit-msg new file mode 100644 index 0000000..c8a0c97 --- /dev/null +++ b/.githooks/commit-msg @@ -0,0 +1,23 @@ +#!/bin/sh + +COMMIT_MESSAGE="$(head -n1 "$1")" +COMMIT_MESSAGE_REGEX="^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\([a-z ]+\))?: .+$" +AUTO_COMMIT_MESSAGE_REGEX="^Merge (pull request|branch) [a-zA-Z0-9#'_/-]+ (of [a-zA-Z0-9#':_. /-]+ )?(from|into) [a-zA-Z0-9#':_. /-]+$" + + +if [[ $COMMIT_MESSAGE =~ $COMMIT_MESSAGE_REGEX || $COMMIT_MESSAGE =~ $AUTO_COMMIT_MESSAGE_REGEX ]]; then + exit 0 +else + echo "Invalid commit message format:" + echo "Your message: '${COMMIT_MESSAGE}'" + echo "" + echo "Please use the following format:" + echo "(): " + echo "" + echo " the () part is optional" + echo "" + echo "Examples:" + echo "feat(login): add support for email login" + echo "fix: fix issue with user profile image upload" + exit 1 +fi \ No newline at end of file diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100644 index 0000000..6d8e44e --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,30 @@ +#!/bin/sh + +# Gitflow branching and naming strategy enforcement for Windows and Mac + +protected_branches="^(main|develop|hotfix/|release/|feature/)" +current_branch=$(git symbolic-ref HEAD | sed 's!refs/heads/!!') + +# Ensure the branch name adheres to the Gitflow naming strategy +if ! [[ ${current_branch} =~ ${protected_branches} ]]; then + echo "Error: The current branch '${current_branch}' does not adhere to the Gitflow naming strategy." + echo "Branch names must match the following patterns: main, develop, hotfix/*, release/*, feature/*." + exit 1 +fi + +# Check if pushing to the correct remote branch +remote_branch=$(git for-each-ref --format='%(upstream:short)' $(git symbolic-ref -q HEAD)) +if [[ -z "${remote_branch}" ]]; then + echo "Error: The current branch '${current_branch}' has no tracking remote branch." + exit 1 +fi + +remote_name=$(echo ${remote_branch} | cut -d/ -f1) +remote_branch_name=$(echo ${remote_branch} | cut -d/ -f2-) + +if [[ "${current_branch}" != "${remote_branch_name}" ]]; then + echo "Error: The current branch '${current_branch}' must be pushed to a remote branch with the same name: '${remote_name}/${current_branch}'." + exit 1 +fi + +exit 0 \ No newline at end of file diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 19d25ca..b169510 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -3,7 +3,7 @@ -## [TICKETID](https://ready-player-me.monday.com/boards/2563815861/pulses/TICKETID) +## [TICKETID](https://ready-player-me.atlassian.net/browse/TICKETID) ## Description diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1d43382..2403a17 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -83,6 +83,15 @@ Scan through our [existing issues](https://github.com/github/docs/issues) to fin ### Commit your update +We encourage following the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) format when it comes to writing commit messages. Our package repositories come with a .githooks folder that has a commit-msg file that can enforce this. +To set this up you just need to configure git's hookspath folder to point there. + +You can do this by +1. Open the terminal +2. Navigate to the root folder of this repository +3. Run the following command + `git config core.hooksPath .githooks` + Commit the changes once you are happy with them. Don't forget to [self-review](#self-review) to speed up the review process:zap:. ### Self review diff --git a/Runtime/AuthManager.cs b/Runtime/AuthManager.cs index ee877f0..125cf07 100644 --- a/Runtime/AuthManager.cs +++ b/Runtime/AuthManager.cs @@ -18,6 +18,7 @@ public static class AuthManager public static Action OnSignedIn; public static Action OnSessionRefreshed; public static Action OnSignedOut; + public static Action OnSignInError; static AuthManager() { @@ -41,11 +42,20 @@ public static async void SendEmailCode(string email) await AuthenticationRequests.SendCodeToEmail(email); } - public static async Task LoginWithCode(string otp) + public static async Task LoginWithCode(string otp) { - userSession = await AuthenticationRequests.LoginWithCode(otp); - IsSignedIn = true; - OnSignedIn?.Invoke(userSession); + try + { + userSession = await AuthenticationRequests.LoginWithCode(otp); + IsSignedIn = true; + OnSignedIn?.Invoke(userSession); + return true; + } + catch (Exception e) + { + OnSignInError?.Invoke(e.Message); + return false; + } } public static async Task RefreshToken() diff --git a/Runtime/AvatarManager.cs b/Runtime/AvatarManager.cs index 30b3bc7..37de5ab 100644 --- a/Runtime/AvatarManager.cs +++ b/Runtime/AvatarManager.cs @@ -49,22 +49,33 @@ public AvatarManager(BodyType bodyType, OutfitGender gender, AvatarConfig avatar /// /// Properties which describes avatar /// Avatar gameObject - public async Task Create(AvatarProperties avatarProperties) + public async Task CreateNewAvatar(AvatarProperties avatarProperties) { try { - avatarId = await avatarAPIRequests.CreateNewAvatar(avatarProperties); + avatarProperties = await avatarAPIRequests.CreateNewAvatar(avatarProperties); + avatarId = avatarProperties.Id; } catch (Exception e) { OnError?.Invoke(e.Message); - return null; + return avatarProperties; + } + + if (ctxSource.IsCancellationRequested) + { + return avatarProperties; } + return avatarProperties; + } + + public async Task GetPreviewAvatar(string id) + { byte[] data; try { - data = await avatarAPIRequests.GetPreviewAvatar(avatarId, avatarConfigParameters); + data = await avatarAPIRequests.GetPreviewAvatar(id, avatarConfigParameters); } catch (Exception e) { diff --git a/Runtime/Utils/ResponseExtension.cs b/Runtime/Utils/ResponseExtension.cs index ba1764c..d1944b8 100644 --- a/Runtime/Utils/ResponseExtension.cs +++ b/Runtime/Utils/ResponseExtension.cs @@ -1,4 +1,5 @@ using System; +using Newtonsoft.Json.Linq; using ReadyPlayerMe.Core; namespace ReadyPlayerMe.AvatarCreator @@ -12,5 +13,18 @@ public static void ThrowIfError(this IResponse response) throw new Exception(response.Error); } } + + public static void ThrowIfError(this Response response) + { + if (!response.IsSuccess) + { + if (!string.IsNullOrEmpty(response.Text)) + { + var json = JObject.Parse(response.Text); + throw new Exception(json["message"]!.ToString()); + } + throw new Exception(response.Error); + } + } } } diff --git a/Runtime/WebRequests/AvatarAPIRequests.cs b/Runtime/WebRequests/AvatarAPIRequests.cs index 23b3686..e3c32d1 100644 --- a/Runtime/WebRequests/AvatarAPIRequests.cs +++ b/Runtime/WebRequests/AvatarAPIRequests.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -74,7 +75,7 @@ public async Task GetAvatarMetadata(string avatarId) return JsonConvert.DeserializeObject(data); } - public async Task CreateNewAvatar(AvatarProperties avatarProperties) + public async Task CreateNewAvatar(AvatarProperties avatarProperties) { var response = await authorizedRequest.SendRequest( new RequestData @@ -85,12 +86,11 @@ public async Task CreateNewAvatar(AvatarProperties avatarProperties) }, ctx: ctx ); - response.ThrowIfError(); var metadata = JObject.Parse(response.Text); - var avatarId = metadata["data"]?["id"]?.ToString(); - return avatarId; + var data = metadata["data"]!.ToString(); + return JsonConvert.DeserializeObject(data); } public async Task GetPreviewAvatar(string avatarId, string parameters = null) diff --git a/Samples~/Prefabs/Selections/CameraPhotoSelection.prefab b/Samples~/Prefabs/Selections/CameraPhotoSelection.prefab index e48bb59..4e7188f 100644 --- a/Samples~/Prefabs/Selections/CameraPhotoSelection.prefab +++ b/Samples~/Prefabs/Selections/CameraPhotoSelection.prefab @@ -546,7 +546,7 @@ MonoBehaviour: m_EditorClassIdentifier: m_Material: {fileID: 0} m_Color: {r: 1, g: 1, b: 1, a: 1} - m_RaycastTarget: 1 + m_RaycastTarget: 0 m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} m_Maskable: 1 m_OnCullStateChanged: diff --git a/Samples~/Scenes/AvatarCreatorExample.unity b/Samples~/Scenes/AvatarCreatorExample.unity index 057a324..64882fd 100644 --- a/Samples~/Scenes/AvatarCreatorExample.unity +++ b/Samples~/Scenes/AvatarCreatorExample.unity @@ -2361,8 +2361,8 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 1} m_AnchorMax: {x: 0, y: 1} - m_AnchoredPosition: {x: 175, y: -27.173203} - m_SizeDelta: {x: 350, y: 50} + m_AnchoredPosition: {x: 200.97995, y: -33.423203} + m_SizeDelta: {x: 402.2054, y: 66.8464} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &446309240 MonoBehaviour: @@ -2388,9 +2388,9 @@ MonoBehaviour: m_Font: {fileID: 12800000, guid: 12aac7490fc6e5b41a811cc24c825f5e, type: 3} m_FontSize: 25 m_FontStyle: 0 - m_BestFit: 0 + m_BestFit: 1 m_MinSize: 0 - m_MaxSize: 40 + m_MaxSize: 25 m_Alignment: 4 m_AlignByGeometry: 0 m_RichText: 1 @@ -6962,8 +6962,8 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 1} m_AnchorMax: {x: 0, y: 1} - m_AnchoredPosition: {x: 174.99997, y: -76.51961} - m_SizeDelta: {x: 200, y: 40} + m_AnchoredPosition: {x: 200.97995, y: -82.76961} + m_SizeDelta: {x: 200, y: 31.846405} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &895232620 MonoBehaviour: @@ -16609,8 +16609,8 @@ RectTransform: m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 1, y: 1} - m_AnchoredPosition: {x: 0, y: -4.8062} - m_SizeDelta: {x: -151.96, y: -51.9072} + m_AnchoredPosition: {x: -0.000030517578, y: -4.806198} + m_SizeDelta: {x: -100.000046, y: -51.9072} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &1869745903 MonoBehaviour: diff --git a/Samples~/Scripts/UI/SelectionScreens/AvatarCreatorSelection.cs b/Samples~/Scripts/UI/SelectionScreens/AvatarCreatorSelection.cs index e94f3cf..8e76df6 100644 --- a/Samples~/Scripts/UI/SelectionScreens/AvatarCreatorSelection.cs +++ b/Samples~/Scripts/UI/SelectionScreens/AvatarCreatorSelection.cs @@ -54,7 +54,7 @@ private async void Setup() AvatarCreatorData.AvatarProperties.Gender, inCreatorConfig, ctxSource.Token); - avatarManager.OnError = OnErrorCallback; + avatarManager.OnError += OnErrorCallback; await LoadAssets(); currentAvatar = await LoadAvatar(); @@ -63,6 +63,7 @@ private async void Setup() return; await LoadAvatarColors(); + assetButtonCreator.SetSelectedAssets(AvatarCreatorData.AvatarProperties.Assets); LoadingManager.DisableLoading(); } @@ -80,8 +81,8 @@ private void Cleanup() private void OnErrorCallback(string error) { - avatarManager.OnError = null; - partnerAssetManager.OnError = null; + avatarManager.OnError -= OnErrorCallback; + partnerAssetManager.OnError -= OnErrorCallback; ctxSource?.Cancel(); StateMachine.Back(); @@ -97,7 +98,7 @@ private async Task LoadAssets() AvatarCreatorData.AvatarProperties.Gender, ctxSource.Token); - partnerAssetManager.OnError = OnErrorCallback; + partnerAssetManager.OnError += OnErrorCallback; var assetIconDownloadTasks = await partnerAssetManager.GetAllAssets(); @@ -115,7 +116,9 @@ private async Task LoadAvatar() if (string.IsNullOrEmpty(AvatarCreatorData.AvatarProperties.Id)) { AvatarCreatorData.AvatarProperties.Assets ??= GetDefaultAssets(); - avatar = await avatarManager.Create(AvatarCreatorData.AvatarProperties); + + AvatarCreatorData.AvatarProperties = await avatarManager.CreateNewAvatar(AvatarCreatorData.AvatarProperties); + avatar = await avatarManager.GetPreviewAvatar(AvatarCreatorData.AvatarProperties.Id); } else { @@ -127,8 +130,6 @@ private async Task LoadAvatar() return null; } - AvatarCreatorData.AvatarProperties.Id = avatarManager.AvatarId; - ProcessAvatar(avatar); DebugPanel.AddLogWithDuration("Avatar loaded", Time.time - startTime); @@ -162,7 +163,6 @@ private void CreateUI(BodyType bodyType, Dictionary assets) assetTypeUICreator.CreateUI(bodyType, AssetTypeHelper.GetAssetTypeList(bodyType)); assetButtonCreator.CreateAssetButtons(assets, UpdateAvatar); assetButtonCreator.CreateClearButton(UpdateAvatar); - assetButtonCreator.SetSelectedAssets(AvatarCreatorData.AvatarProperties.Assets); saveButton.gameObject.SetActive(true); } diff --git a/Samples~/Scripts/UI/SelectionScreens/CameraPhotoSelection.cs b/Samples~/Scripts/UI/SelectionScreens/CameraPhotoSelection.cs index 6913607..31b3155 100644 --- a/Samples~/Scripts/UI/SelectionScreens/CameraPhotoSelection.cs +++ b/Samples~/Scripts/UI/SelectionScreens/CameraPhotoSelection.cs @@ -72,6 +72,8 @@ private void OnCameraButton() texture.Apply(); var bytes = texture.EncodeToPNG(); + + AvatarCreatorData.AvatarProperties.Id = string.Empty; AvatarCreatorData.AvatarProperties.Base64Image = Convert.ToBase64String(bytes); StateMachine.SetState(StateType.Editor); diff --git a/Samples~/Scripts/UI/SelectionScreens/LoginWithEmailSelection.cs b/Samples~/Scripts/UI/SelectionScreens/LoginWithEmailSelection.cs index 3a2e6a2..7113219 100644 --- a/Samples~/Scripts/UI/SelectionScreens/LoginWithEmailSelection.cs +++ b/Samples~/Scripts/UI/SelectionScreens/LoginWithEmailSelection.cs @@ -56,9 +56,20 @@ private void OnChangeEmail() private async void OnLogin() { LoadingManager.EnableLoading("Signing In"); - await AuthManager.LoginWithCode(codeField.text); - OnChangeEmail(); - LoadingManager.DisableLoading(); - StateMachine.SetState(StateType.AvatarSelection); + + AuthManager.OnSignInError += OnSignInError; + + if (await AuthManager.LoginWithCode(codeField.text)) + { + OnChangeEmail(); + LoadingManager.DisableLoading(); + StateMachine.SetState(StateType.AvatarSelection); + } + } + + private void OnSignInError(string error) + { + AuthManager.OnSignInError -= OnSignInError; + LoadingManager.EnableLoading(error, LoadingManager.LoadingType.Popup, false); } } diff --git a/Tests/Editor/AuthTests.cs b/Tests/Editor/AuthTests.cs index 4a7bcdc..71a20ac 100644 --- a/Tests/Editor/AuthTests.cs +++ b/Tests/Editor/AuthTests.cs @@ -10,7 +10,6 @@ public async Task Login_As_Anonymous() { await AuthManager.LoginAsAnonymous(); Assert.False(string.IsNullOrEmpty(AuthManager.UserSession.Id)); - Assert.False(string.IsNullOrEmpty(AuthManager.UserSession.RefreshToken)); } } } diff --git a/Tests/Editor/AvatarCreatorTests.cs b/Tests/Editor/AvatarCreatorTests.cs index cc91927..096f5e6 100644 --- a/Tests/Editor/AvatarCreatorTests.cs +++ b/Tests/Editor/AvatarCreatorTests.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System.Collections.Generic; +using System.Threading.Tasks; using NUnit.Framework; using ReadyPlayerMe.AvatarLoader; using UnityEngine; @@ -9,12 +10,15 @@ public class AvatarCreatorTests { private const string DOMAIN = "demo"; - private GameObject avatar; + private readonly List avatars = new List(); [TearDown] public void TearDown() { - Object.DestroyImmediate(avatar); + foreach (var avatar in avatars) + { + Object.DestroyImmediate(avatar); + } } [Test] @@ -44,20 +48,22 @@ public async Task Avatar_Create_Update_Delete() }; var avatarManager = new AvatarManager(avatarProperties.BodyType, avatarProperties.Gender); - avatar = await avatarManager.Create(avatarProperties); - + avatarProperties = await avatarManager.CreateNewAvatar(avatarProperties); + var avatar = await avatarManager.GetPreviewAvatar(avatarProperties.Id); + avatars.Add(avatar); + Assert.IsNotNull(avatar); Debug.Log("Avatar created with id: " + avatar.name); - + avatar = await avatarManager.UpdateAsset(AssetType.SkinColor, 2.ToString()); Assert.IsNotNull(avatar); Debug.Log("Avatar skinColor updated"); - + // Save Avatar var avatarId = await avatarManager.Save(); Assert.IsNotNull(avatarId); Debug.Log("Avatar metadata saved to permanent storage on server."); - + // Delete the Avatar await avatarManager.Delete(); Debug.Log("Avatar deleted."); @@ -80,9 +86,12 @@ public async Task Avatar_Create_Preview_Avatar_Get_Colors() }; var avatarManager = new AvatarManager(avatarProperties.BodyType, avatarProperties.Gender); - avatar = await avatarManager.Create(avatarProperties); + avatarProperties = await avatarManager.CreateNewAvatar(avatarProperties); + var avatar = await avatarManager.GetPreviewAvatar(avatarProperties.Id); + avatars.Add(avatar); + var avatarAPIRequests = new AvatarAPIRequests(); - var colors = await avatarAPIRequests.GetAllAvatarColors(avatar.name); + var colors = await avatarAPIRequests.GetAllAvatarColors(avatarProperties.Id); Assert.IsNotNull(colors); Assert.Greater(colors.Length, 3); } diff --git a/package.json b/package.json index c1b4c4f..6bdfd42 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "com.readyplayerme.avatarcreator", - "version": "0.2.0", + "version": "0.2.1", "displayName": "Ready Player Me Avatar Creator", "description": "For creating RPM avatars in Unity", "unity": "2020.3",