game: basic auth
This commit is contained in:
parent
383431b965
commit
0d57d2b3a9
|
@ -132,8 +132,10 @@ GameObject:
|
||||||
m_Component:
|
m_Component:
|
||||||
- component: {fileID: 133964672}
|
- component: {fileID: 133964672}
|
||||||
- component: {fileID: 133964671}
|
- component: {fileID: 133964671}
|
||||||
|
- component: {fileID: 133964673}
|
||||||
|
- component: {fileID: 133964674}
|
||||||
m_Layer: 5
|
m_Layer: 5
|
||||||
m_Name: UIDocument
|
m_Name: UI
|
||||||
m_TagString: Untagged
|
m_TagString: Untagged
|
||||||
m_Icon: {fileID: 0}
|
m_Icon: {fileID: 0}
|
||||||
m_NavMeshLayer: 0
|
m_NavMeshLayer: 0
|
||||||
|
@ -151,9 +153,9 @@ MonoBehaviour:
|
||||||
m_Script: {fileID: 19102, guid: 0000000000000000e000000000000000, type: 0}
|
m_Script: {fileID: 19102, guid: 0000000000000000e000000000000000, type: 0}
|
||||||
m_Name:
|
m_Name:
|
||||||
m_EditorClassIdentifier:
|
m_EditorClassIdentifier:
|
||||||
m_PanelSettings: {fileID: 11400000, guid: bffa1f5b972ffbe4c93fc7ced33c800d, type: 2}
|
m_PanelSettings: {fileID: 11400000, guid: 507e346b0e9956149b317358e209c34b, type: 2}
|
||||||
m_ParentUI: {fileID: 0}
|
m_ParentUI: {fileID: 0}
|
||||||
sourceAsset: {fileID: 0}
|
sourceAsset: {fileID: 9197481963319205126, guid: 11e9bf775ad0e834fa8d7d3e80056d11, type: 3}
|
||||||
m_SortingOrder: 0
|
m_SortingOrder: 0
|
||||||
--- !u!4 &133964672
|
--- !u!4 &133964672
|
||||||
Transform:
|
Transform:
|
||||||
|
@ -170,6 +172,34 @@ Transform:
|
||||||
m_Children: []
|
m_Children: []
|
||||||
m_Father: {fileID: 0}
|
m_Father: {fileID: 0}
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
--- !u!114 &133964673
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 133964670}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: 7b7c3f177f7c8ac4eb4b8b61373e37b4, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
lightness: 0
|
||||||
|
chroma: 0
|
||||||
|
hue: 0
|
||||||
|
--- !u!114 &133964674
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 133964670}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: 6149e386d290e7d4ea7ed651a5dd3551, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
state: 0
|
||||||
--- !u!1 &447905425
|
--- !u!1 &447905425
|
||||||
GameObject:
|
GameObject:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
|
@ -179,7 +209,6 @@ GameObject:
|
||||||
serializedVersion: 6
|
serializedVersion: 6
|
||||||
m_Component:
|
m_Component:
|
||||||
- component: {fileID: 447905427}
|
- component: {fileID: 447905427}
|
||||||
- component: {fileID: 447905426}
|
|
||||||
m_Layer: 0
|
m_Layer: 0
|
||||||
m_Name: Backend
|
m_Name: Backend
|
||||||
m_TagString: Untagged
|
m_TagString: Untagged
|
||||||
|
@ -187,19 +216,6 @@ GameObject:
|
||||||
m_NavMeshLayer: 0
|
m_NavMeshLayer: 0
|
||||||
m_StaticEditorFlags: 0
|
m_StaticEditorFlags: 0
|
||||||
m_IsActive: 1
|
m_IsActive: 1
|
||||||
--- !u!114 &447905426
|
|
||||||
MonoBehaviour:
|
|
||||||
m_ObjectHideFlags: 0
|
|
||||||
m_CorrespondingSourceObject: {fileID: 0}
|
|
||||||
m_PrefabInstance: {fileID: 0}
|
|
||||||
m_PrefabAsset: {fileID: 0}
|
|
||||||
m_GameObject: {fileID: 447905425}
|
|
||||||
m_Enabled: 1
|
|
||||||
m_EditorHideFlags: 0
|
|
||||||
m_Script: {fileID: 11500000, guid: e377ac89b8e57264d9a53a33902d1e07, type: 3}
|
|
||||||
m_Name:
|
|
||||||
m_EditorClassIdentifier:
|
|
||||||
connected: 1
|
|
||||||
--- !u!4 &447905427
|
--- !u!4 &447905427
|
||||||
Transform:
|
Transform:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
|
@ -401,6 +417,50 @@ Transform:
|
||||||
m_Children: []
|
m_Children: []
|
||||||
m_Father: {fileID: 0}
|
m_Father: {fileID: 0}
|
||||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
|
--- !u!1 &1204483824
|
||||||
|
GameObject:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
serializedVersion: 6
|
||||||
|
m_Component:
|
||||||
|
- component: {fileID: 1204483826}
|
||||||
|
- component: {fileID: 1204483825}
|
||||||
|
m_Layer: 0
|
||||||
|
m_Name: Game Manager
|
||||||
|
m_TagString: Untagged
|
||||||
|
m_Icon: {fileID: 0}
|
||||||
|
m_NavMeshLayer: 0
|
||||||
|
m_StaticEditorFlags: 0
|
||||||
|
m_IsActive: 1
|
||||||
|
--- !u!114 &1204483825
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 1204483824}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: 0cfc0b50e9003a0468ebdc186439c53b, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
--- !u!4 &1204483826
|
||||||
|
Transform:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 1204483824}
|
||||||
|
serializedVersion: 2
|
||||||
|
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||||
|
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||||
|
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||||
|
m_ConstrainProportionsScale: 0
|
||||||
|
m_Children: []
|
||||||
|
m_Father: {fileID: 0}
|
||||||
|
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||||
--- !u!1660057539 &9223372036854775807
|
--- !u!1660057539 &9223372036854775807
|
||||||
SceneRoots:
|
SceneRoots:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
|
@ -409,3 +469,4 @@ SceneRoots:
|
||||||
- {fileID: 705507995}
|
- {fileID: 705507995}
|
||||||
- {fileID: 133964672}
|
- {fileID: 133964672}
|
||||||
- {fileID: 447905427}
|
- {fileID: 447905427}
|
||||||
|
- {fileID: 1204483826}
|
||||||
|
|
399
ColourMeOKGame/Assets/Scripts/AccountUI.cs
Normal file
399
ColourMeOKGame/Assets/Scripts/AccountUI.cs
Normal file
|
@ -0,0 +1,399 @@
|
||||||
|
using System;
|
||||||
|
using System.Net.Mail;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.Assertions;
|
||||||
|
using UnityEngine.UIElements;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// class to handle the account view ui
|
||||||
|
/// </summary>
|
||||||
|
public class AccountUI : MonoBehaviour
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// state of the account view
|
||||||
|
/// </summary>
|
||||||
|
private enum State
|
||||||
|
{
|
||||||
|
UnassociatedState, // (start)
|
||||||
|
NotSignedIn, // initial
|
||||||
|
AfterContinue, // after
|
||||||
|
SignedIn // post
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// current state of the account view
|
||||||
|
/// </summary>
|
||||||
|
[SerializeField] private State state = State.UnassociatedState;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// account view header text
|
||||||
|
/// </summary>
|
||||||
|
private Label _header;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// username text field
|
||||||
|
/// </summary>
|
||||||
|
private TextField _usernameField;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// email text field
|
||||||
|
/// </summary>
|
||||||
|
private TextField _emailField;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// password text field
|
||||||
|
/// </summary>
|
||||||
|
private TextField _passwordField;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// button to update the username
|
||||||
|
/// </summary>
|
||||||
|
private Button _usernameUpdateButton;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// button to update the email
|
||||||
|
/// </summary>
|
||||||
|
private Button _emailUpdateButton;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// button to update the password
|
||||||
|
/// </summary>
|
||||||
|
private Button _passwordUpdateButton;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// accompanying text for the account input fields, used when an error/notice is needed
|
||||||
|
/// </summary>
|
||||||
|
private Label _accompanyingText;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// either 'continue', 'log in', or 'sign out' button
|
||||||
|
/// (in order of 'initial', 'after', and 'post' states)
|
||||||
|
/// </summary>
|
||||||
|
private Button _primaryActionButton;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// either 'forgot password' or 'create an account'
|
||||||
|
/// (in order of 'initial' and 'after' states, is hidden in 'post' state)
|
||||||
|
/// </summary>
|
||||||
|
private Button _secondaryActionButton;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// function to subscribe button events to their respective functions
|
||||||
|
/// </summary>
|
||||||
|
public void OnEnable()
|
||||||
|
{
|
||||||
|
var ui = GetComponent<UIDocument>().rootVisualElement;
|
||||||
|
|
||||||
|
_header = ui.Q<Label>("AccountHeader");
|
||||||
|
|
||||||
|
_usernameField = ui.Q<TextField>("UsernameField");
|
||||||
|
_emailField = ui.Q<TextField>("EmailField");
|
||||||
|
_passwordField = ui.Q<TextField>("PasswordField");
|
||||||
|
|
||||||
|
_usernameUpdateButton = ui.Q<Button>("UsernameUpdateButton");
|
||||||
|
_usernameUpdateButton.clicked += OnUsernameUpdateButtonClick;
|
||||||
|
|
||||||
|
_emailUpdateButton = ui.Q<Button>("EmailUpdateButton");
|
||||||
|
_emailUpdateButton.clicked += OnEmailUpdateButtonClick;
|
||||||
|
|
||||||
|
_passwordUpdateButton = ui.Q<Button>("PasswordUpdateButton");
|
||||||
|
_passwordUpdateButton.clicked += OnPasswordUpdateButtonClick;
|
||||||
|
|
||||||
|
_accompanyingText = ui.Q<Label>("AccompanyingText");
|
||||||
|
|
||||||
|
_primaryActionButton = ui.Q<Button>("PrimaryActionButton");
|
||||||
|
_primaryActionButton.clicked += OnPrimaryActionButtonClick;
|
||||||
|
|
||||||
|
_secondaryActionButton = ui.Q<Button>("SecondaryActionButton");
|
||||||
|
_secondaryActionButton.clicked += OnSecondaryActionButtonClick;
|
||||||
|
|
||||||
|
TransitionStateTo(State.NotSignedIn);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Start()
|
||||||
|
{
|
||||||
|
if (state == State.UnassociatedState)
|
||||||
|
{
|
||||||
|
throw new Exception("unreachable state");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TransitionStateTo(State newState, bool keepAccompanyingText = false)
|
||||||
|
{
|
||||||
|
// if we're transitioning to the same state, do nothing
|
||||||
|
if (state == newState) return;
|
||||||
|
|
||||||
|
// else, hide the accompanying text
|
||||||
|
if (!keepAccompanyingText) _accompanyingText.style.display = DisplayStyle.None;
|
||||||
|
|
||||||
|
// hide update buttons if not signed in
|
||||||
|
if (newState != State.SignedIn)
|
||||||
|
{
|
||||||
|
_usernameUpdateButton.style.display = DisplayStyle.None;
|
||||||
|
_passwordUpdateButton.style.display = DisplayStyle.None;
|
||||||
|
_emailUpdateButton.style.display = DisplayStyle.None;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_usernameUpdateButton.style.display = DisplayStyle.Flex;
|
||||||
|
_passwordUpdateButton.style.display = DisplayStyle.Flex;
|
||||||
|
_emailUpdateButton.style.display = DisplayStyle.Flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.Log($"transitioning to {newState}");
|
||||||
|
|
||||||
|
// set primary/secondary buttons
|
||||||
|
switch (newState)
|
||||||
|
{
|
||||||
|
case State.NotSignedIn:
|
||||||
|
_header.text = "You are not signed in.";
|
||||||
|
|
||||||
|
_usernameField.style.display = DisplayStyle.None;
|
||||||
|
_emailField.style.display = DisplayStyle.Flex;
|
||||||
|
_passwordField.style.display = DisplayStyle.Flex;
|
||||||
|
|
||||||
|
_primaryActionButton.style.display = DisplayStyle.Flex;
|
||||||
|
_secondaryActionButton.style.display = DisplayStyle.Flex;
|
||||||
|
|
||||||
|
_primaryActionButton.text = "Continue \u2192";
|
||||||
|
_secondaryActionButton.text = "Forgot Password \u2192";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case State.AfterContinue:
|
||||||
|
_header.text = "You are not signed in.";
|
||||||
|
|
||||||
|
_usernameField.style.display = DisplayStyle.None;
|
||||||
|
_emailField.style.display = DisplayStyle.Flex;
|
||||||
|
_passwordField.style.display = DisplayStyle.Flex;
|
||||||
|
|
||||||
|
_primaryActionButton.style.display = DisplayStyle.Flex;
|
||||||
|
_secondaryActionButton.style.display = DisplayStyle.Flex;
|
||||||
|
|
||||||
|
_primaryActionButton.text = "Retry Log In \u2192";
|
||||||
|
_secondaryActionButton.text = "Create an Account \u2192";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case State.SignedIn:
|
||||||
|
var username = GameManager.Instance.Backend.GetUsername();
|
||||||
|
_header.text = string.IsNullOrEmpty(username) ? "You are signed in." : $"Signed in as {username}";
|
||||||
|
|
||||||
|
_usernameField.style.display = DisplayStyle.Flex;
|
||||||
|
_emailField.style.display = DisplayStyle.Flex;
|
||||||
|
_passwordField.style.display = DisplayStyle.Flex;
|
||||||
|
|
||||||
|
_usernameField.value = GameManager.Instance.Backend.GetUsername();
|
||||||
|
_emailField.value = GameManager.Instance.Backend.GetUser().Email;
|
||||||
|
_passwordField.value = "";
|
||||||
|
|
||||||
|
_primaryActionButton.style.display = DisplayStyle.Flex;
|
||||||
|
_secondaryActionButton.style.display = DisplayStyle.None;
|
||||||
|
|
||||||
|
_primaryActionButton.text = "Sign Out \u2192";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case State.UnassociatedState:
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
state = newState;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// validate the email and password fields
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="emailField">the email field to validate</param>
|
||||||
|
/// <param name="passwordField">the password field to validate</param>
|
||||||
|
/// <exception cref="Exception">if the email or password fields are invalid</exception>
|
||||||
|
private void ValidateFields(TextField emailField, TextField passwordField)
|
||||||
|
{
|
||||||
|
emailField.style.color = Color.black;
|
||||||
|
passwordField.style.color = Color.black;
|
||||||
|
|
||||||
|
// validate email
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var dot = emailField.value.LastIndexOf(".", StringComparison.Ordinal);
|
||||||
|
|
||||||
|
// check if a dot exists
|
||||||
|
Assert.IsTrue(dot != -1);
|
||||||
|
|
||||||
|
// if a dot exists, check if the first part has the @ symbol
|
||||||
|
// so emails like 'hello.bro@domain' are invalid (I mean, they are, but not for this)
|
||||||
|
Assert.IsTrue(emailField.value.IndexOf("@", StringComparison.Ordinal) < dot);
|
||||||
|
|
||||||
|
_ = new MailAddress(emailField.value);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
emailField.style.color = Color.red;
|
||||||
|
throw new Exception("Invalid email.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate password
|
||||||
|
if (passwordField.value.Length >= 10) return;
|
||||||
|
passwordField.style.color = Color.red;
|
||||||
|
throw new Exception("Invalid password.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnUsernameUpdateButtonClick()
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnEmailUpdateButtonClick()
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPasswordUpdateButtonClick()
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPrimaryActionButtonClick()
|
||||||
|
{
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
// - continue button
|
||||||
|
// (attempt to figure out to log in or give the option to create an account)
|
||||||
|
// - log in button
|
||||||
|
case State.NotSignedIn:
|
||||||
|
case State.AfterContinue:
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ValidateFields(_emailField, _passwordField);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_accompanyingText.style.display = DisplayStyle.Flex;
|
||||||
|
_accompanyingText.text = e.Message;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GameManager.Instance.Backend.AuthenticateUser(
|
||||||
|
_emailField.value,
|
||||||
|
_passwordField.value,
|
||||||
|
(result) =>
|
||||||
|
{
|
||||||
|
switch (result)
|
||||||
|
{
|
||||||
|
case Backend.AuthenticationResult.Ok:
|
||||||
|
case Backend.AuthenticationResult.AlreadyAuthenticated:
|
||||||
|
TransitionStateTo(State.SignedIn);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Backend.AuthenticationResult.NonExistentUser:
|
||||||
|
TransitionStateTo(State.AfterContinue);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Backend.AuthenticationResult.InvalidEmail:
|
||||||
|
_accompanyingText.style.display = DisplayStyle.Flex;
|
||||||
|
_accompanyingText.text = "Invalid email. Please try again.";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Backend.AuthenticationResult.InvalidCredentials:
|
||||||
|
_accompanyingText.style.display = DisplayStyle.Flex;
|
||||||
|
_accompanyingText.text = "Invalid credentials. Please try again.";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Backend.AuthenticationResult.AlreadyExistingUser:
|
||||||
|
case Backend.AuthenticationResult.GenericError:
|
||||||
|
default:
|
||||||
|
TransitionStateTo(State.AfterContinue);
|
||||||
|
_accompanyingText.style.display = DisplayStyle.Flex;
|
||||||
|
_accompanyingText.text = "Either the account does not exist, or there was an internal error. Try again.";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
// sign out button
|
||||||
|
case State.SignedIn:
|
||||||
|
GameManager.Instance.Backend.SignOutUser();
|
||||||
|
TransitionStateTo(State.NotSignedIn);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case State.UnassociatedState:
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSecondaryActionButtonClick()
|
||||||
|
{
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
// forgot password button
|
||||||
|
case State.NotSignedIn:
|
||||||
|
throw new NotImplementedException();
|
||||||
|
|
||||||
|
// create an account button
|
||||||
|
case State.AfterContinue:
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ValidateFields(_emailField, _passwordField);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_accompanyingText.style.display = DisplayStyle.Flex;
|
||||||
|
_accompanyingText.text = e.Message;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the username field is hidden, show it and then immediately return
|
||||||
|
if (_usernameField.style.display == DisplayStyle.None)
|
||||||
|
{
|
||||||
|
_usernameField.style.display = DisplayStyle.Flex;
|
||||||
|
_accompanyingText.style.display = DisplayStyle.Flex;
|
||||||
|
_accompanyingText.text = "Set a username for your account.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GameManager.Instance.Backend.AuthenticateUser(
|
||||||
|
_emailField.value,
|
||||||
|
_passwordField.value,
|
||||||
|
(result) =>
|
||||||
|
{
|
||||||
|
switch (result)
|
||||||
|
{
|
||||||
|
case Backend.AuthenticationResult.Ok:
|
||||||
|
case Backend.AuthenticationResult.AlreadyAuthenticated:
|
||||||
|
TransitionStateTo(State.SignedIn);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Backend.AuthenticationResult.InvalidEmail:
|
||||||
|
_accompanyingText.style.display = DisplayStyle.Flex;
|
||||||
|
_accompanyingText.text = "Invalid email. Please try again.";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Backend.AuthenticationResult.InvalidCredentials:
|
||||||
|
_accompanyingText.style.display = DisplayStyle.Flex;
|
||||||
|
_accompanyingText.text = "Invalid credentials. Please try again.";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Backend.AuthenticationResult.NonExistentUser:
|
||||||
|
case Backend.AuthenticationResult.AlreadyExistingUser:
|
||||||
|
case Backend.AuthenticationResult.GenericError:
|
||||||
|
default:
|
||||||
|
_accompanyingText.style.display = DisplayStyle.Flex;
|
||||||
|
_accompanyingText.text = "An error occurred. Please try again.";
|
||||||
|
Debug.LogError(result);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
_usernameField.value);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case State.SignedIn:
|
||||||
|
case State.UnassociatedState:
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
ColourMeOKGame/Assets/Scripts/AccountUI.cs.meta
Normal file
11
ColourMeOKGame/Assets/Scripts/AccountUI.cs.meta
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 6149e386d290e7d4ea7ed651a5dd3551
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
|
@ -1,15 +1,17 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Firebase;
|
using Firebase;
|
||||||
using Firebase.Auth;
|
using Firebase.Auth;
|
||||||
using Firebase.Database;
|
using Firebase.Database;
|
||||||
|
using Firebase.Extensions;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// the general managing class for handling communication with the firebase backend
|
/// the general managing class for handling communication with the firebase backend
|
||||||
/// (to be initialised by GameManager)
|
/// (to be initialised by GameManager)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Backend : MonoBehaviour
|
public class Backend
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// enum for the result of the authentication process
|
/// enum for the result of the authentication process
|
||||||
|
@ -20,6 +22,7 @@ public enum AuthenticationResult
|
||||||
AlreadyAuthenticated,
|
AlreadyAuthenticated,
|
||||||
NonExistentUser,
|
NonExistentUser,
|
||||||
AlreadyExistingUser,
|
AlreadyExistingUser,
|
||||||
|
InvalidEmail,
|
||||||
InvalidCredentials,
|
InvalidCredentials,
|
||||||
GenericError
|
GenericError
|
||||||
}
|
}
|
||||||
|
@ -48,9 +51,14 @@ public enum FirebaseConnectionStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// whether the backend is connected to the firebase backend
|
/// callback functions to be invoked when the user signs in
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public FirebaseConnectionStatus status = FirebaseConnectionStatus.NotConnected;
|
private readonly List<Action<FirebaseUser>> _onSignInCallback = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// callback functions to be invoked when the user signs out
|
||||||
|
/// </summary>
|
||||||
|
private readonly List<Action<FirebaseUser>> _onSignOutCallback = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// the firebase authentication object
|
/// the firebase authentication object
|
||||||
|
@ -62,44 +70,25 @@ public enum FirebaseConnectionStatus
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private DatabaseReference _db;
|
private DatabaseReference _db;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// callback function to be invoked when the user signs in
|
|
||||||
/// </summary>
|
|
||||||
private Action<FirebaseUser> _onSignInCallback = user => { Debug.Log("signed in as" + user.UserId); };
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// callback function to be invoked when the user signs out
|
|
||||||
/// </summary>
|
|
||||||
private Action<FirebaseUser> _onSignOutCallback = user => { Debug.Log("signed out" + user.UserId); };
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// the current user object, if authenticated
|
/// the current user object, if authenticated
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private FirebaseUser _user;
|
private FirebaseUser _user;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// script load function
|
/// the current user's username, if authenticated
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void Awake()
|
private string _username;
|
||||||
{
|
|
||||||
Debug.Log("firing firebase Initialise() invocation");
|
|
||||||
Initialise();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// deferred cleanup function
|
/// whether the backend is connected to the firebase backend
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void OnDestroy()
|
public FirebaseConnectionStatus Status = FirebaseConnectionStatus.NotConnected;
|
||||||
{
|
|
||||||
SignOutUser();
|
|
||||||
_auth.StateChanged -= AuthStateChanged;
|
|
||||||
_auth = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// variable initialisation function
|
/// variable initialisation function
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void Initialise()
|
public void Initialise()
|
||||||
{
|
{
|
||||||
FirebaseApp.CheckAndFixDependenciesAsync().ContinueWith(task =>
|
FirebaseApp.CheckAndFixDependenciesAsync().ContinueWith(task =>
|
||||||
{
|
{
|
||||||
|
@ -110,36 +99,55 @@ private void Initialise()
|
||||||
_auth = FirebaseAuth.GetAuth(FirebaseApp.DefaultInstance);
|
_auth = FirebaseAuth.GetAuth(FirebaseApp.DefaultInstance);
|
||||||
_auth.StateChanged += AuthStateChanged;
|
_auth.StateChanged += AuthStateChanged;
|
||||||
_db = FirebaseDatabase.DefaultInstance.RootReference;
|
_db = FirebaseDatabase.DefaultInstance.RootReference;
|
||||||
status = FirebaseConnectionStatus.Connected;
|
Status = FirebaseConnectionStatus.Connected;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DependencyStatus.UnavailableDisabled:
|
case DependencyStatus.UnavailableDisabled:
|
||||||
case DependencyStatus.UnavailableInvalid:
|
case DependencyStatus.UnavailableInvalid:
|
||||||
case DependencyStatus.UnavilableMissing:
|
case DependencyStatus.UnavilableMissing:
|
||||||
case DependencyStatus.UnavailablePermission:
|
case DependencyStatus.UnavailablePermission:
|
||||||
status = FirebaseConnectionStatus.ExternalError;
|
Status = FirebaseConnectionStatus.ExternalError;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DependencyStatus.UnavailableUpdating:
|
case DependencyStatus.UnavailableUpdating:
|
||||||
status = FirebaseConnectionStatus.Updating;
|
Status = FirebaseConnectionStatus.Updating;
|
||||||
RetryInitialiseAfterDelay();
|
RetryInitialiseAfterDelay();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DependencyStatus.UnavailableUpdaterequired:
|
case DependencyStatus.UnavailableUpdaterequired:
|
||||||
status = FirebaseConnectionStatus.UpdateRequired;
|
Status = FirebaseConnectionStatus.UpdateRequired;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DependencyStatus.UnavailableOther:
|
case DependencyStatus.UnavailableOther:
|
||||||
default:
|
default:
|
||||||
status = FirebaseConnectionStatus.InternalError;
|
Status = FirebaseConnectionStatus.InternalError;
|
||||||
Debug.LogError("firebase ??? blew up or something," + task.Result);
|
Debug.LogError("firebase ??? blew up or something," + task.Result);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
Debug.Log("firebase status is" + status);
|
Debug.Log("firebase status is" + Status);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// async function to retry initialisation after a delay
|
||||||
|
/// </summary>
|
||||||
|
private async void RetryInitialiseAfterDelay()
|
||||||
|
{
|
||||||
|
await Task.Delay(TimeSpan.FromSeconds(10));
|
||||||
|
Initialise();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// cleanup function
|
||||||
|
/// </summary>
|
||||||
|
public void Cleanup()
|
||||||
|
{
|
||||||
|
SignOutUser();
|
||||||
|
_auth.StateChanged -= AuthStateChanged;
|
||||||
|
_auth = null;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// function to handle the authentication state change event
|
/// function to handle the authentication state change event
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -154,69 +162,214 @@ private void AuthStateChanged(object sender, EventArgs eventArgs)
|
||||||
var signedIn = _user != _auth.CurrentUser && _auth.CurrentUser != null;
|
var signedIn = _user != _auth.CurrentUser && _auth.CurrentUser != null;
|
||||||
|
|
||||||
// if we're not signed in, but we still hold _user locally, we've signed out
|
// if we're not signed in, but we still hold _user locally, we've signed out
|
||||||
if (!signedIn && _user != null) _onSignOutCallback(_user);
|
if (!signedIn && _user != null)
|
||||||
|
{
|
||||||
|
Debug.Log($"signed out successfully as {_user.UserId}");
|
||||||
|
foreach (var callback in _onSignOutCallback)
|
||||||
|
callback(_user);
|
||||||
|
}
|
||||||
|
|
||||||
// they have signed in, update _user
|
// they have signed in, update _user
|
||||||
_user = _auth.CurrentUser;
|
_user = _auth.CurrentUser;
|
||||||
if (signedIn) _onSignInCallback(_user);
|
if (signedIn)
|
||||||
|
{
|
||||||
|
Debug.Log($"signed in successfully as {_user.UserId}");
|
||||||
|
RetrieveUsername();
|
||||||
|
foreach (var callback in _onSignInCallback) callback(_user);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// function to register a callback for when the user signs in
|
/// function to register a callback for when the user signs in
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="callback">callback function that takes in a FirebaseUser argument</param>
|
/// <param name="callback">callback function that takes in a FirebaseUser argument</param>
|
||||||
private void RegisterOnSignInCallback(Action<FirebaseUser> callback)
|
public void RegisterOnSignInCallback(Action<FirebaseUser> callback)
|
||||||
{
|
{
|
||||||
_onSignInCallback = callback;
|
_onSignInCallback.Add(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// function to register a callback for when the user signs out
|
/// function to register a callback for when the user signs out
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="callback">callback function that takes in a FirebaseUser argument</param>
|
/// <param name="callback">callback function that takes in a FirebaseUser argument</param>
|
||||||
private void RegisterOnSignOutCallback(Action<FirebaseUser> callback)
|
public void RegisterOnSignOutCallback(Action<FirebaseUser> callback)
|
||||||
{
|
{
|
||||||
_onSignOutCallback = callback;
|
_onSignOutCallback.Add(callback);
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// async function to retry initialisation after a delay
|
|
||||||
/// </summary>
|
|
||||||
private async void RetryInitialiseAfterDelay()
|
|
||||||
{
|
|
||||||
await Task.Delay(TimeSpan.FromSeconds(10));
|
|
||||||
Initialise();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// abstraction function to authenticate the user
|
/// abstraction function to authenticate the user
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="username">user name string</param>
|
/// <param name="email">email string</param>
|
||||||
/// <param name="password">user raw password string</param>
|
/// <param name="password">user raw password string</param>
|
||||||
/// <param name="callback">callback function that takes in an AuthenticationResult argument</param>
|
/// <param name="callback">callback function that takes in an AuthenticationResult argument</param>
|
||||||
/// <param name="registerUser">whether to treat authentication as registration</param>
|
/// <param name="registerUser">whether to treat authentication as registration</param>
|
||||||
private void AuthenticateUser(
|
/// <param name="registeringUsername">username string if registering</param>
|
||||||
string username,
|
public void AuthenticateUser(
|
||||||
|
string email,
|
||||||
string password,
|
string password,
|
||||||
Action<AuthenticationResult> callback,
|
Action<AuthenticationResult> callback,
|
||||||
bool registerUser = false)
|
bool registerUser = false,
|
||||||
|
string registeringUsername = "")
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
if (GameManager.Instance.Backend.GetUser() != null)
|
||||||
|
{
|
||||||
|
callback(AuthenticationResult.AlreadyAuthenticated);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (registerUser)
|
||||||
|
{
|
||||||
|
// register user
|
||||||
|
_auth.CreateUserWithEmailAndPasswordAsync(email, password)
|
||||||
|
.ContinueWithOnMainThread(createTask =>
|
||||||
|
{
|
||||||
|
if (createTask.IsCompletedSuccessfully)
|
||||||
|
{
|
||||||
|
// store username
|
||||||
|
_db.Child("users")
|
||||||
|
.Child(_user.UserId)
|
||||||
|
.Child("username")
|
||||||
|
.SetValueAsync(registeringUsername)
|
||||||
|
.ContinueWithOnMainThread(setUsernameTask =>
|
||||||
|
{
|
||||||
|
if (setUsernameTask.IsCompletedSuccessfully)
|
||||||
|
{
|
||||||
|
_username = registeringUsername;
|
||||||
|
callback(AuthenticationResult.Ok);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.LogError(setUsernameTask.Exception);
|
||||||
|
callback(AuthenticationResult.GenericError);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (createTask.Exception?.InnerException == null)
|
||||||
|
{
|
||||||
|
callback(AuthenticationResult.GenericError);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var error = (AuthError)((FirebaseException)createTask.Exception.InnerException).ErrorCode;
|
||||||
|
|
||||||
|
// ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault
|
||||||
|
switch (error)
|
||||||
|
{
|
||||||
|
case AuthError.UserNotFound:
|
||||||
|
callback(AuthenticationResult.NonExistentUser);
|
||||||
|
return;
|
||||||
|
|
||||||
|
case AuthError.InvalidEmail:
|
||||||
|
callback(AuthenticationResult.InvalidEmail);
|
||||||
|
return;
|
||||||
|
|
||||||
|
case AuthError.WeakPassword:
|
||||||
|
case AuthError.InvalidCredential:
|
||||||
|
callback(AuthenticationResult.InvalidCredentials);
|
||||||
|
return;
|
||||||
|
|
||||||
|
case AuthError.AccountExistsWithDifferentCredentials:
|
||||||
|
case AuthError.EmailAlreadyInUse:
|
||||||
|
callback(AuthenticationResult.AlreadyExistingUser);
|
||||||
|
return;
|
||||||
|
|
||||||
|
case AuthError.Failure:
|
||||||
|
default:
|
||||||
|
Debug.LogError(error);
|
||||||
|
Debug.LogError(createTask.Exception);
|
||||||
|
callback(AuthenticationResult.GenericError);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_auth.SignInWithEmailAndPasswordAsync(email, password)
|
||||||
|
.ContinueWithOnMainThread(signInTask =>
|
||||||
|
{
|
||||||
|
if (signInTask.IsCompletedSuccessfully)
|
||||||
|
{
|
||||||
|
callback(AuthenticationResult.Ok);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (signInTask.Exception?.InnerException == null)
|
||||||
|
{
|
||||||
|
callback(AuthenticationResult.GenericError);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var error = (AuthError)((FirebaseException)signInTask.Exception.InnerException).ErrorCode;
|
||||||
|
|
||||||
|
// ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault
|
||||||
|
switch (error)
|
||||||
|
{
|
||||||
|
case AuthError.UserNotFound:
|
||||||
|
callback(AuthenticationResult.NonExistentUser);
|
||||||
|
return;
|
||||||
|
case AuthError.InvalidEmail:
|
||||||
|
callback(AuthenticationResult.InvalidEmail);
|
||||||
|
return;
|
||||||
|
case AuthError.WeakPassword:
|
||||||
|
case AuthError.InvalidCredential:
|
||||||
|
callback(AuthenticationResult.InvalidCredentials);
|
||||||
|
return;
|
||||||
|
case AuthError.AccountExistsWithDifferentCredentials:
|
||||||
|
case AuthError.EmailAlreadyInUse:
|
||||||
|
callback(AuthenticationResult.AlreadyExistingUser);
|
||||||
|
return;
|
||||||
|
case AuthError.Failure:
|
||||||
|
default:
|
||||||
|
Debug.LogError(error);
|
||||||
|
Debug.LogError(signInTask.Exception);
|
||||||
|
callback(AuthenticationResult.GenericError);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// function to retrieve the user's username from the database
|
||||||
|
/// </summary>
|
||||||
|
private void RetrieveUsername()
|
||||||
|
{
|
||||||
|
if (!Status.Equals(FirebaseConnectionStatus.Connected)) return;
|
||||||
|
_db.Child("users").Child(_user.UserId).Child("username").GetValueAsync().ContinueWith(task =>
|
||||||
|
{
|
||||||
|
if (task.IsCompletedSuccessfully)
|
||||||
|
{
|
||||||
|
_username = task.Result.Value.ToString();
|
||||||
|
Debug.Log($"our username is {_username}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_username = "???";
|
||||||
|
Debug.LogError("failed to get username");
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// abstraction function to retrieve the user
|
/// abstraction function to retrieve the user
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>the firebase user object</returns>
|
/// <returns>the firebase user object</returns>
|
||||||
private FirebaseUser GetUser()
|
public FirebaseUser GetUser()
|
||||||
{
|
{
|
||||||
return _user;
|
return _user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string GetUsername()
|
||||||
|
{
|
||||||
|
return _username;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// abstraction function to sign out the user
|
/// abstraction function to sign out the user
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void SignOutUser()
|
public void SignOutUser()
|
||||||
{
|
{
|
||||||
_auth.SignOut();
|
_auth.SignOut();
|
||||||
}
|
}
|
||||||
|
@ -226,7 +379,7 @@ private void SignOutUser()
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="playData">play data</param>
|
/// <param name="playData">play data</param>
|
||||||
/// <param name="callback">callback function that takes in one DatabaseTransactionResult argument</param>
|
/// <param name="callback">callback function that takes in one DatabaseTransactionResult argument</param>
|
||||||
private void SubmitPlay(
|
public void SubmitPlay(
|
||||||
PlayData playData,
|
PlayData playData,
|
||||||
Action<DatabaseTransactionResult> callback)
|
Action<DatabaseTransactionResult> callback)
|
||||||
{
|
{
|
||||||
|
@ -238,7 +391,7 @@ private void SignOutUser()
|
||||||
/// calculation is done locally, call UpdateUserRating to update the user's rating in the database
|
/// calculation is done locally, call UpdateUserRating to update the user's rating in the database
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="callback">callback function that takes in a DatabaseTransactionResult and float (user rating) argument</param>
|
/// <param name="callback">callback function that takes in a DatabaseTransactionResult and float (user rating) argument</param>
|
||||||
private void CalculateUserRating(
|
public void CalculateUserRating(
|
||||||
Action<DatabaseTransactionResult, float> callback)
|
Action<DatabaseTransactionResult, float> callback)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
|
@ -249,7 +402,7 @@ private void SignOutUser()
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="newRating">new user rating value as a float</param>
|
/// <param name="newRating">new user rating value as a float</param>
|
||||||
/// <param name="callback">callback function that takes in one DatabaseTransactionResult argument</param>
|
/// <param name="callback">callback function that takes in one DatabaseTransactionResult argument</param>
|
||||||
private void UpdateUserRating(
|
public void UpdateUserRating(
|
||||||
float newRating,
|
float newRating,
|
||||||
Action<DatabaseTransactionResult> callback)
|
Action<DatabaseTransactionResult> callback)
|
||||||
{
|
{
|
||||||
|
@ -259,14 +412,30 @@ private void SignOutUser()
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// abstraction function to get the leaderboard from the database
|
/// abstraction function to get the leaderboard from the database
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="callback"></param>
|
/// <param name="callback">
|
||||||
/// <exception cref="NotImplementedException"></exception>
|
/// callback function that takes in a DatabaseTransactionResult and LeaderboardEntry[] argument
|
||||||
private void GetLeaderboard(
|
/// </param>
|
||||||
|
public void GetLeaderboard(
|
||||||
Action<DatabaseTransactionResult, LeaderboardEntry[]> callback)
|
Action<DatabaseTransactionResult, LeaderboardEntry[]> callback)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// private void Playground()
|
||||||
|
// {
|
||||||
|
// // update username
|
||||||
|
// _db.Child("users").Child(_user.UserId).Child("username").SetValueAsync("newusername").ContinueWith(task => { });
|
||||||
|
//
|
||||||
|
// // update email
|
||||||
|
// _user.SendEmailVerificationBeforeUpdatingEmailAsync("name@example.com").ContinueWith(task => { });
|
||||||
|
//
|
||||||
|
// // update password
|
||||||
|
// _user.UpdatePasswordAsync("password").ContinueWith(task => { });
|
||||||
|
//
|
||||||
|
// // reset password
|
||||||
|
// _auth.SendPasswordResetEmailAsync("name@example.com").ContinueWith(task => { });
|
||||||
|
// }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// struct for a leaderboard entry
|
/// struct for a leaderboard entry
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -275,6 +444,13 @@ public struct LeaderboardEntry
|
||||||
public string Username;
|
public string Username;
|
||||||
public float Rating;
|
public float Rating;
|
||||||
public int PlayCount;
|
public int PlayCount;
|
||||||
|
|
||||||
|
public LeaderboardEntry(string username, float rating, int playCount)
|
||||||
|
{
|
||||||
|
Username = username;
|
||||||
|
Rating = rating;
|
||||||
|
PlayCount = playCount;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -284,8 +460,8 @@ public struct PlayData
|
||||||
{
|
{
|
||||||
public int RoundsPlayed;
|
public int RoundsPlayed;
|
||||||
public float AverageOverallAccuracy;
|
public float AverageOverallAccuracy;
|
||||||
public float AverageLuminanceAccuracy;
|
public float AverageLightnessAccuracy;
|
||||||
public float AverageRedGreenAccuracy;
|
public float AverageChromaAccuracy;
|
||||||
public float AverageBlueYellowAccuracy;
|
public float AverageHueAccuracy;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -62,7 +62,6 @@ public static RGB gamut_clip_preserve_chroma(RGB rgb)
|
||||||
/// a and b must be normalized so a^2 + b^2 == 1
|
/// a and b must be normalized so a^2 + b^2 == 1
|
||||||
/// </summary>
|
/// </summary>
|
||||||
// https://bottosson.github.io/posts/gamutclipping/ (MIT)
|
// https://bottosson.github.io/posts/gamutclipping/ (MIT)
|
||||||
// ReSharper disable once MemberCanBePrivate.Global
|
|
||||||
public static float find_gamut_intersection(
|
public static float find_gamut_intersection(
|
||||||
float a,
|
float a,
|
||||||
float b,
|
float b,
|
||||||
|
@ -165,7 +164,6 @@ public static RGB gamut_clip_preserve_chroma(RGB rgb)
|
||||||
/// a and b must be normalized so a^2 + b^2 == 1
|
/// a and b must be normalized so a^2 + b^2 == 1
|
||||||
/// </summary>
|
/// </summary>
|
||||||
// https://bottosson.github.io/posts/gamutclipping/ (MIT)
|
// https://bottosson.github.io/posts/gamutclipping/ (MIT)
|
||||||
// ReSharper disable once MemberCanBePrivate.Global
|
|
||||||
public static LC find_cusp(float a, float b)
|
public static LC find_cusp(float a, float b)
|
||||||
{
|
{
|
||||||
// First, find the maximum saturation (saturation S = C/L)
|
// First, find the maximum saturation (saturation S = C/L)
|
||||||
|
@ -180,7 +178,7 @@ public static LC find_cusp(float a, float b)
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://bottosson.github.io/posts/oklab/#converting-from-linear-srgb-to-oklab (public domain)
|
// https://bottosson.github.io/posts/oklab/#converting-from-linear-srgb-to-oklab (public domain)
|
||||||
// ReSharper disable once MemberCanBePrivate.Global
|
|
||||||
public static Lab linear_srgb_to_oklab(RGB c)
|
public static Lab linear_srgb_to_oklab(RGB c)
|
||||||
{
|
{
|
||||||
var l = 0.4122214708f * c.r + 0.5363325363f * c.g + 0.0514459929f * c.b;
|
var l = 0.4122214708f * c.r + 0.5363325363f * c.g + 0.0514459929f * c.b;
|
||||||
|
@ -199,7 +197,6 @@ public static Lab linear_srgb_to_oklab(RGB c)
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://bottosson.github.io/posts/oklab/#converting-from-linear-srgb-to-oklab (public domain)
|
// https://bottosson.github.io/posts/oklab/#converting-from-linear-srgb-to-oklab (public domain)
|
||||||
// ReSharper disable once MemberCanBePrivate.Global
|
|
||||||
public static RGB oklab_to_linear_srgb(Lab c)
|
public static RGB oklab_to_linear_srgb(Lab c)
|
||||||
{
|
{
|
||||||
var interimL = c.L + 0.3963377774f * c.a + 0.2158037573f * c.b;
|
var interimL = c.L + 0.3963377774f * c.a + 0.2158037573f * c.b;
|
||||||
|
@ -223,7 +220,6 @@ public static RGB oklab_to_linear_srgb(Lab c)
|
||||||
/// a and b must be normalized so a^2 + b^2 == 1
|
/// a and b must be normalized so a^2 + b^2 == 1
|
||||||
/// </summary>
|
/// </summary>
|
||||||
// https://bottosson.github.io/posts/gamutclipping/ (MIT)
|
// https://bottosson.github.io/posts/gamutclipping/ (MIT)
|
||||||
// ReSharper disable once MemberCanBePrivate.Global
|
|
||||||
public static float compute_max_saturation(float a, float b)
|
public static float compute_max_saturation(float a, float b)
|
||||||
{
|
{
|
||||||
// Max saturation will be when one of r, g or b goes below zero.
|
// Max saturation will be when one of r, g or b goes below zero.
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using UnityEngine.UIElements;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// singleton for a single source of truth game state and flow management
|
/// singleton for a single source of truth game state and flow management
|
||||||
|
@ -23,6 +24,16 @@ public enum DisplayState
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static GameManager Instance;
|
public static GameManager Instance;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// the visual element object for game ui (hud/prompts/tooltips)
|
||||||
|
/// </summary>
|
||||||
|
private VisualElement _ui;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// backend object for handling communication with the firebase backend
|
||||||
|
/// </summary>
|
||||||
|
public Backend Backend;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// enforces singleton behaviour; sets doesn't destroy on load and checks for multiple instances
|
/// enforces singleton behaviour; sets doesn't destroy on load and checks for multiple instances
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -42,4 +53,21 @@ private void Awake()
|
||||||
Destroy(gameObject);
|
Destroy(gameObject);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// called when the game object is enabled
|
||||||
|
/// </summary>
|
||||||
|
private void OnEnable()
|
||||||
|
{
|
||||||
|
Backend = new Backend();
|
||||||
|
Backend.Initialise();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// called when the game object is disabled
|
||||||
|
/// </summary>
|
||||||
|
private void OnDestroy()
|
||||||
|
{
|
||||||
|
Backend.Cleanup();
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -2,7 +2,10 @@
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.UIElements;
|
using UnityEngine.UIElements;
|
||||||
|
|
||||||
public class OklchColourPicker : MonoBehaviour
|
/// <summary>
|
||||||
|
/// class to handle the oklch colour picker ui
|
||||||
|
/// </summary>
|
||||||
|
public class OklchColourPickerUI : MonoBehaviour
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// perceptual lightness value of the colour (0-100)
|
/// perceptual lightness value of the colour (0-100)
|
||||||
|
@ -68,14 +71,6 @@ public void OnEnable()
|
||||||
_responseColour = ui.Q<VisualElement>("ResponseColour");
|
_responseColour = ui.Q<VisualElement>("ResponseColour");
|
||||||
}
|
}
|
||||||
|
|
||||||
// /// <summary>
|
|
||||||
// /// update the response preview colour
|
|
||||||
// /// </summary>
|
|
||||||
// private void Update()
|
|
||||||
// {
|
|
||||||
// _responseColour.style.backgroundColor = ToColor();
|
|
||||||
// }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// handle lightness slider change
|
/// handle lightness slider change
|
||||||
/// </summary>
|
/// </summary>
|
|
@ -1,10 +1,3 @@
|
||||||
Button {
|
|
||||||
border-left-color: rgba(0, 0, 0, 0);
|
|
||||||
border-right-color: rgba(0, 0, 0, 0);
|
|
||||||
border-top-color: rgba(0, 0, 0, 0);
|
|
||||||
border-bottom-color: rgba(0, 0, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#Content > Button {
|
#Content > Button {
|
||||||
border-top-color: rgb(208, 152, 194);
|
border-top-color: rgb(208, 152, 194);
|
||||||
border-right-color: rgba(208, 152, 194, 0);
|
border-right-color: rgba(208, 152, 194, 0);
|
||||||
|
@ -15,14 +8,8 @@ #Content > Button {
|
||||||
|
|
||||||
#Content Button {
|
#Content Button {
|
||||||
background-color: rgba(0, 0, 0, 0);
|
background-color: rgba(0, 0, 0, 0);
|
||||||
padding-left: 0;
|
padding: 12px 0;
|
||||||
padding-right: 0;
|
margin: 0;
|
||||||
padding-top: 12px;
|
|
||||||
padding-bottom: 12px;
|
|
||||||
margin-left: 0;
|
|
||||||
margin-right: 0;
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 0;
|
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
color: rgb(208, 152, 194);
|
color: rgb(208, 152, 194);
|
||||||
-unity-text-align: upper-left;
|
-unity-text-align: upper-left;
|
||||||
|
@ -34,14 +21,8 @@ #Content Label {
|
||||||
color: rgb(208, 152, 194);
|
color: rgb(208, 152, 194);
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
-unity-text-align: upper-left;
|
-unity-text-align: upper-left;
|
||||||
padding-top: 12px;
|
padding: 12px 0;
|
||||||
padding-bottom: 12px;
|
margin: 0;
|
||||||
padding-left: 0;
|
|
||||||
padding-right: 0;
|
|
||||||
margin-left: 0;
|
|
||||||
margin-right: 0;
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#MainView Label {
|
#MainView Label {
|
||||||
|
@ -49,28 +30,8 @@ #MainView Label {
|
||||||
color: rgb(15, 13, 27);
|
color: rgb(15, 13, 27);
|
||||||
}
|
}
|
||||||
|
|
||||||
.stretch {
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.center {
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.circle {
|
|
||||||
width: 70%;
|
|
||||||
height: 0;
|
|
||||||
padding-top: 70%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lch-slider Label {
|
.lch-slider Label {
|
||||||
display: flex;
|
display: flex;
|
||||||
color: rgb(36, 0, 255);
|
|
||||||
width: auto;
|
width: auto;
|
||||||
min-width: 15%;
|
min-width: 15%;
|
||||||
}
|
}
|
||||||
|
@ -94,18 +55,94 @@ .lch-slider #unity-tracker {
|
||||||
|
|
||||||
.lch-slider #unity-dragger {
|
.lch-slider #unity-dragger {
|
||||||
background-color: rgb(15, 13, 27);
|
background-color: rgb(15, 13, 27);
|
||||||
border-left-color: rgb(0, 0, 0);
|
border-left-color: rgb(15, 13, 27);
|
||||||
border-right-color: rgb(0, 0, 0);
|
border-right-color: rgb(15, 13, 27);
|
||||||
border-top-color: rgb(0, 0, 0);
|
border-top-color: rgb(15, 13, 27);
|
||||||
border-bottom-color: rgb(0, 0, 0);
|
border-bottom-color: rgb(15, 13, 27);
|
||||||
border-top-width: 12px;
|
border-top-width: 12px;
|
||||||
border-right-width: 12px;
|
border-right-width: 12px;
|
||||||
border-bottom-width: 12px;
|
border-bottom-width: 12px;
|
||||||
border-left-width: 12px;
|
border-left-width: 12px;
|
||||||
|
border-radius: 5px;
|
||||||
|
height: 24px;
|
||||||
|
width: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#AccountFields TextField {
|
||||||
|
flex-shrink: 0;
|
||||||
|
flex-grow: 1;
|
||||||
|
margin-top: 0.75%;
|
||||||
|
margin-right: 0;
|
||||||
|
margin-bottom: 0.75%;
|
||||||
|
margin-left: 0;
|
||||||
|
font-size: 22px;
|
||||||
|
padding-top: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
padding-left: 0;
|
||||||
|
color: rgb(15, 13, 27);
|
||||||
|
}
|
||||||
|
|
||||||
|
#AccountFields TextField Label {
|
||||||
|
-unity-text-align: middle-left;
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 10%;
|
||||||
|
color: rgb(15, 13, 27);
|
||||||
|
}
|
||||||
|
|
||||||
|
#AccountFields TextField #unity-text-input {
|
||||||
border-top-left-radius: 5px;
|
border-top-left-radius: 5px;
|
||||||
border-top-right-radius: 5px;
|
border-top-right-radius: 5px;
|
||||||
border-bottom-right-radius: 5px;
|
border-bottom-right-radius: 5px;
|
||||||
border-bottom-left-radius: 5px;
|
border-bottom-left-radius: 5px;
|
||||||
height: 24px;
|
border-left-color: rgba(0, 0, 0, 0);
|
||||||
width: 24px;
|
border-right-color: rgba(0, 0, 0, 0);
|
||||||
|
border-top-color: rgba(0, 0, 0, 0);
|
||||||
|
border-bottom-color: rgba(0, 0, 0, 0);
|
||||||
|
background-color: rgb(255, 232, 249);
|
||||||
|
}
|
||||||
|
|
||||||
|
#AccountFields Button {
|
||||||
|
flex-grow: 0;
|
||||||
|
flex-shrink: 1;
|
||||||
|
width: auto;
|
||||||
|
padding-top: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
padding-left: 0;
|
||||||
|
margin-top: 0.75%;
|
||||||
|
margin-right: 0;
|
||||||
|
margin-bottom: 0.75%;
|
||||||
|
margin-left: 1.5%;
|
||||||
|
font-size: 22px;
|
||||||
|
-unity-text-align: middle-center;
|
||||||
|
background-color: rgb(15, 13, 27);
|
||||||
|
border-left-color: rgba(0, 0, 0, 0);
|
||||||
|
border-right-color: rgba(0, 0, 0, 0);
|
||||||
|
border-top-color: rgba(0, 0, 0, 0);
|
||||||
|
border-bottom-color: rgba(0, 0, 0, 0);
|
||||||
|
border-top-left-radius: 5px;
|
||||||
|
border-top-right-radius: 5px;
|
||||||
|
border-bottom-right-radius: 5px;
|
||||||
|
border-bottom-left-radius: 5px;
|
||||||
|
color: rgb(208, 152, 194);
|
||||||
|
min-width: 8.5%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#AccountButtons > Button {
|
||||||
|
font-size: 22px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0);
|
||||||
|
border-left-color: rgba(0, 0, 0, 0);
|
||||||
|
border-right-color: rgba(0, 0, 0, 0);
|
||||||
|
border-top-color: rgba(0, 0, 0, 0);
|
||||||
|
border-bottom-color: rgba(0, 0, 0, 0);
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
margin-left: 0;
|
||||||
|
margin-right: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
padding-top: 0;
|
||||||
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,67 +1,164 @@
|
||||||
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" xsi="http://www.w3.org/2001/XMLSchema-instance" engine="UnityEngine.UIElements" editor="UnityEditor.UIElements" noNamespaceSchemaLocation="../../UIElementsSchema/UIElements.xsd" editor-extension-mode="False">
|
<ui:UXML xmlns:ui="UnityEngine.UIElements" xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
<Style src="project://database/Assets/UI/GameUI.uss?fileID=7433441132597879392&guid=2c7ff79f21a3e8e408e76d75944d575b&type=3#GameUI" />
|
engine="UnityEngine.UIElements" editor="UnityEditor.UIElements"
|
||||||
<ui:VisualElement name="Root" style="flex-grow: 1; background-color: rgb(208, 152, 194); justify-content: space-around; align-items: stretch; align-self: stretch; flex-direction: row;">
|
noNamespaceSchemaLocation="../../UIElementsSchema/UIElements.xsd" editor-extension-mode="False">
|
||||||
<ui:VisualElement name="SideView" style="flex-grow: 0; background-color: rgb(15, 13, 27); flex-shrink: 0; width: 25%; justify-content: space-between;">
|
<Style src="project://database/Assets/UI/GameUI.uss?fileID=7433441132597879392&guid=2c7ff79f21a3e8e408e76d75944d575b&type=3#GameUI"/>
|
||||||
<ui:VisualElement name="Header" style="flex-grow: 0; margin-top: 10%; margin-right: 10%; margin-bottom: 0; margin-left: 10%; justify-content: space-between; align-items: flex-start; flex-direction: column;">
|
<ui:VisualElement name="Root"
|
||||||
<ui:Label tabindex="-1" text="Colour Me OK" parse-escape-sequences="true" display-tooltip-when-elided="true" name="Name" style="color: rgb(208, 152, 194); -unity-font-style: bold; font-size: 58px; white-space: normal;" />
|
style="flex-grow: 1; background-color: rgb(208, 152, 194); justify-content: space-around; align-items: stretch; align-self: stretch; flex-direction: row;">
|
||||||
<ui:Label tabindex="-1" text="Color Me OK is a colour-matching game using the coordinates of the OKLCh colour model on the OKLab percepually uniform colour space." parse-escape-sequences="true" display-tooltip-when-elided="true" name="Description" style="font-size: 16px; white-space: normal; text-overflow: clip; color: rgb(208, 152, 194);" />
|
<ui:VisualElement name="SideView"
|
||||||
|
style="flex-grow: 0; background-color: rgb(15, 13, 27); flex-shrink: 0; width: 25%; justify-content: space-between;">
|
||||||
|
<ui:VisualElement name="Header"
|
||||||
|
style="flex-grow: 0; margin-top: 10%; margin-right: 10%; margin-bottom: 0; margin-left: 10%; justify-content: space-between; align-items: flex-start; flex-direction: column;">
|
||||||
|
<ui:Label tabindex="-1" text="Colour Me OK" parse-escape-sequences="true"
|
||||||
|
display-tooltip-when-elided="true" name="Name"
|
||||||
|
style="color: rgb(208, 152, 194); -unity-font-style: bold; font-size: 58px; white-space: normal;"/>
|
||||||
|
<ui:Label tabindex="-1"
|
||||||
|
text="Color Me OK is a colour-matching game using the coordinates of the OKLCh colour model on the OKLab perceptually uniform colour space."
|
||||||
|
parse-escape-sequences="true" display-tooltip-when-elided="true" name="Description"
|
||||||
|
style="font-size: 16px; white-space: normal; text-overflow: clip; color: rgb(208, 152, 194);"/>
|
||||||
</ui:VisualElement>
|
</ui:VisualElement>
|
||||||
<ui:VisualElement name="Content" style="flex-grow: 0; padding-right: 10%; padding-bottom: 10%; padding-left: 10%;">
|
<ui:VisualElement name="Content"
|
||||||
<ui:Button text="Play ↗" parse-escape-sequences="true" display-tooltip-when-elided="true" name="PlayButton" />
|
style="flex-grow: 0; padding-right: 10%; padding-bottom: 10%; padding-left: 10%;">
|
||||||
<ui:Button text="Leaderboard ↗" parse-escape-sequences="true" display-tooltip-when-elided="true" name="LeaderboardButton" />
|
<ui:Button text="Play ↗" parse-escape-sequences="true" display-tooltip-when-elided="true"
|
||||||
<ui:Button text="Account ↗" parse-escape-sequences="true" display-tooltip-when-elided="true" name="AccountButton" />
|
name="PlayButton"/>
|
||||||
<ui:VisualElement name="AccountSection" style="flex-grow: 0; border-top-color: rgb(208, 152, 194); margin-top: 0; border-top-width: 1px; margin-right: 0; margin-bottom: 0; margin-left: 0; border-bottom-color: rgb(208, 152, 194); border-bottom-width: 1px; padding-bottom: 12px;">
|
<ui:Button text="Leaderboard ↗" parse-escape-sequences="true" display-tooltip-when-elided="true"
|
||||||
<ui:VisualElement name="UserDetails" style="flex-grow: 1; flex-direction: row; align-items: stretch; justify-content: space-between; font-size: 10px; align-self: stretch;">
|
name="LeaderboardButton"/>
|
||||||
|
<ui:Button text="Account ↗" parse-escape-sequences="true" display-tooltip-when-elided="true"
|
||||||
|
name="AccountButton"/>
|
||||||
|
<ui:VisualElement name="AccountSection"
|
||||||
|
style="flex-grow: 0; border-top-color: rgb(208, 152, 194); margin-top: 0; border-top-width: 1px; margin-right: 0; margin-bottom: 0; margin-left: 0; border-bottom-color: rgb(208, 152, 194); border-bottom-width: 1px; padding-bottom: 12px;">
|
||||||
|
<ui:VisualElement name="PlayerDetails"
|
||||||
|
style="flex-grow: 1; flex-direction: row; align-items: stretch; justify-content: space-between; font-size: 10px; align-self: stretch;">
|
||||||
<ui:VisualElement name="PlayerNameDetail" style="flex-grow: 1;">
|
<ui:VisualElement name="PlayerNameDetail" style="flex-grow: 1;">
|
||||||
<ui:Label tabindex="-1" text="Player" parse-escape-sequences="true" display-tooltip-when-elided="true" name="PlayerHeader" style="-unity-font-style: normal; font-size: 14px; padding-bottom: 0; -unity-text-align: lower-left;" />
|
<ui:Label tabindex="-1" text="Player" parse-escape-sequences="true"
|
||||||
<ui:Label tabindex="-1" text="Not Signed In" parse-escape-sequences="true" display-tooltip-when-elided="true" name="PlayerText" style="-unity-font-style: normal; font-size: 18px; padding-top: 6px;" />
|
display-tooltip-when-elided="true" name="PlayerHeader"
|
||||||
|
style="-unity-font-style: normal; font-size: 14px; padding-bottom: 0; -unity-text-align: lower-left;"/>
|
||||||
|
<ui:Label tabindex="-1" text="Not Signed In" parse-escape-sequences="true"
|
||||||
|
display-tooltip-when-elided="true" name="PlayerText"
|
||||||
|
style="-unity-font-style: normal; font-size: 18px; padding-top: 6px;"/>
|
||||||
</ui:VisualElement>
|
</ui:VisualElement>
|
||||||
<ui:VisualElement name="PlayerRatingDetail" style="flex-grow: 0;">
|
<ui:VisualElement name="PlayerRatingDetail" style="flex-grow: 0;">
|
||||||
<ui:Label tabindex="-1" text="Rating" parse-escape-sequences="true" display-tooltip-when-elided="true" name="RatingHeader" style="-unity-font-style: normal; font-size: 14px; padding-bottom: 0; -unity-text-align: lower-right;" />
|
<ui:Label tabindex="-1" text="Rating" parse-escape-sequences="true"
|
||||||
<ui:Label tabindex="-1" text="00.00%" parse-escape-sequences="true" display-tooltip-when-elided="true" name="RatingText" style="-unity-font-style: normal; font-size: 18px; padding-top: 6px; -unity-text-align: upper-right;" />
|
display-tooltip-when-elided="true" name="RatingHeader"
|
||||||
|
style="-unity-font-style: normal; font-size: 14px; padding-bottom: 0; -unity-text-align: lower-right;"/>
|
||||||
|
<ui:Label tabindex="-1" text="00.00%" parse-escape-sequences="true"
|
||||||
|
display-tooltip-when-elided="true" name="RatingText"
|
||||||
|
style="-unity-font-style: normal; font-size: 18px; padding-top: 6px; -unity-text-align: upper-right;"/>
|
||||||
</ui:VisualElement>
|
</ui:VisualElement>
|
||||||
</ui:VisualElement>
|
</ui:VisualElement>
|
||||||
<ui:VisualElement name="AccuracyDetails" style="flex-grow: 1; flex-direction: row; align-items: stretch; justify-content: space-between; font-size: 10px; align-self: stretch; padding-top: 4px;">
|
<ui:VisualElement name="AccuracyDetails"
|
||||||
|
style="flex-grow: 1; flex-direction: row; align-items: stretch; justify-content: space-between; font-size: 10px; align-self: stretch; padding-top: 4px;">
|
||||||
<ui:VisualElement name="LightnessAccuracyDetail" style="flex-grow: 0;">
|
<ui:VisualElement name="LightnessAccuracyDetail" style="flex-grow: 0;">
|
||||||
<ui:Label tabindex="-1" text="Lightness Accuracy" parse-escape-sequences="true" display-tooltip-when-elided="true" name="LightnessAccuracyHeader" style="-unity-font-style: normal; font-size: 14px; padding-bottom: 0; -unity-text-align: lower-left; padding-top: 0;" />
|
<ui:Label tabindex="-1" text="Lightness Accuracy" parse-escape-sequences="true"
|
||||||
<ui:Label tabindex="-1" text="00.0%" parse-escape-sequences="true" display-tooltip-when-elided="true" name="LightnessAccuracyText" style="-unity-font-style: normal; font-size: 18px; padding-top: 6px; padding-bottom: 0;" />
|
display-tooltip-when-elided="true" name="LightnessAccuracyHeader"
|
||||||
|
style="-unity-font-style: normal; font-size: 14px; padding-bottom: 0; -unity-text-align: lower-left; padding-top: 0;"/>
|
||||||
|
<ui:Label tabindex="-1" text="00.0%" parse-escape-sequences="true"
|
||||||
|
display-tooltip-when-elided="true" name="LightnessAccuracyText"
|
||||||
|
style="-unity-font-style: normal; font-size: 18px; padding-top: 6px; padding-bottom: 0;"/>
|
||||||
</ui:VisualElement>
|
</ui:VisualElement>
|
||||||
<ui:VisualElement name="ChromaAccuracyDetail" style="flex-grow: 0;">
|
<ui:VisualElement name="ChromaAccuracyDetail" style="flex-grow: 0;">
|
||||||
<ui:Label tabindex="-1" text="Chroma Accuracy" parse-escape-sequences="true" display-tooltip-when-elided="true" name="ChromaAccuracyHeader" style="-unity-font-style: normal; font-size: 14px; padding-bottom: 0; -unity-text-align: lower-center; padding-top: 0;" />
|
<ui:Label tabindex="-1" text="Chroma Accuracy" parse-escape-sequences="true"
|
||||||
<ui:Label tabindex="-1" text="00.0%" parse-escape-sequences="true" display-tooltip-when-elided="true" name="ChromaAccuracyText" style="-unity-font-style: normal; font-size: 18px; padding-top: 6px; -unity-text-align: upper-center; padding-bottom: 0;" />
|
display-tooltip-when-elided="true" name="ChromaAccuracyHeader"
|
||||||
|
style="-unity-font-style: normal; font-size: 14px; padding-bottom: 0; -unity-text-align: lower-center; padding-top: 0;"/>
|
||||||
|
<ui:Label tabindex="-1" text="00.0%" parse-escape-sequences="true"
|
||||||
|
display-tooltip-when-elided="true" name="ChromaAccuracyText"
|
||||||
|
style="-unity-font-style: normal; font-size: 18px; padding-top: 6px; -unity-text-align: upper-center; padding-bottom: 0;"/>
|
||||||
</ui:VisualElement>
|
</ui:VisualElement>
|
||||||
<ui:VisualElement name="HueAccuracyDetail" style="flex-grow: 0;">
|
<ui:VisualElement name="HueAccuracyDetail" style="flex-grow: 0;">
|
||||||
<ui:Label tabindex="-1" text="Hue Accuracy" parse-escape-sequences="true" display-tooltip-when-elided="true" name="HueAccuracyHeader" style="-unity-font-style: normal; font-size: 14px; padding-bottom: 0; -unity-text-align: lower-right; padding-top: 0;" />
|
<ui:Label tabindex="-1" text="Hue Accuracy" parse-escape-sequences="true"
|
||||||
<ui:Label tabindex="-1" text="00.0%" parse-escape-sequences="true" display-tooltip-when-elided="true" name="HueAccuracyText" style="-unity-font-style: normal; font-size: 18px; padding-top: 6px; -unity-text-align: upper-right; padding-bottom: 0;" />
|
display-tooltip-when-elided="true" name="HueAccuracyHeader"
|
||||||
|
style="-unity-font-style: normal; font-size: 14px; padding-bottom: 0; -unity-text-align: lower-right; padding-top: 0;"/>
|
||||||
|
<ui:Label tabindex="-1" text="00.0%" parse-escape-sequences="true"
|
||||||
|
display-tooltip-when-elided="true" name="HueAccuracyText"
|
||||||
|
style="-unity-font-style: normal; font-size: 18px; padding-top: 6px; -unity-text-align: upper-right; padding-bottom: 0;"/>
|
||||||
</ui:VisualElement>
|
</ui:VisualElement>
|
||||||
</ui:VisualElement>
|
</ui:VisualElement>
|
||||||
</ui:VisualElement>
|
</ui:VisualElement>
|
||||||
</ui:VisualElement>
|
</ui:VisualElement>
|
||||||
</ui:VisualElement>
|
</ui:VisualElement>
|
||||||
<ui:VisualElement name="MainView" style="flex-grow: 0; flex-shrink: 0; width: 75%; justify-content: space-between;">
|
<ui:VisualElement name="MainView"
|
||||||
<ui:VisualElement name="GameView" style="flex-grow: 0; margin-top: 3.25%; margin-right: 3.25%; margin-bottom: 3.25%; margin-left: 3.25%; justify-content: space-between; height: 100%; align-self: stretch;">
|
style="flex-grow: 0; flex-shrink: 0; width: 75%; justify-content: space-between;">
|
||||||
<ui:VisualElement name="GameHeader" style="flex-grow: 0; flex-direction: row; justify-content: space-between; align-self: stretch;">
|
<ui:VisualElement name="GameView"
|
||||||
<ui:Label tabindex="-1" text="1/5" parse-escape-sequences="true" display-tooltip-when-elided="true" name="RoundText" style="font-size: 58px; -unity-font-style: normal;" />
|
style="flex-grow: 0; margin-top: 3.25%; margin-right: 3.25%; margin-bottom: 3.25%; margin-left: 3.25%; justify-content: space-between; height: 100%; align-self: stretch; display: none;">
|
||||||
<ui:Label tabindex="-1" text="0.00s" parse-escape-sequences="true" display-tooltip-when-elided="true" name="TimeText" style="-unity-text-align: lower-right; font-size: 58px;" />
|
<ui:VisualElement name="GameHeader"
|
||||||
|
style="flex-grow: 0; flex-direction: row; justify-content: space-between; align-self: stretch;">
|
||||||
|
<ui:Label tabindex="-1" text="1/5" parse-escape-sequences="true" display-tooltip-when-elided="true"
|
||||||
|
name="RoundText" style="font-size: 58px; -unity-font-style: normal;"/>
|
||||||
|
<ui:Label tabindex="-1" text="0.00s" parse-escape-sequences="true"
|
||||||
|
display-tooltip-when-elided="true" name="TimeText"
|
||||||
|
style="-unity-text-align: lower-right; font-size: 58px;"/>
|
||||||
</ui:VisualElement>
|
</ui:VisualElement>
|
||||||
<ui:VisualElement name="ColourPreview" style="flex-grow: 0; height: 50%; background-color: rgb(255, 255, 255); border-top-left-radius: 8px; border-top-right-radius: 8px; border-bottom-right-radius: 8px; border-bottom-left-radius: 8px; flex-direction: row; padding-top: 2%; padding-right: 2%; padding-bottom: 2%; padding-left: 2%; justify-content: space-between;">
|
<ui:VisualElement name="ColourPreview"
|
||||||
<ui:VisualElement name="TemplatePreview" style="flex-grow: 0; flex-shrink: 0; height: 100%; width: 49%;">
|
style="flex-grow: 0; height: 50%; background-color: rgb(255, 255, 255); border-top-left-radius: 8px; border-top-right-radius: 8px; border-bottom-right-radius: 8px; border-bottom-left-radius: 8px; flex-direction: row; padding-top: 2%; padding-right: 2%; padding-bottom: 2%; padding-left: 2%; justify-content: space-between;">
|
||||||
<ui:Label tabindex="-1" text="Template" parse-escape-sequences="true" display-tooltip-when-elided="true" name="TemplateText" style="margin-bottom: 12px; margin-top: 0; -unity-font-style: normal;" />
|
<ui:VisualElement name="TemplatePreview"
|
||||||
<ui:VisualElement name="TemplateColour" style="flex-grow: 1; border-top-left-radius: 8px; border-top-right-radius: 8px; border-bottom-right-radius: 8px; border-bottom-left-radius: 8px; background-color: rgb(0, 0, 0);" />
|
style="flex-grow: 0; flex-shrink: 0; height: 100%; width: 49%;">
|
||||||
|
<ui:Label tabindex="-1" text="Template" parse-escape-sequences="true"
|
||||||
|
display-tooltip-when-elided="true" name="TemplateText"
|
||||||
|
style="margin-bottom: 12px; margin-top: 0; -unity-font-style: normal;"/>
|
||||||
|
<ui:VisualElement name="TemplateColour"
|
||||||
|
style="flex-grow: 1; border-top-left-radius: 8px; border-top-right-radius: 8px; border-bottom-right-radius: 8px; border-bottom-left-radius: 8px; background-color: rgb(0, 0, 0);"/>
|
||||||
</ui:VisualElement>
|
</ui:VisualElement>
|
||||||
<ui:VisualElement name="ResponsePreview" style="flex-grow: 0; flex-shrink: 0; height: 100%; width: 49%;">
|
<ui:VisualElement name="ResponsePreview"
|
||||||
<ui:Label tabindex="-1" text="Response" parse-escape-sequences="true" display-tooltip-when-elided="true" name="ResponseText" style="margin-bottom: 12px; margin-top: 0; -unity-font-style: normal; -unity-text-align: upper-right;" />
|
style="flex-grow: 0; flex-shrink: 0; height: 100%; width: 49%;">
|
||||||
<ui:VisualElement name="ResponseColour" style="flex-grow: 1; border-top-left-radius: 8px; border-top-right-radius: 8px; border-bottom-right-radius: 8px; border-bottom-left-radius: 8px; background-color: rgb(0, 0, 0);" />
|
<ui:Label tabindex="-1" text="Response" parse-escape-sequences="true"
|
||||||
|
display-tooltip-when-elided="true" name="ResponseText"
|
||||||
|
style="margin-bottom: 12px; margin-top: 0; -unity-font-style: normal; -unity-text-align: upper-right;"/>
|
||||||
|
<ui:VisualElement name="ResponseColour"
|
||||||
|
style="flex-grow: 1; border-top-left-radius: 8px; border-top-right-radius: 8px; border-bottom-right-radius: 8px; border-bottom-left-radius: 8px; background-color: rgb(0, 0, 0);"/>
|
||||||
</ui:VisualElement>
|
</ui:VisualElement>
|
||||||
</ui:VisualElement>
|
</ui:VisualElement>
|
||||||
<ui:VisualElement name="ResponseSliders" style="flex-grow: 0; display: flex;">
|
<ui:VisualElement name="ResponseSliders" style="flex-grow: 0; display: flex;">
|
||||||
<ui:Slider label="Lightness" high-value="100" name="ResponseLightnessSlider" class="lch-slider" />
|
<ui:Slider label="Lightness" high-value="100" name="ResponseLightnessSlider" class="lch-slider"/>
|
||||||
<ui:Slider label="Chroma" high-value="0.5" name="ResponseChromaSlider" class="lch-slider" />
|
<ui:Slider label="Chroma" high-value="0.5" name="ResponseChromaSlider" class="lch-slider"/>
|
||||||
<ui:Slider label="Hue" high-value="360" name="ResponseHueSlider" class="lch-slider" />
|
<ui:Slider label="Hue" high-value="360" name="ResponseHueSlider" class="lch-slider"/>
|
||||||
|
</ui:VisualElement>
|
||||||
|
</ui:VisualElement>
|
||||||
|
<ui:VisualElement name="LeaderboardView"
|
||||||
|
style="flex-grow: 1; display: none; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; margin-top: 3.25%; margin-right: 3.25%; margin-bottom: 3.25%; margin-left: 3.25%; flex-direction: column; justify-content: space-between;">
|
||||||
|
<ui:VisualElement name="LeaderboardHeader" style="flex-grow: 0;">
|
||||||
|
<ui:Label tabindex="-1" text="Leaderboard" parse-escape-sequences="true"
|
||||||
|
display-tooltip-when-elided="true" name="RoundText"
|
||||||
|
style="font-size: 58px; -unity-font-style: bold;"/>
|
||||||
|
</ui:VisualElement>
|
||||||
|
<ui:ListView name="LeaderboardListView"/>
|
||||||
|
</ui:VisualElement>
|
||||||
|
<ui:VisualElement name="AccountView"
|
||||||
|
style="flex-grow: 1; display: flex; padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; margin-top: 3.25%; margin-right: 3.25%; margin-bottom: 3.25%; margin-left: 3.25%; flex-direction: column; justify-content: space-between;">
|
||||||
|
<ui:Label tabindex="-1" text="You are not signed in." parse-escape-sequences="true"
|
||||||
|
display-tooltip-when-elided="true" name="AccountHeader"
|
||||||
|
style="font-size: 58px; -unity-font-style: normal;"/>
|
||||||
|
<ui:VisualElement name="AccountFields" style="flex-grow: 0;">
|
||||||
|
<ui:VisualElement name="UsernameContainer"
|
||||||
|
style="flex-grow: 1; flex-direction: row; justify-content: space-between;">
|
||||||
|
<ui:TextField picking-mode="Ignore" label="Username" name="UsernameField"/>
|
||||||
|
<ui:Button text="Update" parse-escape-sequences="true" display-tooltip-when-elided="true"
|
||||||
|
name="UsernameUpdateButton"/>
|
||||||
|
</ui:VisualElement>
|
||||||
|
<ui:VisualElement name="EmailContainer"
|
||||||
|
style="flex-grow: 1; flex-direction: row; justify-content: space-between;">
|
||||||
|
<ui:TextField picking-mode="Ignore" label="Email" name="EmailField"
|
||||||
|
keyboard-type="EmailAddress"/>
|
||||||
|
<ui:Button text="Update" parse-escape-sequences="true" display-tooltip-when-elided="true"
|
||||||
|
name="EmailUpdateButton"/>
|
||||||
|
</ui:VisualElement>
|
||||||
|
<ui:VisualElement name="PasswordContainer"
|
||||||
|
style="flex-grow: 1; flex-direction: row; justify-content: space-between;">
|
||||||
|
<ui:TextField picking-mode="Ignore" label="Password" name="PasswordField" password="true"/>
|
||||||
|
<ui:Button text="Update" parse-escape-sequences="true" display-tooltip-when-elided="true"
|
||||||
|
name="PasswordUpdateButton"/>
|
||||||
|
</ui:VisualElement>
|
||||||
|
<ui:Label tabindex="-1" text="A verification email has been sent. Check your inbox."
|
||||||
|
parse-escape-sequences="true" display-tooltip-when-elided="true" name="AccompanyingText"
|
||||||
|
style="padding-top: 0; padding-right: 0; padding-bottom: 0; padding-left: 0; margin-top: 1.5%; margin-right: 0; margin-bottom: 0.75%; margin-left: 0; -unity-text-align: upper-left; display: none;"/>
|
||||||
|
</ui:VisualElement>
|
||||||
|
<ui:VisualElement name="AccountButtons" style="flex-grow: 0; align-items: flex-start;">
|
||||||
|
<ui:Button text="Primary Action Button →" parse-escape-sequences="true"
|
||||||
|
display-tooltip-when-elided="true" name="PrimaryActionButton"
|
||||||
|
style="-unity-text-align: middle-center; margin-bottom: 1%; margin-right: 1%; margin-top: 1%; -unity-font-style: bold;"/>
|
||||||
|
<ui:Button text="Secondary Action Button →" parse-escape-sequences="true"
|
||||||
|
display-tooltip-when-elided="true" name="SecondaryActionButton"
|
||||||
|
style="margin-top: 1%; margin-right: 1%; -unity-font-style: bold;"/>
|
||||||
</ui:VisualElement>
|
</ui:VisualElement>
|
||||||
</ui:VisualElement>
|
</ui:VisualElement>
|
||||||
<ui:VisualElement name="LeaderboardView" style="flex-grow: 1; visibility: visible; display: none; padding-top: 10%; padding-right: 10%; padding-bottom: 10%; padding-left: 10%;" />
|
|
||||||
<ui:VisualElement name="AccountView" style="flex-grow: 1; display: none; padding-top: 10%; padding-right: 10%; padding-bottom: 10%; padding-left: 10%;" />
|
|
||||||
</ui:VisualElement>
|
</ui:VisualElement>
|
||||||
</ui:VisualElement>
|
</ui:VisualElement>
|
||||||
</ui:UXML>
|
</ui:UXML>
|
||||||
|
|
Reference in a new issue