using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using Uniject; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Purchasing; using UnityEngine.Purchasing.Extension; using UnityEngine.UI; namespace UnityEngine.Purchasing { /// /// User interface fake store. /// internal class UIFakeStore : FakeStore { const string EnvironmentDescriptionPostfix = "\n\n[Environment: FakeStore]"; const string SuccessString = "Success"; const int RetrieveProductsDescriptionCount = 2; DialogRequest m_CurrentDialog; int m_LastSelectedDropdownIndex; GameObject m_UIFakeStoreWindowObject; GameObject m_EventSystem; // Dynamically created. Auto-null'd by UI system. #pragma warning disable 0414 readonly IUtil m_Util; #pragma warning restore 0414 public UIFakeStore() { } public UIFakeStore(IUtil util) { m_Util = util; } /// /// Creates and displays a modal dialog UI. Note pointer events can "drill through" the /// UI activating underlying interface elements. Consider using techniques mentioned in /// http://forum.unity3d.com/threads/frequently-asked-ui-questions.264479/ in apps /// to mitigate this. Shows only one at a time. /// /// true, if UI was started, false otherwise. /// Store model being shown; uses dialogType to decode. /// Dialog type. /// Callback called when dialog dismissed. /// The 1st type parameter. protected override bool StartUI(object model, DialogType dialogType, Action callback) { var options = new List { // Add a default option for "Success" SuccessString }; foreach (T code in Enum.GetValues(typeof(T))) { options.Add(code.ToString()); } var callbackWrapper = new Action((bool result, int codeValue) => { // TRICKY: Would prefer to use .NET 4+'s dynamic keyword over double-casting to what I know is an enum type. var value = (T)(object)codeValue; callback(result, value); }); string title = null, okayButton = null, cancelButton = null; if (dialogType == DialogType.Purchase) { title = CreatePurchaseQuestion((ProductDefinition)model); if (UIMode == FakeStoreUIMode.DeveloperUser) { // Developer UIMode is one button, one option menu, so the button must support both pass and fail okayButton = "OK"; } else { okayButton = "Buy"; } } else if (dialogType == DialogType.RetrieveProducts) { title = CreateRetrieveProductsQuestion((ReadOnlyCollection)model); okayButton = "OK"; } else { Debug.LogError("Unrecognized DialogType " + dialogType); } cancelButton = "Cancel"; return StartUI(title, okayButton, cancelButton, options, callbackWrapper); } /// /// Helper /// bool StartUI(string queryText, string okayButtonText, string cancelButtonText, List options, Action callback) { // One dialog at a time please if (IsShowingDialog()) { return false; } // Wrap this dialog request for later use var dr = new DialogRequest { QueryText = queryText, OkayButtonText = okayButtonText, CancelButtonText = cancelButtonText, Options = options, Callback = callback }; m_CurrentDialog = dr; InstantiateDialog(); return true; } private void InstantiateDialog() { if (m_CurrentDialog != null) { var runtimeCanvas = GetOrCreateFakeStoreWindow(); AddLifeCycleNotifierAndSetDestroyCallback(runtimeCanvas.gameObject); EnsureEventSystemCreated(runtimeCanvas.transform); ConfigureDialogWindow(runtimeCanvas); } else { Debug.LogError(this + " requires m_CurrentDialog. Not showing dialog."); } } private UIFakeStoreWindow GetOrCreateFakeStoreWindow() { if (m_UIFakeStoreWindowObject == null) { m_UIFakeStoreWindowObject = new GameObject("UIFakeStoreWindow", typeof(Transform)); m_UIFakeStoreWindowObject.AddComponent(); } return m_UIFakeStoreWindowObject.GetComponent(); } private void AddLifeCycleNotifierAndSetDestroyCallback(GameObject gameObject) { var notifier = gameObject.AddComponent(); notifier.OnDestroyCallback = () => { m_CurrentDialog = null; }; } private void EnsureEventSystemCreated(Transform rootTransform) { if (Object.FindObjectOfType() == null) { CreateEventSystem(rootTransform); } } private void ConfigureDialogWindow(UIFakeStoreWindow dialogWindow) { var doCancel = UIMode != FakeStoreUIMode.DeveloperUser; var doDropDown = UIMode != FakeStoreUIMode.StandardUser; dialogWindow.ConfigureMainDialogText(m_CurrentDialog.QueryText, m_CurrentDialog.OkayButtonText, m_CurrentDialog.CancelButtonText); if (doDropDown) { dialogWindow.ConfigureDropdownOptions(m_CurrentDialog.Options); } ConfigureDialogWindowCallbacks(dialogWindow, doCancel, doDropDown); } void ConfigureDialogWindowCallbacks(UIFakeStoreWindow dialogWindow, bool assignCancelCallback, bool assignDropDownCallback) { Action cancelAction = null; Action dropdownAction = null; if (assignCancelCallback) { cancelAction = CancelButtonClicked; } if (assignDropDownCallback) { dropdownAction = DropdownValueChanged; } dialogWindow.AssignCallbacks(OkayButtonClicked, cancelAction, dropdownAction); } private void CreateEventSystem(Transform rootTransform) { m_EventSystem = new GameObject("EventSystem", typeof(EventSystem)); m_EventSystem.AddComponent(); m_EventSystem.transform.parent = rootTransform; } private string CreatePurchaseQuestion(ProductDefinition definition) { return "Do you want to Purchase " + definition.id + "?" + EnvironmentDescriptionPostfix; } private string CreateRetrieveProductsQuestion(ReadOnlyCollection definitions) { var title = "Do you want to initialize purchasing for products {"; title += string.Join(", ", definitions.Take(RetrieveProductsDescriptionCount).Select(pid => pid.id).ToArray()); if (definitions.Count > RetrieveProductsDescriptionCount) { title += ", ..."; } title += "}?" + EnvironmentDescriptionPostfix; return title; } /// /// Positive button clicked. For yes/no dialog will send true message. For /// multiselect (FakeStoreUIMode.DeveloperUser) dialog may send true or false /// message, along with chosen option. /// private void OkayButtonClicked() { var result = false; // Return false if the user chose something other than Success, and is in Development mode. // True if the "Success" option was chosen, or if this is non-Development mode. if (m_LastSelectedDropdownIndex == 0 || UIMode != FakeStoreUIMode.DeveloperUser) { // Ensure we return true result = true; } var codeValue = Math.Max(0, m_LastSelectedDropdownIndex - 1); // Pop SuccessString m_CurrentDialog.Callback(result, codeValue); CloseDialog(); } /// /// Negative button clicked. Sends false message. /// private void CancelButtonClicked() { var codeValue = Math.Max(0, m_LastSelectedDropdownIndex - 1); // Pop SuccessString // ASSUME: This is FakeStoreUIMode.StandardUser m_CurrentDialog.Callback(false, codeValue); CloseDialog(); } private void DropdownValueChanged(int selectedItem) { m_LastSelectedDropdownIndex = selectedItem; } private void CloseDialog() { m_CurrentDialog = null; if (m_UIFakeStoreWindowObject != null) { Object.Destroy(m_UIFakeStoreWindowObject); } } public bool IsShowingDialog() { return m_CurrentDialog != null; } } }